aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.clang-format10
-rw-r--r--CMakeLists.txt19
-rw-r--r--README.md120
-rw-r--r--cmake/FindQt5.cmake103
-rw-r--r--cmake/QtCreatorAPI.cmake4
-rw-r--r--cmake/QtCreatorAPIInternal.cmake3
-rw-r--r--cmake/QtCreatorDocumentation.cmake4
-rw-r--r--cmake/QtCreatorIDEBranding.cmake6
-rw-r--r--cmake/QtCreatorTranslations.cmake4
-rw-r--r--doc/qtcreator/src/overview/creator-acknowledgements.qdoc57
-rw-r--r--qbs/imports/QtcTestFiles.qbs6
-rw-r--r--qbs/modules/qtc/qtc.qbs6
-rw-r--r--share/qtcreator/CMakeLists.txt4
-rw-r--r--share/qtcreator/debugger/creatortypes.py4
-rw-r--r--share/qtcreator/debugger/dumper.py18
-rw-r--r--share/qtcreator/debugger/lldbbridge.py11
-rw-r--r--share/qtcreator/debugger/pdbbridge.py2
-rw-r--r--share/qtcreator/debugger/utils.py1
-rwxr-xr-xshare/qtcreator/scripts/openTerminal.py112
-rw-r--r--share/qtcreator/templates/qt4project/customwidgetwizard/tpl_collection.cpp2
-rw-r--r--share/qtcreator/templates/qt4project/customwidgetwizard/tpl_collection.h4
-rw-r--r--share/qtcreator/templates/qt4project/customwidgetwizard/tpl_plugin.pro6
-rw-r--r--share/qtcreator/templates/qt4project/customwidgetwizard/tpl_single.cpp4
-rw-r--r--share/qtcreator/templates/qt4project/customwidgetwizard/tpl_single.h26
-rw-r--r--share/qtcreator/templates/qt4project/customwidgetwizard/tpl_widget.h2
-rw-r--r--share/qtcreator/templates/wizards/projects/qtforpythonapplication/empty/wizard.json32
-rw-r--r--share/qtcreator/templates/wizards/projects/qtforpythonapplication/mainwindow/wizard.json36
-rw-r--r--share/qtcreator/templates/wizards/projects/qtforpythonapplication/qtquickapplication/wizard.json108
-rw-r--r--share/qtcreator/templates/wizards/projects/qtforpythonapplication/widget/wizard.json36
-rw-r--r--share/qtcreator/templates/wizards/qtcreatorplugin/CMakeLists.txt17
-rw-r--r--share/qtcreator/templates/wizards/qtcreatorplugin/myplugin_global.h2
-rw-r--r--share/qtcreator/themes/dark.creatortheme22
-rw-r--r--share/qtcreator/themes/default.creatortheme22
-rw-r--r--share/qtcreator/themes/design-light.creatortheme22
-rw-r--r--share/qtcreator/themes/design.creatortheme22
-rw-r--r--share/qtcreator/themes/flat-dark.creatortheme22
-rw-r--r--share/qtcreator/themes/flat-light.creatortheme22
-rw-r--r--share/qtcreator/themes/flat.creatortheme22
-rw-r--r--src/CMakeLists.txt9
-rw-r--r--src/libs/3rdparty/CMakeLists.txt6
-rw-r--r--src/libs/3rdparty/cplusplus/AST.cpp60
-rw-r--r--src/libs/3rdparty/cplusplus/AST.h160
-rw-r--r--src/libs/3rdparty/cplusplus/ASTClone.cpp97
-rw-r--r--src/libs/3rdparty/cplusplus/ASTMatch0.cpp51
-rw-r--r--src/libs/3rdparty/cplusplus/ASTMatcher.cpp126
-rw-r--r--src/libs/3rdparty/cplusplus/ASTMatcher.h7
-rw-r--r--src/libs/3rdparty/cplusplus/ASTVisit.cpp61
-rw-r--r--src/libs/3rdparty/cplusplus/ASTVisitor.h14
-rw-r--r--src/libs/3rdparty/cplusplus/ASTfwd.h7
-rw-r--r--src/libs/3rdparty/cplusplus/Bind.cpp10
-rw-r--r--src/libs/3rdparty/cplusplus/Bind.h2
-rw-r--r--src/libs/3rdparty/cplusplus/Keywords.cpp104
-rw-r--r--src/libs/3rdparty/cplusplus/Keywords.kwgen10
-rw-r--r--src/libs/3rdparty/cplusplus/Parser.cpp304
-rw-r--r--src/libs/3rdparty/cplusplus/Parser.h8
-rw-r--r--src/libs/3rdparty/cplusplus/Symbol.h4
-rw-r--r--src/libs/3rdparty/cplusplus/Token.cpp8
-rw-r--r--src/libs/3rdparty/cplusplus/Token.h8
-rw-r--r--src/libs/3rdparty/libptyqt/.clang-format1
-rw-r--r--src/libs/3rdparty/libptyqt/CMakeLists.txt28
-rw-r--r--src/libs/3rdparty/libptyqt/LICENSE21
-rw-r--r--src/libs/3rdparty/libptyqt/conptyprocess.cpp341
-rw-r--r--src/libs/3rdparty/libptyqt/conptyprocess.h169
-rw-r--r--src/libs/3rdparty/libptyqt/iptyprocess.h56
-rw-r--r--src/libs/3rdparty/libptyqt/ptyqt.cpp45
-rw-r--r--src/libs/3rdparty/libptyqt/ptyqt.h12
-rw-r--r--src/libs/3rdparty/libptyqt/ptyqt.qbs45
-rw-r--r--src/libs/3rdparty/libptyqt/unixptyprocess.cpp374
-rw-r--r--src/libs/3rdparty/libptyqt/unixptyprocess.h66
-rw-r--r--src/libs/3rdparty/libptyqt/winptyprocess.cpp278
-rw-r--r--src/libs/3rdparty/libptyqt/winptyprocess.h43
-rw-r--r--src/libs/3rdparty/libvterm/CMakeLists.txt18
-rw-r--r--src/libs/3rdparty/libvterm/CONTRIBUTING22
-rw-r--r--src/libs/3rdparty/libvterm/LICENSE23
-rw-r--r--src/libs/3rdparty/libvterm/include/vterm.h637
-rw-r--r--src/libs/3rdparty/libvterm/include/vterm_keycodes.h61
-rw-r--r--src/libs/3rdparty/libvterm/src/encoding.c230
-rw-r--r--src/libs/3rdparty/libvterm/src/encoding/DECdrawing.inc36
-rw-r--r--src/libs/3rdparty/libvterm/src/encoding/uk.inc6
-rw-r--r--src/libs/3rdparty/libvterm/src/fullwidth.inc111
-rw-r--r--src/libs/3rdparty/libvterm/src/keyboard.c226
-rw-r--r--src/libs/3rdparty/libvterm/src/mouse.c99
-rw-r--r--src/libs/3rdparty/libvterm/src/parser.c402
-rw-r--r--src/libs/3rdparty/libvterm/src/pen.c613
-rw-r--r--src/libs/3rdparty/libvterm/src/rect.h56
-rw-r--r--src/libs/3rdparty/libvterm/src/screen.c1183
-rw-r--r--src/libs/3rdparty/libvterm/src/state.c2315
-rw-r--r--src/libs/3rdparty/libvterm/src/unicode.c313
-rw-r--r--src/libs/3rdparty/libvterm/src/utf8.h39
-rw-r--r--src/libs/3rdparty/libvterm/src/vterm.c429
-rw-r--r--src/libs/3rdparty/libvterm/src/vterm_internal.h296
-rw-r--r--src/libs/3rdparty/libvterm/vterm.pc.in8
-rw-r--r--src/libs/3rdparty/libvterm/vterm.qbs34
-rw-r--r--src/libs/3rdparty/winpty/.gitattributes19
-rw-r--r--src/libs/3rdparty/winpty/.gitignore16
-rw-r--r--src/libs/3rdparty/winpty/CMakeLists.txt1
-rw-r--r--src/libs/3rdparty/winpty/LICENSE21
-rw-r--r--src/libs/3rdparty/winpty/README.md151
-rw-r--r--src/libs/3rdparty/winpty/RELEASES.md280
-rw-r--r--src/libs/3rdparty/winpty/VERSION.txt1
-rw-r--r--src/libs/3rdparty/winpty/appveyor.yml16
-rw-r--r--src/libs/3rdparty/winpty/configure167
-rw-r--r--src/libs/3rdparty/winpty/misc/.gitignore2
-rw-r--r--src/libs/3rdparty/winpty/misc/BufferResizeTests.cc90
-rw-r--r--src/libs/3rdparty/winpty/misc/ChangeScreenBuffer.cc53
-rw-r--r--src/libs/3rdparty/winpty/misc/ClearConsole.cc72
-rw-r--r--src/libs/3rdparty/winpty/misc/ConinMode.cc117
-rw-r--r--src/libs/3rdparty/winpty/misc/ConinMode.ps1116
-rw-r--r--src/libs/3rdparty/winpty/misc/ConoutMode.cc113
-rw-r--r--src/libs/3rdparty/winpty/misc/DebugClient.py42
-rw-r--r--src/libs/3rdparty/winpty/misc/DebugServer.py63
-rw-r--r--src/libs/3rdparty/winpty/misc/DumpLines.py5
-rw-r--r--src/libs/3rdparty/winpty/misc/EnableExtendedFlags.txt46
-rw-r--r--src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP437-Consolas.txt528
-rw-r--r--src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP437-Lucida.txt633
-rw-r--r--src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP932.txt630
-rw-r--r--src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP936.txt630
-rw-r--r--src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP949.txt630
-rw-r--r--src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP950.txt630
-rw-r--r--src/libs/3rdparty/winpty/misc/Font-Report-June2016/MinimumWindowWidths.txt16
-rw-r--r--src/libs/3rdparty/winpty/misc/Font-Report-June2016/Results.txt4
-rw-r--r--src/libs/3rdparty/winpty/misc/Font-Report-June2016/Windows10SetFontBugginess.txt144
-rw-r--r--src/libs/3rdparty/winpty/misc/FontSurvey.cc100
-rw-r--r--src/libs/3rdparty/winpty/misc/FormatChar.h21
-rw-r--r--src/libs/3rdparty/winpty/misc/FreezePerfTest.cc62
-rw-r--r--src/libs/3rdparty/winpty/misc/GetCh.cc20
-rw-r--r--src/libs/3rdparty/winpty/misc/GetConsolePos.cc41
-rw-r--r--src/libs/3rdparty/winpty/misc/GetFont.cc261
-rw-r--r--src/libs/3rdparty/winpty/misc/IdentifyConsoleWindow.ps151
-rw-r--r--src/libs/3rdparty/winpty/misc/IsNewConsole.cc87
-rw-r--r--src/libs/3rdparty/winpty/misc/MouseInputNotes.txt90
-rw-r--r--src/libs/3rdparty/winpty/misc/MoveConsoleWindow.cc34
-rw-r--r--src/libs/3rdparty/winpty/misc/Notes.txt219
-rw-r--r--src/libs/3rdparty/winpty/misc/OSVersion.cc27
-rw-r--r--src/libs/3rdparty/winpty/misc/ScreenBufferFreezeInactive.cc101
-rw-r--r--src/libs/3rdparty/winpty/misc/ScreenBufferTest.cc671
-rw-r--r--src/libs/3rdparty/winpty/misc/ScreenBufferTest2.cc151
-rw-r--r--src/libs/3rdparty/winpty/misc/SelectAllTest.cc45
-rw-r--r--src/libs/3rdparty/winpty/misc/SetBufInfo.cc90
-rw-r--r--src/libs/3rdparty/winpty/misc/SetBufferSize.cc32
-rw-r--r--src/libs/3rdparty/winpty/misc/SetCursorPos.cc10
-rw-r--r--src/libs/3rdparty/winpty/misc/SetFont.cc145
-rw-r--r--src/libs/3rdparty/winpty/misc/SetWindowRect.cc36
-rw-r--r--src/libs/3rdparty/winpty/misc/ShowArgv.cc12
-rw-r--r--src/libs/3rdparty/winpty/misc/ShowConsoleInput.cc40
-rw-r--r--src/libs/3rdparty/winpty/misc/Spew.py5
-rw-r--r--src/libs/3rdparty/winpty/misc/TestUtil.cc172
-rw-r--r--src/libs/3rdparty/winpty/misc/UnicodeDoubleWidthTest.cc102
-rw-r--r--src/libs/3rdparty/winpty/misc/UnicodeWideTest1.cc246
-rw-r--r--src/libs/3rdparty/winpty/misc/UnicodeWideTest2.cc130
-rw-r--r--src/libs/3rdparty/winpty/misc/UnixEcho.cc89
-rw-r--r--src/libs/3rdparty/winpty/misc/Utf16Echo.cc46
-rw-r--r--src/libs/3rdparty/winpty/misc/VeryLargeRead.cc122
-rw-r--r--src/libs/3rdparty/winpty/misc/VkEscapeTest.cc56
-rw-r--r--src/libs/3rdparty/winpty/misc/Win10ResizeWhileFrozen.cc52
-rw-r--r--src/libs/3rdparty/winpty/misc/Win10WrapTest1.cc57
-rw-r--r--src/libs/3rdparty/winpty/misc/Win10WrapTest2.cc30
-rw-r--r--src/libs/3rdparty/winpty/misc/Win32Echo1.cc26
-rw-r--r--src/libs/3rdparty/winpty/misc/Win32Echo2.cc19
-rw-r--r--src/libs/3rdparty/winpty/misc/Win32Test1.cc46
-rw-r--r--src/libs/3rdparty/winpty/misc/Win32Test2.cc70
-rw-r--r--src/libs/3rdparty/winpty/misc/Win32Test3.cc78
-rw-r--r--src/libs/3rdparty/winpty/misc/Win32Write1.cc44
-rw-r--r--src/libs/3rdparty/winpty/misc/WindowsBugCrashReader.cc27
-rw-r--r--src/libs/3rdparty/winpty/misc/WriteConsole.cc106
-rw-r--r--src/libs/3rdparty/winpty/misc/build32.sh9
-rw-r--r--src/libs/3rdparty/winpty/misc/build64.sh9
-rw-r--r--src/libs/3rdparty/winpty/misc/color-test.sh212
-rw-r--r--src/libs/3rdparty/winpty/misc/font-notes.txt300
-rw-r--r--src/libs/3rdparty/winpty/misc/winbug-15048.cc201
-rw-r--r--src/libs/3rdparty/winpty/ship/build-pty4j-libpty.bat36
-rw-r--r--src/libs/3rdparty/winpty/ship/common_ship.py89
-rw-r--r--src/libs/3rdparty/winpty/ship/make_msvc_package.py163
-rw-r--r--src/libs/3rdparty/winpty/ship/ship.py104
-rw-r--r--src/libs/3rdparty/winpty/src/CMakeLists.txt111
-rw-r--r--src/libs/3rdparty/winpty/src/agent/Agent.cc612
-rw-r--r--src/libs/3rdparty/winpty/src/agent/Agent.h103
-rw-r--r--src/libs/3rdparty/winpty/src/agent/AgentCreateDesktop.cc84
-rw-r--r--src/libs/3rdparty/winpty/src/agent/AgentCreateDesktop.h28
-rw-r--r--src/libs/3rdparty/winpty/src/agent/ConsoleFont.cc698
-rw-r--r--src/libs/3rdparty/winpty/src/agent/ConsoleFont.h28
-rw-r--r--src/libs/3rdparty/winpty/src/agent/ConsoleInput.cc852
-rw-r--r--src/libs/3rdparty/winpty/src/agent/ConsoleInput.h109
-rw-r--r--src/libs/3rdparty/winpty/src/agent/ConsoleInputReencoding.cc121
-rw-r--r--src/libs/3rdparty/winpty/src/agent/ConsoleInputReencoding.h36
-rw-r--r--src/libs/3rdparty/winpty/src/agent/ConsoleLine.cc152
-rw-r--r--src/libs/3rdparty/winpty/src/agent/ConsoleLine.h41
-rw-r--r--src/libs/3rdparty/winpty/src/agent/Coord.h87
-rw-r--r--src/libs/3rdparty/winpty/src/agent/DebugShowInput.cc239
-rw-r--r--src/libs/3rdparty/winpty/src/agent/DebugShowInput.h32
-rw-r--r--src/libs/3rdparty/winpty/src/agent/DefaultInputMap.cc422
-rw-r--r--src/libs/3rdparty/winpty/src/agent/DefaultInputMap.h28
-rw-r--r--src/libs/3rdparty/winpty/src/agent/DsrSender.h30
-rw-r--r--src/libs/3rdparty/winpty/src/agent/EventLoop.cc99
-rw-r--r--src/libs/3rdparty/winpty/src/agent/EventLoop.h47
-rw-r--r--src/libs/3rdparty/winpty/src/agent/InputMap.cc246
-rw-r--r--src/libs/3rdparty/winpty/src/agent/InputMap.h114
-rw-r--r--src/libs/3rdparty/winpty/src/agent/LargeConsoleRead.cc71
-rw-r--r--src/libs/3rdparty/winpty/src/agent/LargeConsoleRead.h68
-rw-r--r--src/libs/3rdparty/winpty/src/agent/NamedPipe.cc378
-rw-r--r--src/libs/3rdparty/winpty/src/agent/NamedPipe.h125
-rw-r--r--src/libs/3rdparty/winpty/src/agent/Scraper.cc699
-rw-r--r--src/libs/3rdparty/winpty/src/agent/Scraper.h103
-rw-r--r--src/libs/3rdparty/winpty/src/agent/SimplePool.h75
-rw-r--r--src/libs/3rdparty/winpty/src/agent/SmallRect.h143
-rw-r--r--src/libs/3rdparty/winpty/src/agent/Terminal.cc535
-rw-r--r--src/libs/3rdparty/winpty/src/agent/Terminal.h69
-rw-r--r--src/libs/3rdparty/winpty/src/agent/UnicodeEncoding.h157
-rw-r--r--src/libs/3rdparty/winpty/src/agent/UnicodeEncodingTest.cc189
-rw-r--r--src/libs/3rdparty/winpty/src/agent/Win32Console.cc107
-rw-r--r--src/libs/3rdparty/winpty/src/agent/Win32Console.h67
-rw-r--r--src/libs/3rdparty/winpty/src/agent/Win32ConsoleBuffer.cc193
-rw-r--r--src/libs/3rdparty/winpty/src/agent/Win32ConsoleBuffer.h99
-rw-r--r--src/libs/3rdparty/winpty/src/agent/main.cc120
-rw-r--r--src/libs/3rdparty/winpty/src/agent/subdir.mk61
-rw-r--r--src/libs/3rdparty/winpty/src/configurations.gypi60
-rw-r--r--src/libs/3rdparty/winpty/src/debugserver/DebugServer.cc117
-rw-r--r--src/libs/3rdparty/winpty/src/debugserver/subdir.mk41
-rw-r--r--src/libs/3rdparty/winpty/src/include/winpty.h242
-rw-r--r--src/libs/3rdparty/winpty/src/include/winpty_constants.h131
-rw-r--r--src/libs/3rdparty/winpty/src/libwinpty/AgentLocation.cc75
-rw-r--r--src/libs/3rdparty/winpty/src/libwinpty/AgentLocation.h28
-rw-r--r--src/libs/3rdparty/winpty/src/libwinpty/LibWinptyException.h54
-rw-r--r--src/libs/3rdparty/winpty/src/libwinpty/WinptyInternal.h72
-rw-r--r--src/libs/3rdparty/winpty/src/libwinpty/subdir.mk46
-rw-r--r--src/libs/3rdparty/winpty/src/libwinpty/winpty.cc970
-rw-r--r--src/libs/3rdparty/winpty/src/shared/AgentMsg.h38
-rw-r--r--src/libs/3rdparty/winpty/src/shared/BackgroundDesktop.cc122
-rw-r--r--src/libs/3rdparty/winpty/src/shared/BackgroundDesktop.h73
-rw-r--r--src/libs/3rdparty/winpty/src/shared/Buffer.cc103
-rw-r--r--src/libs/3rdparty/winpty/src/shared/Buffer.h102
-rw-r--r--src/libs/3rdparty/winpty/src/shared/DebugClient.cc187
-rw-r--r--src/libs/3rdparty/winpty/src/shared/DebugClient.h38
-rw-r--r--src/libs/3rdparty/winpty/src/shared/GenRandom.cc138
-rw-r--r--src/libs/3rdparty/winpty/src/shared/GenRandom.h55
-rw-r--r--src/libs/3rdparty/winpty/src/shared/GetCommitHash.bat13
-rw-r--r--src/libs/3rdparty/winpty/src/shared/Mutex.h54
-rw-r--r--src/libs/3rdparty/winpty/src/shared/OsModule.h63
-rw-r--r--src/libs/3rdparty/winpty/src/shared/OwnedHandle.cc36
-rw-r--r--src/libs/3rdparty/winpty/src/shared/OwnedHandle.h45
-rw-r--r--src/libs/3rdparty/winpty/src/shared/PrecompiledHeader.h43
-rw-r--r--src/libs/3rdparty/winpty/src/shared/StringBuilder.h227
-rw-r--r--src/libs/3rdparty/winpty/src/shared/StringBuilderTest.cc114
-rw-r--r--src/libs/3rdparty/winpty/src/shared/StringUtil.cc55
-rw-r--r--src/libs/3rdparty/winpty/src/shared/StringUtil.h80
-rw-r--r--src/libs/3rdparty/winpty/src/shared/TimeMeasurement.h63
-rw-r--r--src/libs/3rdparty/winpty/src/shared/UnixCtrlChars.h45
-rw-r--r--src/libs/3rdparty/winpty/src/shared/UpdateGenVersion.bat20
-rw-r--r--src/libs/3rdparty/winpty/src/shared/WindowsSecurity.cc460
-rw-r--r--src/libs/3rdparty/winpty/src/shared/WindowsSecurity.h104
-rw-r--r--src/libs/3rdparty/winpty/src/shared/WindowsVersion.cc252
-rw-r--r--src/libs/3rdparty/winpty/src/shared/WindowsVersion.h29
-rw-r--r--src/libs/3rdparty/winpty/src/shared/WinptyAssert.cc55
-rw-r--r--src/libs/3rdparty/winpty/src/shared/WinptyAssert.h64
-rw-r--r--src/libs/3rdparty/winpty/src/shared/WinptyException.cc57
-rw-r--r--src/libs/3rdparty/winpty/src/shared/WinptyException.h43
-rw-r--r--src/libs/3rdparty/winpty/src/shared/WinptyVersion.cc42
-rw-r--r--src/libs/3rdparty/winpty/src/shared/WinptyVersion.h27
-rw-r--r--src/libs/3rdparty/winpty/src/shared/winpty_snprintf.h99
-rw-r--r--src/libs/3rdparty/winpty/src/subdir.mk5
-rw-r--r--src/libs/3rdparty/winpty/src/tests/subdir.mk28
-rw-r--r--src/libs/3rdparty/winpty/src/tests/trivial_test.cc158
-rw-r--r--src/libs/3rdparty/winpty/src/unix-adapter/InputHandler.cc114
-rw-r--r--src/libs/3rdparty/winpty/src/unix-adapter/InputHandler.h56
-rw-r--r--src/libs/3rdparty/winpty/src/unix-adapter/OutputHandler.cc80
-rw-r--r--src/libs/3rdparty/winpty/src/unix-adapter/OutputHandler.h53
-rw-r--r--src/libs/3rdparty/winpty/src/unix-adapter/Util.cc86
-rw-r--r--src/libs/3rdparty/winpty/src/unix-adapter/Util.h31
-rw-r--r--src/libs/3rdparty/winpty/src/unix-adapter/WakeupFd.cc70
-rw-r--r--src/libs/3rdparty/winpty/src/unix-adapter/WakeupFd.h42
-rw-r--r--src/libs/3rdparty/winpty/src/unix-adapter/main.cc729
-rw-r--r--src/libs/3rdparty/winpty/src/unix-adapter/subdir.mk41
-rw-r--r--src/libs/3rdparty/winpty/src/winpty.gyp206
-rw-r--r--src/libs/3rdparty/winpty/vcbuild.bat83
-rw-r--r--src/libs/3rdparty/winpty/winpty.qbs207
-rw-r--r--src/libs/advanceddockingsystem/ads_globals_p.h8
-rw-r--r--src/libs/advanceddockingsystem/dockareatabbar.cpp3
-rw-r--r--src/libs/advanceddockingsystem/dockareatitlebar.cpp3
-rw-r--r--src/libs/advanceddockingsystem/dockareawidget.cpp3
-rw-r--r--src/libs/advanceddockingsystem/dockcontainerwidget.cpp3
-rw-r--r--src/libs/advanceddockingsystem/dockmanager.cpp3
-rw-r--r--src/libs/advanceddockingsystem/docksplitter.cpp3
-rw-r--r--src/libs/advanceddockingsystem/dockwidget.cpp3
-rw-r--r--src/libs/advanceddockingsystem/dockwidgettab.cpp3
-rw-r--r--src/libs/advanceddockingsystem/floatingdockcontainer.cpp3
-rw-r--r--src/libs/advanceddockingsystem/floatingdragpreview.cpp3
-rw-r--r--src/libs/cplusplus/CppDocument.cpp10
-rw-r--r--src/libs/cplusplus/CppDocument.h8
-rw-r--r--src/libs/cplusplus/DependencyTable.cpp18
-rw-r--r--src/libs/cplusplus/DependencyTable.h5
-rw-r--r--src/libs/cplusplus/cplusplus.qbs1
-rw-r--r--src/libs/cplusplus/pp-engine.cpp63
-rw-r--r--src/libs/cplusplus/pp-engine.h1
-rw-r--r--src/libs/extensionsystem/CMakeLists.txt2
-rw-r--r--src/libs/languageserverprotocol/jsonrpcmessages.h1
-rw-r--r--src/libs/languageserverprotocol/lsptypes.cpp10
-rw-r--r--src/libs/languageserverprotocol/lsptypes.h3
-rw-r--r--src/libs/languageserverprotocol/lsputils.h7
-rw-r--r--src/libs/languageserverprotocol/workspace.h2
-rw-r--r--src/libs/libs.qbs4
m---------src/libs/qlitehtml0
-rw-r--r--src/libs/qmljs/qmljsmodelmanagerinterface.cpp95
-rw-r--r--src/libs/qmljs/qmljsmodelmanagerinterface.h19
-rw-r--r--src/libs/qmljs/qmljsplugindumper.cpp22
-rw-r--r--src/libs/qmljs/qmljsutils.cpp5
-rw-r--r--src/libs/tracing/timelinetracemanager.cpp36
-rw-r--r--src/libs/utils/CMakeLists.txt19
-rw-r--r--src/libs/utils/aspects.cpp86
-rw-r--r--src/libs/utils/aspects.h27
-rw-r--r--src/libs/utils/asynctask.cpp49
-rw-r--r--src/libs/utils/asynctask.h68
-rw-r--r--src/libs/utils/clangutils.cpp5
-rw-r--r--src/libs/utils/commandline.cpp38
-rw-r--r--src/libs/utils/commandline.h4
-rw-r--r--src/libs/utils/devicefileaccess.cpp120
-rw-r--r--src/libs/utils/devicefileaccess.h25
-rw-r--r--src/libs/utils/deviceshell.cpp1
-rw-r--r--src/libs/utils/differ.cpp25
-rw-r--r--src/libs/utils/differ.h8
-rw-r--r--src/libs/utils/dropsupport.cpp4
-rw-r--r--src/libs/utils/environment.cpp480
-rw-r--r--src/libs/utils/environment.h125
-rw-r--r--src/libs/utils/filepath.cpp448
-rw-r--r--src/libs/utils/filepath.h29
-rw-r--r--src/libs/utils/filesearch.cpp23
-rw-r--r--src/libs/utils/filesearch.h7
-rw-r--r--src/libs/utils/filestreamer.cpp510
-rw-r--r--src/libs/utils/filestreamer.h62
-rw-r--r--src/libs/utils/filestreamermanager.cpp201
-rw-r--r--src/libs/utils/filestreamermanager.h42
-rw-r--r--src/libs/utils/fileutils.cpp12
-rw-r--r--src/libs/utils/fileutils.h7
-rw-r--r--src/libs/utils/fileutils_mac.h1
-rw-r--r--src/libs/utils/fileutils_mac.mm11
-rw-r--r--src/libs/utils/fsengine/diriterator.h3
-rw-r--r--src/libs/utils/fsengine/fileiconprovider.cpp41
-rw-r--r--src/libs/utils/fsengine/fileiteratordevicesappender.h5
-rw-r--r--src/libs/utils/fsengine/fixedlistfsengine.h2
-rw-r--r--src/libs/utils/fsengine/fsengine_impl.cpp2
-rw-r--r--src/libs/utils/fsengine/fsenginehandler.cpp33
-rw-r--r--src/libs/utils/images/iconoverlay_close_small.pngbin0 -> 173 bytes
-rw-r--r--src/libs/utils/images/[email protected]bin0 -> 249 bytes
-rw-r--r--src/libs/utils/images/pinned_small.pngbin0 -> 145 bytes
-rw-r--r--src/libs/utils/images/[email protected]bin0 -> 233 bytes
-rw-r--r--src/libs/utils/launcherpackets.cpp6
-rw-r--r--src/libs/utils/launcherpackets.h1
-rw-r--r--src/libs/utils/launchersocket.cpp3
-rw-r--r--src/libs/utils/link.h22
-rw-r--r--src/libs/utils/multitextcursor.cpp271
-rw-r--r--src/libs/utils/multitextcursor.h46
-rw-r--r--src/libs/utils/osspecificaspects.h30
-rw-r--r--src/libs/utils/pathchooser.cpp14
-rw-r--r--src/libs/utils/pathchooser.h2
-rw-r--r--src/libs/utils/process_ctrlc_stub.cpp10
-rw-r--r--src/libs/utils/process_stub.qbs21
-rw-r--r--src/libs/utils/process_stub_unix.c340
-rw-r--r--src/libs/utils/process_stub_win.c204
-rw-r--r--src/libs/utils/processenums.h1
-rw-r--r--src/libs/utils/processinfo.cpp175
-rw-r--r--src/libs/utils/processinfo.h4
-rw-r--r--src/libs/utils/processinterface.cpp11
-rw-r--r--src/libs/utils/processinterface.h30
-rw-r--r--src/libs/utils/processutils.cpp33
-rw-r--r--src/libs/utils/processutils.h2
-rw-r--r--src/libs/utils/qrcparser.h3
-rw-r--r--src/libs/utils/qtcolorbutton.cpp74
-rw-r--r--src/libs/utils/qtcprocess.cpp161
-rw-r--r--src/libs/utils/qtcprocess.h7
-rw-r--r--src/libs/utils/stringtable.cpp10
-rw-r--r--src/libs/utils/stringutils.cpp19
-rw-r--r--src/libs/utils/stringutils.h4
-rw-r--r--src/libs/utils/tasktree.cpp434
-rw-r--r--src/libs/utils/tasktree.h91
-rw-r--r--src/libs/utils/terminalcommand.cpp8
-rw-r--r--src/libs/utils/terminalhooks.cpp190
-rw-r--r--src/libs/utils/terminalhooks.h93
-rw-r--r--src/libs/utils/terminalinterface.cpp411
-rw-r--r--src/libs/utils/terminalinterface.h61
-rw-r--r--src/libs/utils/terminalprocess.cpp721
-rw-r--r--src/libs/utils/terminalprocess_p.h54
-rw-r--r--src/libs/utils/theme/theme.h23
-rw-r--r--src/libs/utils/utils.qbs11
-rw-r--r--src/libs/utils/utils.qrc4
-rw-r--r--src/libs/utils/utiltypes.h14
-rw-r--r--src/plugins/CMakeLists.txt3
-rw-r--r--src/plugins/android/android.qbs4
-rw-r--r--src/plugins/android/androidavdmanager.cpp26
-rw-r--r--src/plugins/android/androidavdmanager.h9
-rw-r--r--src/plugins/android/androidbuildapkstep.cpp5
-rw-r--r--src/plugins/android/androidconfigurations.cpp6
-rw-r--r--src/plugins/android/androidcreatekeystorecertificate.cpp3
-rw-r--r--src/plugins/android/androiddeployqtstep.cpp105
-rw-r--r--src/plugins/android/androiddeployqtstep.h83
-rw-r--r--src/plugins/android/androiddevice.cpp10
-rw-r--r--src/plugins/android/androidmanager.cpp6
-rw-r--r--src/plugins/android/androidmanifesteditorwidget.cpp4
-rw-r--r--src/plugins/android/androidplugin.cpp4
-rw-r--r--src/plugins/android/androidqmlpreviewworker.cpp4
-rw-r--r--src/plugins/android/androidruncontrol.h6
-rw-r--r--src/plugins/android/androidrunnerworker.cpp13
-rw-r--r--src/plugins/android/androidsdkmanager.cpp78
-rw-r--r--src/plugins/autotest/autotest.qbs4
-rw-r--r--src/plugins/autotest/autotestplugin.cpp18
-rw-r--r--src/plugins/autotest/autotestunittests.cpp16
-rw-r--r--src/plugins/autotest/boost/boosttestconfiguration.cpp6
-rw-r--r--src/plugins/autotest/boost/boosttestconfiguration.h3
-rw-r--r--src/plugins/autotest/boost/boosttestoutputreader.cpp6
-rw-r--r--src/plugins/autotest/boost/boosttestoutputreader.h3
-rw-r--r--src/plugins/autotest/boost/boosttestparser.cpp5
-rw-r--r--src/plugins/autotest/boost/boosttestparser.h2
-rw-r--r--src/plugins/autotest/boost/boosttesttreeitem.cpp10
-rw-r--r--src/plugins/autotest/catch/catchconfiguration.cpp8
-rw-r--r--src/plugins/autotest/catch/catchconfiguration.h3
-rw-r--r--src/plugins/autotest/catch/catchoutputreader.cpp9
-rw-r--r--src/plugins/autotest/catch/catchoutputreader.h3
-rw-r--r--src/plugins/autotest/catch/catchtestparser.cpp5
-rw-r--r--src/plugins/autotest/catch/catchtestparser.h2
-rw-r--r--src/plugins/autotest/catch/catchtreeitem.cpp16
-rw-r--r--src/plugins/autotest/ctest/ctestconfiguration.cpp5
-rw-r--r--src/plugins/autotest/ctest/ctestconfiguration.h3
-rw-r--r--src/plugins/autotest/ctest/ctestoutputreader.cpp7
-rw-r--r--src/plugins/autotest/ctest/ctestoutputreader.h3
-rw-r--r--src/plugins/autotest/ctest/ctesttreeitem.cpp6
-rw-r--r--src/plugins/autotest/gtest/gtestconfiguration.cpp7
-rw-r--r--src/plugins/autotest/gtest/gtestconfiguration.h3
-rw-r--r--src/plugins/autotest/gtest/gtestoutputreader.cpp9
-rw-r--r--src/plugins/autotest/gtest/gtestoutputreader.h3
-rw-r--r--src/plugins/autotest/gtest/gtestparser.cpp5
-rw-r--r--src/plugins/autotest/gtest/gtestparser.h2
-rw-r--r--src/plugins/autotest/gtest/gtesttreeitem.cpp14
-rw-r--r--src/plugins/autotest/itestparser.h6
-rw-r--r--src/plugins/autotest/qtest/qttestconfiguration.cpp9
-rw-r--r--src/plugins/autotest/qtest/qttestconfiguration.h3
-rw-r--r--src/plugins/autotest/qtest/qttestoutputreader.cpp18
-rw-r--r--src/plugins/autotest/qtest/qttestoutputreader.h6
-rw-r--r--src/plugins/autotest/qtest/qttestparser.cpp5
-rw-r--r--src/plugins/autotest/qtest/qttestparser.h2
-rw-r--r--src/plugins/autotest/qtest/qttesttreeitem.cpp16
-rw-r--r--src/plugins/autotest/quick/quicktestconfiguration.cpp14
-rw-r--r--src/plugins/autotest/quick/quicktestconfiguration.h3
-rw-r--r--src/plugins/autotest/quick/quicktestparser.cpp38
-rw-r--r--src/plugins/autotest/quick/quicktestparser.h4
-rw-r--r--src/plugins/autotest/quick/quicktesttreeitem.cpp14
-rw-r--r--src/plugins/autotest/testcodeparser.cpp109
-rw-r--r--src/plugins/autotest/testcodeparser.h21
-rw-r--r--src/plugins/autotest/testconfiguration.cpp16
-rw-r--r--src/plugins/autotest/testconfiguration.h4
-rw-r--r--src/plugins/autotest/testnavigationwidget.cpp8
-rw-r--r--src/plugins/autotest/testoutputreader.cpp17
-rw-r--r--src/plugins/autotest/testoutputreader.h6
-rw-r--r--src/plugins/autotest/testprojectsettings.cpp2
-rw-r--r--src/plugins/autotest/testresultdelegate.h7
-rw-r--r--src/plugins/autotest/testrunner.cpp385
-rw-r--r--src/plugins/autotest/testrunner.h28
-rw-r--r--src/plugins/autotest/testtreemodel.cpp18
-rw-r--r--src/plugins/autotoolsprojectmanager/autotoolsbuildsystem.cpp3
-rw-r--r--src/plugins/autotoolsprojectmanager/makefileparser.cpp4
-rw-r--r--src/plugins/baremetal/baremetaldebugsupport.cpp1
-rw-r--r--src/plugins/baremetal/baremetaldebugsupport.h5
-rw-r--r--src/plugins/baremetal/baremetaldevice.cpp1
-rw-r--r--src/plugins/baremetal/debugservers/gdb/gdbserverprovider.cpp2
-rw-r--r--src/plugins/baremetal/debugservers/gdb/openocdgdbserverprovider.cpp2
-rw-r--r--src/plugins/baremetal/debugservers/uvsc/uvproject.cpp2
-rw-r--r--src/plugins/baremetal/debugservers/uvsc/uvscserverprovider.cpp1
-rw-r--r--src/plugins/bookmarks/bookmarkfilter.cpp4
-rw-r--r--src/plugins/bookmarks/bookmarkmanager.cpp3
-rw-r--r--src/plugins/boot2qt/qdbdeployconfigurationfactory.cpp2
-rw-r--r--src/plugins/boot2qt/qdbdevice.cpp9
-rw-r--r--src/plugins/boot2qt/qdbmakedefaultappstep.cpp52
-rw-r--r--src/plugins/boot2qt/qdbplugin.cpp21
-rw-r--r--src/plugins/boot2qt/qdbstopapplicationstep.cpp51
-rw-r--r--src/plugins/clangcodemodel/CMakeLists.txt1
-rw-r--r--src/plugins/clangcodemodel/clangcodemodel.qbs6
-rw-r--r--src/plugins/clangcodemodel/clangcodemodelplugin.cpp47
-rw-r--r--src/plugins/clangcodemodel/clangcodemodelplugin.h2
-rw-r--r--src/plugins/clangcodemodel/clangdclient.cpp47
-rw-r--r--src/plugins/clangcodemodel/clangdclient.h2
-rw-r--r--src/plugins/clangcodemodel/clangdfindreferences.cpp5
-rw-r--r--src/plugins/clangcodemodel/clangdlocatorfilters.cpp175
-rw-r--r--src/plugins/clangcodemodel/clangdlocatorfilters.h12
-rw-r--r--src/plugins/clangcodemodel/clangdsemantichighlighting.cpp24
-rw-r--r--src/plugins/clangcodemodel/clangdsemantichighlighting.h8
-rw-r--r--src/plugins/clangcodemodel/clangmodelmanagersupport.cpp37
-rw-r--r--src/plugins/clangcodemodel/test/clangbatchfileprocessor.cpp729
-rw-r--r--src/plugins/clangcodemodel/test/clangbatchfileprocessor.h16
-rw-r--r--src/plugins/clangcodemodel/test/clangdtests.cpp22
-rw-r--r--src/plugins/clangformat/clangformat.qbs4
-rw-r--r--src/plugins/clangformat/clangformatbaseindenter.cpp4
-rw-r--r--src/plugins/clangformat/clangformatfile.h3
-rw-r--r--src/plugins/clangformat/clangformatindenter.cpp2
-rw-r--r--src/plugins/clangformat/clangformatutils.cpp8
-rw-r--r--src/plugins/clangtools/clangtool.cpp8
-rw-r--r--src/plugins/clangtools/clangtoolrunner.cpp24
-rw-r--r--src/plugins/clangtools/clangtools.qbs4
-rw-r--r--src/plugins/clangtools/clangtoolsdiagnosticmodel.cpp8
-rw-r--r--src/plugins/clangtools/clangtoolspreconfiguredsessiontests.cpp26
-rw-r--r--src/plugins/clangtools/clangtoolsprojectsettings.cpp2
-rw-r--r--src/plugins/clangtools/clangtoolsutils.cpp2
-rw-r--r--src/plugins/clangtools/diagnosticconfigswidget.cpp4
-rw-r--r--src/plugins/clangtools/documentclangtoolrunner.cpp6
-rw-r--r--src/plugins/clangtools/unit-tests/exported-diagnostics/CMakeLists.txt2
-rw-r--r--src/plugins/classview/classviewmanager.cpp13
-rw-r--r--src/plugins/classview/classviewparsertreeitem.cpp8
-rw-r--r--src/plugins/clearcase/clearcaseplugin.cpp23
-rw-r--r--src/plugins/clearcase/clearcasesync.cpp32
-rw-r--r--src/plugins/clearcase/clearcasesync.h9
-rw-r--r--src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp17
-rw-r--r--src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp2
-rw-r--r--src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp5
-rw-r--r--src/plugins/cmakeprojectmanager/cmakelocatorfilter.cpp61
-rw-r--r--src/plugins/cmakeprojectmanager/cmakelocatorfilter.h5
-rw-r--r--src/plugins/cmakeprojectmanager/cmakeproject.cpp16
-rw-r--r--src/plugins/cmakeprojectmanager/cmakeproject.h2
-rw-r--r--src/plugins/cmakeprojectmanager/cmakeprojectimporter.h3
-rw-r--r--src/plugins/cmakeprojectmanager/cmakeprojectmanager.cpp12
-rw-r--r--src/plugins/cmakeprojectmanager/cmakeprojectnodes.cpp4
-rw-r--r--src/plugins/cmakeprojectmanager/fileapidataextractor.cpp27
-rw-r--r--src/plugins/cmakeprojectmanager/fileapiparser.cpp7
-rw-r--r--src/plugins/cmakeprojectmanager/fileapiparser.h8
-rw-r--r--src/plugins/cmakeprojectmanager/fileapireader.cpp10
-rw-r--r--src/plugins/cmakeprojectmanager/presetsmacros.cpp55
-rw-r--r--src/plugins/cmakeprojectmanager/presetsparser.cpp14
-rw-r--r--src/plugins/compilationdatabaseprojectmanager/compilationdatabaseproject.cpp14
-rw-r--r--src/plugins/compilationdatabaseprojectmanager/compilationdatabaseprojectmanager.qbs4
-rw-r--r--src/plugins/compilationdatabaseprojectmanager/compilationdatabaseprojectmanagerplugin.cpp4
-rw-r--r--src/plugins/compilationdatabaseprojectmanager/compilationdatabasetests.cpp4
-rw-r--r--src/plugins/compilationdatabaseprojectmanager/compilationdatabaseutils.cpp25
-rw-r--r--src/plugins/compilationdatabaseprojectmanager/compilationdatabaseutils.h8
-rw-r--r--src/plugins/compilationdatabaseprojectmanager/compilationdbparser.cpp6
-rw-r--r--src/plugins/conan/conanplugin.cpp8
-rw-r--r--src/plugins/copilot/CMakeLists.txt17
-rw-r--r--src/plugins/copilot/Copilot.json.in19
-rw-r--r--src/plugins/copilot/authwidget.cpp149
-rw-r--r--src/plugins/copilot/authwidget.h46
-rw-r--r--src/plugins/copilot/copilot.qbs33
-rw-r--r--src/plugins/copilot/copilot.qrc6
-rw-r--r--src/plugins/copilot/copilotclient.cpp220
-rw-r--r--src/plugins/copilot/copilotclient.h63
-rw-r--r--src/plugins/copilot/copilothoverhandler.cpp140
-rw-r--r--src/plugins/copilot/copilothoverhandler.h32
-rw-r--r--src/plugins/copilot/copilotoptionspage.cpp110
-rw-r--r--src/plugins/copilot/copilotoptionspage.h25
-rw-r--r--src/plugins/copilot/copilotplugin.cpp64
-rw-r--r--src/plugins/copilot/copilotplugin.h32
-rw-r--r--src/plugins/copilot/copilotsettings.cpp65
-rw-r--r--src/plugins/copilot/copilotsettings.h21
-rw-r--r--src/plugins/copilot/copilotsuggestion.cpp41
-rw-r--r--src/plugins/copilot/copilotsuggestion.h30
-rw-r--r--src/plugins/copilot/copilottr.h15
-rw-r--r--src/plugins/copilot/images/settingscategory_copilot.pngbin0 -> 219 bytes
-rw-r--r--src/plugins/copilot/images/[email protected]bin0 -> 404 bytes
-rw-r--r--src/plugins/copilot/requests/checkstatus.h54
-rw-r--r--src/plugins/copilot/requests/getcompletions.h119
-rw-r--r--src/plugins/copilot/requests/signinconfirm.h36
-rw-r--r--src/plugins/copilot/requests/signininitiate.h43
-rw-r--r--src/plugins/copilot/requests/signout.h26
-rw-r--r--src/plugins/coreplugin/actionmanager/actionmanager.cpp4
-rw-r--r--src/plugins/coreplugin/actionsfilter.cpp6
-rw-r--r--src/plugins/coreplugin/coreplugin.qbs4
-rw-r--r--src/plugins/coreplugin/editormanager/documentmodel.cpp108
-rw-r--r--src/plugins/coreplugin/editormanager/documentmodel_p.h12
-rw-r--r--src/plugins/coreplugin/editormanager/editormanager.cpp12
-rw-r--r--src/plugins/coreplugin/editormanager/editormanager.h14
-rw-r--r--src/plugins/coreplugin/fileutils.cpp7
-rw-r--r--src/plugins/coreplugin/find/searchresultwidget.cpp2
-rw-r--r--src/plugins/coreplugin/locator/basefilefilter.cpp23
-rw-r--r--src/plugins/coreplugin/locator/basefilefilter.h4
-rw-r--r--src/plugins/coreplugin/locator/directoryfilter.cpp111
-rw-r--r--src/plugins/coreplugin/locator/directoryfilter.h5
-rw-r--r--src/plugins/coreplugin/locator/filesystemfilter.cpp75
-rw-r--r--src/plugins/coreplugin/locator/filesystemfilter.h2
-rw-r--r--src/plugins/coreplugin/locator/ilocatorfilter.cpp59
-rw-r--r--src/plugins/coreplugin/locator/ilocatorfilter.h20
-rw-r--r--src/plugins/coreplugin/locator/locator.cpp16
-rw-r--r--src/plugins/coreplugin/locator/locator_test.cpp2
-rw-r--r--src/plugins/coreplugin/locator/locatorsearchutils.cpp4
-rw-r--r--src/plugins/coreplugin/locator/opendocumentsfilter.cpp18
-rw-r--r--src/plugins/coreplugin/locator/opendocumentsfilter.h7
-rw-r--r--src/plugins/coreplugin/manhattanstyle.cpp6
-rw-r--r--src/plugins/coreplugin/plugindialog.cpp1
-rw-r--r--src/plugins/coreplugin/plugininstallwizard.cpp21
-rw-r--r--src/plugins/coreplugin/progressmanager/progressmanager.cpp27
-rw-r--r--src/plugins/coreplugin/progressmanager/progressmanager.h2
-rw-r--r--src/plugins/coreplugin/progressmanager/progressview.cpp42
-rw-r--r--src/plugins/coreplugin/progressmanager/progressview.h1
-rw-r--r--src/plugins/coreplugin/systemsettings.cpp6
-rw-r--r--src/plugins/coreplugin/welcomepagehelper.cpp61
-rw-r--r--src/plugins/coreplugin/welcomepagehelper.h9
-rw-r--r--src/plugins/cppcheck/cppcheckplugin.cpp11
-rw-r--r--src/plugins/cppcheck/cppchecktrigger.cpp4
-rw-r--r--src/plugins/cppeditor/baseeditordocumentparser.cpp9
-rw-r--r--src/plugins/cppeditor/baseeditordocumentparser.h11
-rw-r--r--src/plugins/cppeditor/baseeditordocumentprocessor.cpp19
-rw-r--r--src/plugins/cppeditor/baseeditordocumentprocessor.h7
-rw-r--r--src/plugins/cppeditor/builtincursorinfo.cpp6
-rw-r--r--src/plugins/cppeditor/builtineditordocumentparser.cpp6
-rw-r--r--src/plugins/cppeditor/builtineditordocumentparser.h3
-rw-r--r--src/plugins/cppeditor/builtineditordocumentprocessor.cpp10
-rw-r--r--src/plugins/cppeditor/cppcodeformatter.cpp2
-rw-r--r--src/plugins/cppeditor/cppcodemodelinspectordumper.cpp8
-rw-r--r--src/plugins/cppeditor/cppcodemodelsettings.cpp1
-rw-r--r--src/plugins/cppeditor/cppcompletion_test.cpp16
-rw-r--r--src/plugins/cppeditor/cppcurrentdocumentfilter.cpp80
-rw-r--r--src/plugins/cppeditor/cppcurrentdocumentfilter.h3
-rw-r--r--src/plugins/cppeditor/cppeditor.qbs4
-rw-r--r--src/plugins/cppeditor/cppeditorplugin.cpp4
-rw-r--r--src/plugins/cppeditor/cppeditorwidget.cpp6
-rw-r--r--src/plugins/cppeditor/cppelementevaluator.cpp43
-rw-r--r--src/plugins/cppeditor/cppelementevaluator.h10
-rw-r--r--src/plugins/cppeditor/cppfindreferences.cpp78
-rw-r--r--src/plugins/cppeditor/cppfunctiondecldeflink.cpp4
-rw-r--r--src/plugins/cppeditor/cpphighlighter.cpp1
-rw-r--r--src/plugins/cppeditor/cppincludesfilter.cpp34
-rw-r--r--src/plugins/cppeditor/cppincludesfilter.h3
-rw-r--r--src/plugins/cppeditor/cppindexingsupport.cpp48
-rw-r--r--src/plugins/cppeditor/cppindexingsupport.h2
-rw-r--r--src/plugins/cppeditor/cpplocatorfilter.cpp45
-rw-r--r--src/plugins/cppeditor/cpplocatorfilter.h4
-rw-r--r--src/plugins/cppeditor/cpplocatorfilter_test.cpp5
-rw-r--r--src/plugins/cppeditor/cppmodelmanager.cpp263
-rw-r--r--src/plugins/cppeditor/cppmodelmanager_test.cpp2
-rw-r--r--src/plugins/cppeditor/cppoutlinemodel.cpp19
-rw-r--r--src/plugins/cppeditor/cppprojectinfogenerator.cpp16
-rw-r--r--src/plugins/cppeditor/cppprojectinfogenerator.h11
-rw-r--r--src/plugins/cppeditor/cppprojectupdater.cpp8
-rw-r--r--src/plugins/cppeditor/cppquickfixes.cpp9
-rw-r--r--src/plugins/cppeditor/cppquickfixsettingspage.h4
-rw-r--r--src/plugins/cppeditor/cppsemanticinfoupdater.cpp17
-rw-r--r--src/plugins/cppeditor/cpptoolsjsextension.cpp5
-rw-r--r--src/plugins/cppeditor/cpptoolsreuse.cpp9
-rw-r--r--src/plugins/cppeditor/cpptoolstestcase.cpp8
-rw-r--r--src/plugins/cppeditor/cpptypehierarchy.cpp3
-rw-r--r--src/plugins/cppeditor/generatedcodemodelsupport.cpp2
-rw-r--r--src/plugins/cppeditor/generatedcodemodelsupport.h2
-rw-r--r--src/plugins/cppeditor/indexitem.cpp4
-rw-r--r--src/plugins/cppeditor/indexitem.h5
-rw-r--r--src/plugins/cppeditor/insertionpointlocator.h26
-rw-r--r--src/plugins/cppeditor/modelmanagertesthelper.cpp6
-rw-r--r--src/plugins/cppeditor/projectinfo_test.cpp6
-rw-r--r--src/plugins/cppeditor/searchsymbols.cpp3
-rw-r--r--src/plugins/cppeditor/symbolsearcher_test.cpp6
-rw-r--r--src/plugins/cppeditor/symbolsfindfilter.cpp13
-rw-r--r--src/plugins/cppeditor/testcases/highlightingtestcase.cpp2
-rw-r--r--src/plugins/cppeditor/typehierarchybuilder.cpp28
-rw-r--r--src/plugins/cppeditor/typehierarchybuilder.h12
-rw-r--r--src/plugins/debugger/breakhandler.cpp4
-rw-r--r--src/plugins/debugger/breakhandler.h2
-rw-r--r--src/plugins/debugger/breakpoint.cpp7
-rw-r--r--src/plugins/debugger/breakpoint.h2
-rw-r--r--src/plugins/debugger/cdb/cdbengine.cpp8
-rw-r--r--src/plugins/debugger/cdb/cdbengine.h4
-rw-r--r--src/plugins/debugger/commonoptionspage.cpp3
-rw-r--r--src/plugins/debugger/debugger.qbs4
-rw-r--r--src/plugins/debugger/debuggeractions.cpp10
-rw-r--r--src/plugins/debugger/debuggeractions.h1
-rw-r--r--src/plugins/debugger/debuggerdialogs.cpp1
-rw-r--r--src/plugins/debugger/debuggerengine.cpp33
-rw-r--r--src/plugins/debugger/debuggerengine.h14
-rw-r--r--src/plugins/debugger/debuggeritem.cpp17
-rw-r--r--src/plugins/debugger/debuggeritem.h5
-rw-r--r--src/plugins/debugger/debuggeritemmanager.cpp118
-rw-r--r--src/plugins/debugger/debuggerkitinformation.cpp94
-rw-r--r--src/plugins/debugger/debuggerkitinformation.h1
-rw-r--r--src/plugins/debugger/debuggerplugin.cpp17
-rw-r--r--src/plugins/debugger/debuggerruncontrol.cpp10
-rw-r--r--src/plugins/debugger/gdb/gdbengine.cpp171
-rw-r--r--src/plugins/debugger/gdb/gdbengine.h20
-rw-r--r--src/plugins/debugger/lldb/lldbengine.cpp18
-rw-r--r--src/plugins/debugger/lldb/lldbengine.h4
-rw-r--r--src/plugins/debugger/loadcoredialog.cpp8
-rw-r--r--src/plugins/debugger/moduleshandler.cpp30
-rw-r--r--src/plugins/debugger/moduleshandler.h6
-rw-r--r--src/plugins/debugger/pdb/pdbengine.cpp10
-rw-r--r--src/plugins/debugger/pdb/pdbengine.h4
-rw-r--r--src/plugins/debugger/qml/qmlengine.cpp20
-rw-r--r--src/plugins/debugger/qml/qmlengine.h6
-rw-r--r--src/plugins/debugger/qml/qmlengineutils.cpp8
-rw-r--r--src/plugins/debugger/qml/qmlengineutils.h4
-rw-r--r--src/plugins/debugger/qml/qmlinspectoragent.cpp7
-rw-r--r--src/plugins/debugger/sourcefileshandler.cpp14
-rw-r--r--src/plugins/debugger/sourcefileshandler.h6
-rw-r--r--src/plugins/debugger/terminal.cpp3
-rw-r--r--src/plugins/debugger/uvsc/uvscengine.cpp2
-rw-r--r--src/plugins/debugger/watchdata.cpp14
-rw-r--r--src/plugins/debugger/watchdata.h2
-rw-r--r--src/plugins/debugger/watchhandler.cpp60
-rw-r--r--src/plugins/debugger/watchhandler.h1
-rw-r--r--src/plugins/designer/codemodelhelpers.cpp4
-rw-r--r--src/plugins/designer/designer.qbs4
-rw-r--r--src/plugins/designer/qtcreatorintegration.cpp16
-rw-r--r--src/plugins/designer/resourcehandler.cpp13
-rw-r--r--src/plugins/diffeditor/diffeditordocument.cpp6
-rw-r--r--src/plugins/diffeditor/diffeditorplugin.cpp29
-rw-r--r--src/plugins/diffeditor/diffutils.cpp108
-rw-r--r--src/plugins/diffeditor/diffutils.h8
-rw-r--r--src/plugins/diffeditor/sidebysidediffeditorwidget.cpp61
-rw-r--r--src/plugins/diffeditor/sidebysidediffeditorwidget.h26
-rw-r--r--src/plugins/diffeditor/unifieddiffeditorwidget.cpp71
-rw-r--r--src/plugins/diffeditor/unifieddiffeditorwidget.h30
-rw-r--r--src/plugins/docker/dockerapi.cpp4
-rw-r--r--src/plugins/docker/dockerdevice.cpp201
-rw-r--r--src/plugins/docker/dockerdevice.h4
-rw-r--r--src/plugins/docker/dockersettings.cpp2
-rw-r--r--src/plugins/fakevim/fakevim.qbs4
-rw-r--r--src/plugins/genericprojectmanager/genericproject.cpp8
-rw-r--r--src/plugins/git/branchmodel.cpp104
-rw-r--r--src/plugins/git/branchmodel.h3
-rw-r--r--src/plugins/git/branchview.cpp70
-rw-r--r--src/plugins/git/branchview.h3
-rw-r--r--src/plugins/git/gerrit/gerritpushdialog.cpp2
-rw-r--r--src/plugins/git/gitclient.cpp46
-rw-r--r--src/plugins/git/gitclient.h6
-rw-r--r--src/plugins/git/gitgrep.cpp15
-rw-r--r--src/plugins/git/gitsubmiteditor.cpp4
-rw-r--r--src/plugins/gitlab/gitlabdialog.cpp4
-rw-r--r--src/plugins/gitlab/gitlabplugin.cpp18
-rw-r--r--src/plugins/haskell/CMakeLists.txt1
-rw-r--r--src/plugins/haskell/haskell.qrc3
-rw-r--r--src/plugins/haskell/images/category_haskell.pngbin6264 -> 0 bytes
-rw-r--r--src/plugins/haskell/images/settingscategory_haskell.pngbin0 -> 187 bytes
-rw-r--r--src/plugins/haskell/images/[email protected]bin0 -> 322 bytes
-rw-r--r--src/plugins/haskell/optionspage.cpp2
-rw-r--r--src/plugins/help/CMakeLists.txt3
-rw-r--r--src/plugins/help/help.qbs1
-rw-r--r--src/plugins/help/helpindexfilter.cpp63
-rw-r--r--src/plugins/help/helpindexfilter.h14
-rw-r--r--src/plugins/help/helpmanager.cpp18
-rw-r--r--src/plugins/help/helpmanager.h13
-rw-r--r--src/plugins/help/helpmode.cpp24
-rw-r--r--src/plugins/help/helpmode.h21
-rw-r--r--src/plugins/help/helpplugin.cpp52
-rw-r--r--src/plugins/imageviewer/CMakeLists.txt2
-rw-r--r--src/plugins/imageviewer/imageview.cpp2
-rw-r--r--src/plugins/ios/iosbuildstep.cpp6
-rw-r--r--src/plugins/ios/iosdsymbuildstep.cpp3
-rw-r--r--src/plugins/ios/iostoolhandler.cpp23
-rw-r--r--src/plugins/ios/simulatorcontrol.cpp113
-rw-r--r--src/plugins/ios/simulatorcontrol.h6
-rw-r--r--src/plugins/languageclient/CMakeLists.txt1
-rw-r--r--src/plugins/languageclient/callhierarchy.cpp48
-rw-r--r--src/plugins/languageclient/callhierarchy.h6
-rw-r--r--src/plugins/languageclient/client.cpp40
-rw-r--r--src/plugins/languageclient/client.h6
-rw-r--r--src/plugins/languageclient/clientrequesttask.cpp39
-rw-r--r--src/plugins/languageclient/clientrequesttask.h78
-rw-r--r--src/plugins/languageclient/languageclient.qbs2
-rw-r--r--src/plugins/languageclient/languageclient_global.h2
-rw-r--r--src/plugins/languageclient/languageclientmanager.cpp25
-rw-r--r--src/plugins/languageclient/languageclientmanager.h4
-rw-r--r--src/plugins/languageclient/languageclientoutline.cpp110
-rw-r--r--src/plugins/languageclient/languageclientoutline.h38
-rw-r--r--src/plugins/languageclient/languageclientsettings.cpp2
-rw-r--r--src/plugins/languageclient/languageclientsymbolsupport.cpp4
-rw-r--r--src/plugins/languageclient/locatorfilter.cpp121
-rw-r--r--src/plugins/languageclient/locatorfilter.h21
-rw-r--r--src/plugins/mcusupport/mcusupport.qbs3
-rw-r--r--src/plugins/mcusupport/mcusupportdevice.h1
-rw-r--r--src/plugins/mcusupport/mcusupportplugin.cpp6
-rw-r--r--src/plugins/mercurial/mercurialclient.cpp2
-rw-r--r--src/plugins/mesonprojectmanager/mesonbuildconfiguration.cpp3
-rw-r--r--src/plugins/mesonprojectmanager/mesonbuildconfiguration.h4
-rw-r--r--src/plugins/mesonprojectmanager/mesonprojectparser.cpp3
-rw-r--r--src/plugins/modeleditor/componentviewcontroller.cpp6
-rw-r--r--src/plugins/modeleditor/elementtasks.cpp26
-rw-r--r--src/plugins/modeleditor/modelindexer.cpp6
-rw-r--r--src/plugins/nim/editor/nimcompletionassistprovider.cpp2
-rw-r--r--src/plugins/nim/images/settingscategory_nim.pngbin245 -> 245 bytes
-rw-r--r--src/plugins/nim/images/[email protected]bin511 -> 511 bytes
-rw-r--r--src/plugins/nim/project/nimcompilerbuildstep.cpp2
-rw-r--r--src/plugins/nim/project/nimcompilercleanstep.cpp2
-rw-r--r--src/plugins/perfprofiler/perfdatareader.cpp2
-rw-r--r--src/plugins/perfprofiler/perfloaddialog.cpp4
-rw-r--r--src/plugins/perfprofiler/perfprofiler.qbs4
-rw-r--r--src/plugins/perfprofiler/perfprofilertool.cpp8
-rw-r--r--src/plugins/perfprofiler/perfsettings.cpp2
-rw-r--r--src/plugins/perfprofiler/perftracepointdialog.cpp4
-rw-r--r--src/plugins/plugins.qbs5
-rw-r--r--src/plugins/projectexplorer/CMakeLists.txt8
-rw-r--r--src/plugins/projectexplorer/allprojectsfilter.cpp23
-rw-r--r--src/plugins/projectexplorer/allprojectsfilter.h5
-rw-r--r--src/plugins/projectexplorer/allprojectsfind.cpp6
-rw-r--r--src/plugins/projectexplorer/buildconfiguration.cpp4
-rw-r--r--src/plugins/projectexplorer/buildmanager.cpp12
-rw-r--r--src/plugins/projectexplorer/buildsettingspropertiespage.cpp8
-rw-r--r--src/plugins/projectexplorer/buildstep.cpp23
-rw-r--r--src/plugins/projectexplorer/buildstep.h40
-rw-r--r--src/plugins/projectexplorer/buildstepspage.cpp8
-rw-r--r--src/plugins/projectexplorer/buildsystem.cpp4
-rw-r--r--src/plugins/projectexplorer/copystep.cpp114
-rw-r--r--src/plugins/projectexplorer/copystep.h22
-rw-r--r--src/plugins/projectexplorer/currentprojectfilter.cpp24
-rw-r--r--src/plugins/projectexplorer/currentprojectfilter.h5
-rw-r--r--src/plugins/projectexplorer/currentprojectfind.cpp13
-rw-r--r--src/plugins/projectexplorer/dependenciespanel.cpp22
-rw-r--r--src/plugins/projectexplorer/devicesupport/desktopdevice.cpp82
-rw-r--r--src/plugins/projectexplorer/devicesupport/desktopdevice.h8
-rw-r--r--src/plugins/projectexplorer/devicesupport/devicemanager.cpp24
-rw-r--r--src/plugins/projectexplorer/devicesupport/devicesettingswidget.cpp61
-rw-r--r--src/plugins/projectexplorer/devicesupport/devicesettingswidget.h1
-rw-r--r--src/plugins/projectexplorer/devicesupport/idevice.cpp8
-rw-r--r--src/plugins/projectexplorer/devicesupport/idevice.h2
-rw-r--r--src/plugins/projectexplorer/devicesupport/idevicefactory.cpp26
-rw-r--r--src/plugins/projectexplorer/devicesupport/idevicefactory.h3
-rw-r--r--src/plugins/projectexplorer/devicesupport/localprocesslist.cpp59
-rw-r--r--src/plugins/projectexplorer/devicesupport/processlist.cpp61
-rw-r--r--src/plugins/projectexplorer/devicesupport/processlist.h (renamed from src/plugins/projectexplorer/devicesupport/localprocesslist.h)10
-rw-r--r--src/plugins/projectexplorer/devicesupport/sshdeviceprocesslist.cpp83
-rw-r--r--src/plugins/projectexplorer/devicesupport/sshdeviceprocesslist.h36
-rw-r--r--src/plugins/projectexplorer/devicesupport/sshparameters.cpp29
-rw-r--r--src/plugins/projectexplorer/devicesupport/sshparameters.h30
-rw-r--r--src/plugins/projectexplorer/editorconfiguration.cpp8
-rw-r--r--src/plugins/projectexplorer/extracompiler.cpp66
-rw-r--r--src/plugins/projectexplorer/extracompiler.h4
-rw-r--r--src/plugins/projectexplorer/fileinsessionfinder.cpp12
-rw-r--r--src/plugins/projectexplorer/filesinallprojectsfind.cpp4
-rw-r--r--src/plugins/projectexplorer/gcctoolchain.cpp8
-rw-r--r--src/plugins/projectexplorer/gcctoolchain.h4
-rw-r--r--src/plugins/projectexplorer/jsonwizard/jsonfieldpage.cpp1
-rw-r--r--src/plugins/projectexplorer/jsonwizard/jsonsummarypage.cpp4
-rw-r--r--src/plugins/projectexplorer/kitchooser.cpp4
-rw-r--r--src/plugins/projectexplorer/makestep.cpp16
-rw-r--r--src/plugins/projectexplorer/makestep.h5
-rw-r--r--src/plugins/projectexplorer/miniprojecttargetselector.cpp56
-rw-r--r--src/plugins/projectexplorer/msvctoolchain.cpp26
-rw-r--r--src/plugins/projectexplorer/msvctoolchain.h11
-rw-r--r--src/plugins/projectexplorer/project.cpp158
-rw-r--r--src/plugins/projectexplorer/project.h6
-rw-r--r--src/plugins/projectexplorer/projectexplorer.cpp224
-rw-r--r--src/plugins/projectexplorer/projectexplorer.h3
-rw-r--r--src/plugins/projectexplorer/projectexplorer.qbs12
-rw-r--r--src/plugins/projectexplorer/projectexplorer.qrc8
-rw-r--r--src/plugins/projectexplorer/projectfilewizardextension.cpp5
-rw-r--r--src/plugins/projectexplorer/projectmanager.cpp931
-rw-r--r--src/plugins/projectexplorer/projectmanager.h74
-rw-r--r--src/plugins/projectexplorer/projectmodels.cpp20
-rw-r--r--src/plugins/projectexplorer/projectnodes.h2
-rw-r--r--src/plugins/projectexplorer/projectnodeshelper.h26
-rw-r--r--src/plugins/projectexplorer/projecttree.cpp22
-rw-r--r--src/plugins/projectexplorer/projecttreewidget.cpp4
-rw-r--r--src/plugins/projectexplorer/projectwelcomepage.cpp5
-rw-r--r--src/plugins/projectexplorer/projectwindow.cpp22
-rw-r--r--src/plugins/projectexplorer/projectwizardpage.cpp4
-rw-r--r--src/plugins/projectexplorer/rawprojectpart.cpp6
-rw-r--r--src/plugins/projectexplorer/rawprojectpart.h2
-rw-r--r--src/plugins/projectexplorer/runconfiguration.cpp5
-rw-r--r--src/plugins/projectexplorer/runconfigurationaspects.cpp13
-rw-r--r--src/plugins/projectexplorer/runconfigurationaspects.h1
-rw-r--r--src/plugins/projectexplorer/runcontrol.cpp8
-rw-r--r--src/plugins/projectexplorer/runsettingspropertiespage.cpp9
-rw-r--r--src/plugins/projectexplorer/selectablefilesmodel.cpp14
-rw-r--r--src/plugins/projectexplorer/selectablefilesmodel.h6
-rw-r--r--src/plugins/projectexplorer/session.cpp1045
-rw-r--r--src/plugins/projectexplorer/session.h74
-rw-r--r--src/plugins/projectexplorer/session_p.h38
-rw-r--r--src/plugins/projectexplorer/sessionmodel.cpp7
-rw-r--r--src/plugins/projectexplorer/target.cpp79
-rw-r--r--src/plugins/projectexplorer/target.h7
-rw-r--r--src/plugins/projectexplorer/targetsettingspanel.cpp14
-rw-r--r--src/plugins/projectexplorer/targetsetuppage.cpp2
-rw-r--r--src/plugins/projectexplorer/task.cpp28
-rw-r--r--src/plugins/projectexplorer/task.h1
-rw-r--r--src/plugins/projectexplorer/taskhub.cpp9
-rw-r--r--src/plugins/projectexplorer/taskwindow.cpp257
-rw-r--r--src/plugins/projectexplorer/testdata/multi-target-project/CMakeLists.txt3
-rw-r--r--src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-app.pro4
-rw-r--r--src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-lib.cpp6
-rw-r--r--src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-lib.pro4
-rw-r--r--src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-main.cpp6
-rw-r--r--src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-shared.h3
-rw-r--r--src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project.pro4
-rw-r--r--src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project.qbs10
-rw-r--r--src/plugins/projectexplorer/toolchain.cpp19
-rw-r--r--src/plugins/projectexplorer/toolchain.h10
-rw-r--r--src/plugins/projectexplorer/treescanner.cpp16
-rw-r--r--src/plugins/projectexplorer/treescanner.h4
-rw-r--r--src/plugins/python/CMakeLists.txt1
-rw-r--r--src/plugins/python/Python.json.in8
-rw-r--r--src/plugins/python/pipsupport.cpp6
-rw-r--r--src/plugins/python/pyside.cpp14
-rw-r--r--src/plugins/python/pyside.h1
-rw-r--r--src/plugins/python/pysidebuildconfiguration.cpp2
-rw-r--r--src/plugins/python/python.qbs2
-rw-r--r--src/plugins/python/pythoneditor.cpp43
-rw-r--r--src/plugins/python/pythonlanguageclient.cpp6
-rw-r--r--src/plugins/python/pythonplugin.cpp6
-rw-r--r--src/plugins/python/pythonproject.h3
-rw-r--r--src/plugins/python/pythonsettings.cpp92
-rw-r--r--src/plugins/python/pythonsettings.h13
-rw-r--r--src/plugins/python/pythonutils.cpp31
-rw-r--r--src/plugins/python/pythonutils.h4
-rw-r--r--src/plugins/python/pythonwizardpage.cpp219
-rw-r--r--src/plugins/python/pythonwizardpage.h43
-rw-r--r--src/plugins/qbsprojectmanager/qbsinstallstep.cpp10
-rw-r--r--src/plugins/qbsprojectmanager/qbsinstallstep.h2
-rw-r--r--src/plugins/qbsprojectmanager/qbsnodetreebuilder.cpp4
-rw-r--r--src/plugins/qbsprojectmanager/qbsproject.cpp6
-rw-r--r--src/plugins/qbsprojectmanager/qbsprojectmanagerplugin.cpp18
-rw-r--r--src/plugins/qbsprojectmanager/qbsprojectparser.cpp8
-rw-r--r--src/plugins/qmakeprojectmanager/customwidgetwizard/plugingenerator.cpp18
-rw-r--r--src/plugins/qmakeprojectmanager/librarydetailscontroller.cpp11
-rw-r--r--src/plugins/qmakeprojectmanager/makefileparse.cpp11
-rw-r--r--src/plugins/qmakeprojectmanager/profileeditor.cpp10
-rw-r--r--src/plugins/qmakeprojectmanager/qmakenodes.h1
-rw-r--r--src/plugins/qmakeprojectmanager/qmakenodetreebuilder.cpp23
-rw-r--r--src/plugins/qmakeprojectmanager/qmakeparser.cpp7
-rw-r--r--src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp11
-rw-r--r--src/plugins/qmakeprojectmanager/qmakeparsernodes.h2
-rw-r--r--src/plugins/qmakeprojectmanager/qmakeproject.cpp54
-rw-r--r--src/plugins/qmakeprojectmanager/qmakeproject.h1
-rw-r--r--src/plugins/qmakeprojectmanager/qmakeprojectmanagerplugin.cpp12
-rw-r--r--src/plugins/qmakeprojectmanager/qmakestep.cpp2
-rw-r--r--src/plugins/qmakeprojectmanager/wizards/qtprojectparameters.cpp6
-rw-r--r--src/plugins/qmldesigner/CMakeLists.txt3
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp4
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp24
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.cpp12
-rw-r--r--src/plugins/qmldesigner/assetexporterplugin/filepathmodel.cpp19
-rw-r--r--src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.cpp2
-rw-r--r--src/plugins/qmldesigner/components/componentcore/changestyleaction.cpp4
-rw-r--r--src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp4
-rw-r--r--src/plugins/qmldesigner/components/eventlist/eventlist.cpp4
-rw-r--r--src/plugins/qmldesigner/components/integration/designdocument.cpp8
-rw-r--r--src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp6
-rw-r--r--src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp4
-rw-r--r--src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp4
-rw-r--r--src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp2
-rw-r--r--src/plugins/qmldesigner/components/navigator/navigatorview.cpp6
-rw-r--r--src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.cpp4
-rw-r--r--src/plugins/qmldesigner/designercore/instances/puppetdialog.cpp42
-rw-r--r--src/plugins/qmldesigner/designercore/instances/puppetdialog.h35
-rw-r--r--src/plugins/qmldesigner/designercore/instances/puppetdialog.ui96
-rw-r--r--src/plugins/qmldesigner/documentmanager.cpp13
-rw-r--r--src/plugins/qmldesigner/generateresource.cpp22
-rw-r--r--src/plugins/qmldesigner/qmldesignerexternaldependencies.cpp8
-rw-r--r--src/plugins/qmldesigner/qmldesignerplugin.cpp6
-rw-r--r--src/plugins/qmldesigner/qmldesignerprojectmanager.cpp16
-rw-r--r--src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp8
-rw-r--r--src/plugins/qmljseditor/qmljseditingsettingspage.cpp4
-rw-r--r--src/plugins/qmljseditor/qmljsfindreferences.cpp70
-rw-r--r--src/plugins/qmljseditor/qmljsquickfix.h2
-rw-r--r--src/plugins/qmljseditor/qmljssemantichighlighter.cpp26
-rw-r--r--src/plugins/qmljseditor/qmljssemantichighlighter.h2
-rw-r--r--src/plugins/qmljseditor/qmltaskmanager.cpp12
-rw-r--r--src/plugins/qmljseditor/qmltaskmanager.h2
-rw-r--r--src/plugins/qmljstools/qmljsfunctionfilter.cpp31
-rw-r--r--src/plugins/qmljstools/qmljsfunctionfilter.h3
-rw-r--r--src/plugins/qmljstools/qmljslocatordata.cpp6
-rw-r--r--src/plugins/qmljstools/qmljsmodelmanager.cpp9
-rw-r--r--src/plugins/qmljstools/qmljsrefactoringchanges.h2
-rw-r--r--src/plugins/qmljstools/qmljstools.qbs4
-rw-r--r--src/plugins/qmlpreview/qmlpreview.qbs4
-rw-r--r--src/plugins/qmlpreview/qmlpreviewplugin.cpp10
-rw-r--r--src/plugins/qmlpreview/qmlpreviewruncontrol.cpp2
-rw-r--r--src/plugins/qmlprofiler/qmlprofiler.qbs4
-rw-r--r--src/plugins/qmlprofiler/qmlprofilerdetailsrewriter.cpp2
-rw-r--r--src/plugins/qmlprofiler/qmlprofilertool.cpp4
-rw-r--r--src/plugins/qmlprofiler/tests/qmlprofilerclientmanager_test.cpp40
-rw-r--r--src/plugins/qmlprofiler/tests/qmlprofilerdetailsrewriter_test.cpp26
-rw-r--r--src/plugins/qmlprojectmanager/cmakegen/cmakeprojectconverter.cpp13
-rw-r--r--src/plugins/qmlprojectmanager/cmakegen/generatecmakelists.cpp21
-rw-r--r--src/plugins/qmlprojectmanager/projectfilecontenttools.cpp2
-rw-r--r--src/plugins/qmlprojectmanager/projectfilecontenttools.h2
-rw-r--r--src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp4
-rw-r--r--src/plugins/qmlprojectmanager/qmlproject.cpp4
-rw-r--r--src/plugins/qmlprojectmanager/qmlprojectplugin.cpp6
-rw-r--r--src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp4
-rw-r--r--src/plugins/qnx/CMakeLists.txt3
-rw-r--r--src/plugins/qnx/qnx.qbs6
-rw-r--r--src/plugins/qnx/qnxconfiguration.cpp10
-rw-r--r--src/plugins/qnx/qnxdebugsupport.cpp4
-rw-r--r--src/plugins/qnx/qnxdevice.cpp218
-rw-r--r--src/plugins/qnx/qnxdevice.h34
-rw-r--r--src/plugins/qnx/qnxdeviceprocesslist.cpp59
-rw-r--r--src/plugins/qnx/qnxdeviceprocesslist.h21
-rw-r--r--src/plugins/qnx/qnxdeviceprocesssignaloperation.cpp36
-rw-r--r--src/plugins/qnx/qnxdeviceprocesssignaloperation.h22
-rw-r--r--src/plugins/qnx/qnxdevicetester.cpp76
-rw-r--r--src/plugins/qnx/qnxdevicetester.h17
-rw-r--r--src/plugins/qnx/qnxdevicewizard.cpp48
-rw-r--r--src/plugins/qnx/qnxdevicewizard.h38
-rw-r--r--src/plugins/qnx/qnxplugin.cpp2
-rw-r--r--src/plugins/qnx/slog2inforunner.cpp9
-rw-r--r--src/plugins/qtsupport/CMakeLists.txt2
-rw-r--r--src/plugins/qtsupport/baseqtversion.cpp7
-rw-r--r--src/plugins/qtsupport/exampleslistmodel.cpp286
-rw-r--r--src/plugins/qtsupport/exampleslistmodel.h25
-rw-r--r--src/plugins/qtsupport/examplesparser.cpp301
-rw-r--r--src/plugins/qtsupport/examplesparser.h49
-rw-r--r--src/plugins/qtsupport/externaleditors.cpp4
-rw-r--r--src/plugins/qtsupport/gettingstartedwelcomepage.cpp76
-rw-r--r--src/plugins/qtsupport/gettingstartedwelcomepage.h9
-rw-r--r--src/plugins/qtsupport/qtsupport.qbs2
-rw-r--r--src/plugins/qtsupport/qtsupportplugin.cpp6
-rw-r--r--src/plugins/remotelinux/CMakeLists.txt2
-rw-r--r--src/plugins/remotelinux/abstractremotelinuxdeploystep.cpp182
-rw-r--r--src/plugins/remotelinux/abstractremotelinuxdeploystep.h90
-rw-r--r--src/plugins/remotelinux/customcommanddeploystep.cpp72
-rw-r--r--src/plugins/remotelinux/filesystemaccess_test.cpp376
-rw-r--r--src/plugins/remotelinux/filesystemaccess_test.h15
-rw-r--r--src/plugins/remotelinux/genericdirectuploadservice.cpp314
-rw-r--r--src/plugins/remotelinux/genericdirectuploadservice.h38
-rw-r--r--src/plugins/remotelinux/genericdirectuploadstep.cpp331
-rw-r--r--src/plugins/remotelinux/genericdirectuploadstep.h19
-rw-r--r--src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.cpp61
-rw-r--r--src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.h13
-rw-r--r--src/plugins/remotelinux/genericlinuxdeviceconfigurationwizard.cpp8
-rw-r--r--src/plugins/remotelinux/genericlinuxdeviceconfigurationwizardpages.cpp34
-rw-r--r--src/plugins/remotelinux/genericlinuxdeviceconfigurationwizardpages.h3
-rw-r--r--src/plugins/remotelinux/killappstep.cpp46
-rw-r--r--src/plugins/remotelinux/linuxdevice.cpp493
-rw-r--r--src/plugins/remotelinux/linuxdevice.h8
-rw-r--r--src/plugins/remotelinux/linuxprocessinterface.h33
-rw-r--r--src/plugins/remotelinux/makeinstallstep.cpp29
-rw-r--r--src/plugins/remotelinux/makeinstallstep.h7
-rw-r--r--src/plugins/remotelinux/remotelinux.qbs7
-rw-r--r--src/plugins/remotelinux/remotelinux_constants.h4
-rw-r--r--src/plugins/remotelinux/remotelinuxrunconfiguration.cpp17
-rw-r--r--src/plugins/remotelinux/rsyncdeploystep.cpp129
-rw-r--r--src/plugins/remotelinux/rsyncdeploystep.h21
-rw-r--r--src/plugins/remotelinux/sshprocessinterface.h46
-rw-r--r--src/plugins/remotelinux/tarpackagecreationstep.cpp26
-rw-r--r--src/plugins/remotelinux/tarpackagedeploystep.cpp98
-rw-r--r--src/plugins/scxmleditor/scxmleditordocument.cpp2
-rw-r--r--src/plugins/silversearcher/findinfilessilversearcher.cpp14
-rw-r--r--src/plugins/silversearcher/silversearcher.qbs4
-rw-r--r--src/plugins/squish/squishfilehandler.cpp5
-rw-r--r--src/plugins/studiowelcome/stylemodel.cpp4
-rw-r--r--src/plugins/studiowelcome/wizardhandler.cpp2
-rw-r--r--src/plugins/subversion/subversionclient.cpp2
-rw-r--r--src/plugins/terminal/CMakeLists.txt23
-rw-r--r--src/plugins/terminal/Terminal.json.in19
-rw-r--r--src/plugins/terminal/celliterator.cpp99
-rw-r--r--src/plugins/terminal/celliterator.h97
-rw-r--r--src/plugins/terminal/glyphcache.cpp48
-rw-r--r--src/plugins/terminal/glyphcache.h34
-rw-r--r--src/plugins/terminal/images/settingscategory_terminal.pngbin0 -> 164 bytes
-rw-r--r--src/plugins/terminal/images/[email protected]bin0 -> 193 bytes
-rw-r--r--src/plugins/terminal/images/terminal.pngbin0 -> 154 bytes
-rw-r--r--src/plugins/terminal/images/[email protected]bin0 -> 180 bytes
-rw-r--r--src/plugins/terminal/keys.cpp83
-rw-r--r--src/plugins/terminal/keys.h15
-rw-r--r--src/plugins/terminal/scrollback.cpp61
-rw-r--r--src/plugins/terminal/scrollback.h57
-rw-r--r--src/plugins/terminal/shellintegration.cpp143
-rw-r--r--src/plugins/terminal/shellintegration.h34
-rwxr-xr-xsrc/plugins/terminal/shellintegrations/shellintegration-bash.sh252
-rw-r--r--src/plugins/terminal/shellintegrations/shellintegration-env.zsh15
-rw-r--r--src/plugins/terminal/shellintegrations/shellintegration-login.zsh7
-rw-r--r--src/plugins/terminal/shellintegrations/shellintegration-profile.zsh15
-rw-r--r--src/plugins/terminal/shellintegrations/shellintegration-rc.zsh160
-rw-r--r--src/plugins/terminal/shellintegrations/shellintegration.fish122
-rw-r--r--src/plugins/terminal/shellintegrations/shellintegration.ps1158
-rw-r--r--src/plugins/terminal/shellmodel.cpp110
-rw-r--r--src/plugins/terminal/shellmodel.h37
-rw-r--r--src/plugins/terminal/terminal.qbs46
-rw-r--r--src/plugins/terminal/terminal.qrc15
-rw-r--r--src/plugins/terminal/terminalcommands.cpp182
-rw-r--r--src/plugins/terminal/terminalcommands.h75
-rw-r--r--src/plugins/terminal/terminalpane.cpp379
-rw-r--r--src/plugins/terminal/terminalpane.h61
-rw-r--r--src/plugins/terminal/terminalplugin.cpp84
-rw-r--r--src/plugins/terminal/terminalplugin.h30
-rw-r--r--src/plugins/terminal/terminalprocessimpl.cpp68
-rw-r--r--src/plugins/terminal/terminalprocessimpl.h18
-rw-r--r--src/plugins/terminal/terminalsearch.cpp283
-rw-r--r--src/plugins/terminal/terminalsearch.h82
-rw-r--r--src/plugins/terminal/terminalsettings.cpp177
-rw-r--r--src/plugins/terminal/terminalsettings.h36
-rw-r--r--src/plugins/terminal/terminalsettingspage.cpp456
-rw-r--r--src/plugins/terminal/terminalsettingspage.h22
-rw-r--r--src/plugins/terminal/terminalsurface.cpp531
-rw-r--r--src/plugins/terminal/terminalsurface.h111
-rw-r--r--src/plugins/terminal/terminaltr.h15
-rw-r--r--src/plugins/terminal/terminalwidget.cpp1446
-rw-r--r--src/plugins/terminal/terminalwidget.h226
-rwxr-xr-xsrc/plugins/terminal/tests/colors112
-rwxr-xr-xsrc/plugins/terminal/tests/cursor/bar3
-rwxr-xr-xsrc/plugins/terminal/tests/cursor/blinkbar3
-rwxr-xr-xsrc/plugins/terminal/tests/cursor/blinkblock3
-rwxr-xr-xsrc/plugins/terminal/tests/cursor/blinkunderline3
-rwxr-xr-xsrc/plugins/terminal/tests/cursor/block3
-rwxr-xr-xsrc/plugins/terminal/tests/cursor/underline3
-rwxr-xr-xsrc/plugins/terminal/tests/decoration9
-rwxr-xr-xsrc/plugins/terminal/tests/filenames22
-rwxr-xr-xsrc/plugins/terminal/tests/integration70
-rw-r--r--src/plugins/texteditor/CMakeLists.txt1
-rw-r--r--src/plugins/texteditor/basehoverhandler.h3
-rw-r--r--src/plugins/texteditor/codeassist/asyncprocessor.cpp4
-rw-r--r--src/plugins/texteditor/codeassist/codeassistant.cpp9
-rw-r--r--src/plugins/texteditor/formattexteditor.cpp4
-rw-r--r--src/plugins/texteditor/markdowneditor.cpp103
-rw-r--r--src/plugins/texteditor/markdowneditor.h16
-rw-r--r--src/plugins/texteditor/quickfix.h6
-rw-r--r--src/plugins/texteditor/textdocument.cpp16
-rw-r--r--src/plugins/texteditor/textdocument.h4
-rw-r--r--src/plugins/texteditor/textdocumentlayout.cpp119
-rw-r--r--src/plugins/texteditor/textdocumentlayout.h31
-rw-r--r--src/plugins/texteditor/texteditor.cpp195
-rw-r--r--src/plugins/texteditor/texteditor.h20
-rw-r--r--src/plugins/texteditor/texteditor.qbs6
-rw-r--r--src/plugins/texteditor/texteditoractionhandler.cpp5
-rw-r--r--src/plugins/texteditor/texteditoractionhandler.h3
-rw-r--r--src/plugins/texteditor/texteditorconstants.h1
-rw-r--r--src/plugins/texteditor/texteditorplugin.cpp2
-rw-r--r--src/plugins/texteditor/textmark.cpp11
-rw-r--r--src/plugins/todo/todoitemsprovider.cpp7
-rw-r--r--src/plugins/valgrind/callgrindengine.cpp7
-rw-r--r--src/plugins/valgrind/callgrindtool.cpp4
-rw-r--r--src/plugins/valgrind/memcheckerrorview.cpp2
-rw-r--r--src/plugins/valgrind/memchecktool.cpp12
-rw-r--r--src/plugins/valgrind/suppressiondialog.cpp8
-rw-r--r--src/plugins/valgrind/valgrind.qbs4
-rw-r--r--src/plugins/vcpkg/CMakeLists.txt16
-rw-r--r--src/plugins/vcpkg/Vcpkg.json.in30
-rw-r--r--src/plugins/vcpkg/vcpkg.qbs32
-rw-r--r--src/plugins/vcpkg/vcpkg.qrc6
-rw-r--r--src/plugins/vcpkg/vcpkg_test.cpp110
-rw-r--r--src/plugins/vcpkg/vcpkg_test.h24
-rw-r--r--src/plugins/vcpkg/vcpkgconstants.h14
-rw-r--r--src/plugins/vcpkg/vcpkgmanifesteditor.cpp71
-rw-r--r--src/plugins/vcpkg/vcpkgmanifesteditor.h16
-rw-r--r--src/plugins/vcpkg/vcpkgplugin.cpp38
-rw-r--r--src/plugins/vcpkg/vcpkgplugin.h26
-rw-r--r--src/plugins/vcpkg/vcpkgsearch.cpp214
-rw-r--r--src/plugins/vcpkg/vcpkgsearch.h29
-rw-r--r--src/plugins/vcpkg/vcpkgsettings.cpp80
-rw-r--r--src/plugins/vcpkg/vcpkgsettings.h30
-rw-r--r--src/plugins/vcpkg/vcpkgtr.h15
-rw-r--r--src/plugins/vcpkg/wizards/manifest/vcpkg.json.tpl6
-rw-r--r--src/plugins/vcpkg/wizards/manifest/wizard.json80
-rw-r--r--src/plugins/vcsbase/cleandialog.cpp29
-rw-r--r--src/plugins/vcsbase/commonvcssettings.cpp6
-rw-r--r--src/plugins/vcsbase/vcsbaseclient.cpp11
-rw-r--r--src/plugins/vcsbase/vcsbaseclient.h8
-rw-r--r--src/plugins/vcsbase/vcsbasediffeditorcontroller.cpp13
-rw-r--r--src/plugins/vcsbase/vcsbaseeditor.cpp4
-rw-r--r--src/plugins/vcsbase/vcsbaseplugin.cpp14
-rw-r--r--src/plugins/vcsbase/vcsbasesubmiteditor.cpp4
-rw-r--r--src/plugins/webassembly/webassembly.qbs4
-rw-r--r--src/plugins/webassembly/webassemblydevice.h1
-rw-r--r--src/shared/qtsingleapplication/qtlocalpeer.h2
-rw-r--r--src/shared/qtsingleapplication/qtsingleapplication.h2
-rw-r--r--src/shared/registryaccess/CMakeLists.txt6
-rw-r--r--src/tools/CMakeLists.txt15
-rw-r--r--src/tools/cplusplus-shared/CPlusPlusTool.qbs1
-rw-r--r--src/tools/cplusplustools.qbs1
-rw-r--r--src/tools/icons/qtcreatoricons.svg119
m---------src/tools/perfparser0
-rw-r--r--src/tools/process_stub/CMakeLists.txt12
-rw-r--r--src/tools/process_stub/main.cpp551
-rw-r--r--src/tools/process_stub/process_stub.qbs10
-rw-r--r--src/tools/processlauncher/launchersockethandler.cpp5
-rw-r--r--src/tools/qml2puppet/CMakeLists.txt18
-rw-r--r--src/tools/qml2puppet/qml2puppet/appmetadata.cpp51
-rw-r--r--src/tools/qml2puppet/qml2puppet/appmetadata.h44
-rw-r--r--src/tools/qtcdebugger/main.cpp2
-rw-r--r--src/tools/sdktool/CMakeLists.txt30
-rw-r--r--src/tools/sdktool/addcmakeoperation.cpp5
-rw-r--r--src/tools/sdktool/adddebuggeroperation.cpp3
-rw-r--r--src/tools/sdktool/addkitoperation.cpp4
-rw-r--r--src/tools/sdktool/addqtoperation.cpp9
-rw-r--r--src/tools/sdktool/addtoolchainoperation.cpp5
-rw-r--r--src/tools/sdktool/main.cpp11
-rw-r--r--src/tools/sdktool/operation.cpp25
-rw-r--r--src/tools/sdktool/operation.h2
-rw-r--r--src/tools/sdktool/sdkpersistentsettings.cpp871
-rw-r--r--src/tools/sdktool/sdkpersistentsettings.h37
-rw-r--r--src/tools/sdktool/sdktoollib.qbs31
-rw-r--r--src/tools/sdktool/settings.cpp26
-rw-r--r--src/tools/sdktool/settings.h6
-rw-r--r--src/tools/tools.qbs1
-rw-r--r--tests/auto/CMakeLists.txt1
-rw-r--r--tests/auto/auto.qbs1
-rw-r--r--tests/auto/cplusplus/cxx11/tst_cxx11.cpp257
-rw-r--r--tests/auto/cplusplus/lexer/tst_lexer.cpp33
-rw-r--r--tests/auto/debugger/CMakeLists.txt2
-rw-r--r--tests/auto/debugger/tst_dumpers.cpp10
-rw-r--r--tests/auto/environment/tst_environment.cpp91
-rw-r--r--tests/auto/examples/CMakeLists.txt5
-rw-r--r--tests/auto/examples/examples.qbs9
-rw-r--r--tests/auto/examples/tst_examples.cpp162
-rw-r--r--tests/auto/extensionsystem/pluginmanager/tst_pluginmanager.cpp6
-rw-r--r--tests/auto/filesearch/tst_filesearch.cpp8
-rw-r--r--tests/auto/qml/codemodel/check/tst_check.cpp3
-rw-r--r--tests/auto/qml/codemodel/dependencies/tst_dependencies.cpp3
-rw-r--r--tests/auto/qml/codemodel/ecmascript7/tst_ecmascript7.cpp3
-rw-r--r--tests/auto/qml/codemodel/importscheck/tst_importscheck.cpp6
-rw-r--r--tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp4
-rw-r--r--tests/auto/tracing/timelineabstractrenderer/tst_timelineabstractrenderer.cpp10
-rw-r--r--tests/auto/tracing/timelinemodel/tst_timelinemodel.cpp36
-rw-r--r--tests/auto/tracing/timelinemodelaggregator/tst_timelinemodelaggregator.cpp30
-rw-r--r--tests/auto/tracing/timelinenotesmodel/tst_timelinenotesmodel.cpp4
-rw-r--r--tests/auto/tracing/timelinezoomcontrol/tst_timelinezoomcontrol.cpp29
-rw-r--r--tests/auto/utils/CMakeLists.txt2
-rw-r--r--tests/auto/utils/asynctask/tst_asynctask.cpp226
-rw-r--r--tests/auto/utils/commandline/tst_commandline.cpp77
-rw-r--r--tests/auto/utils/deviceshell/tst_deviceshell.cpp21
-rw-r--r--tests/auto/utils/filepath/CMakeLists.txt4
-rw-r--r--tests/auto/utils/filepath/filepath.qbs11
-rw-r--r--tests/auto/utils/filepath/tst_filepath.cpp1662
-rw-r--r--tests/auto/utils/fileutils/tst_fileutils.cpp1293
-rw-r--r--tests/auto/utils/fsengine/tst_fsengine.cpp53
-rw-r--r--tests/auto/utils/mathutils/tst_mathutils.cpp2
-rw-r--r--tests/auto/utils/qtcprocess/tst_qtcprocess.cpp31
-rw-r--r--tests/auto/utils/stringutils/tst_stringutils.cpp34
-rw-r--r--tests/auto/utils/tasktree/tst_tasktree.cpp127
-rw-r--r--tests/auto/utils/unixdevicefileaccess/CMakeLists.txt4
-rw-r--r--tests/auto/utils/unixdevicefileaccess/tst_unixdevicefileaccess.cpp78
-rw-r--r--tests/auto/utils/unixdevicefileaccess/unixdevicefileaccess.qbs11
-rw-r--r--tests/auto/utils/utils.qbs2
-rw-r--r--tests/manual/deviceshell/tst_deviceshell.cpp34
-rw-r--r--tests/manual/tasktree/main.cpp6
-rw-r--r--tests/unit/CMakeLists.txt2
-rw-r--r--tests/unit/unittest/CMakeLists.txt11
1214 files changed, 60838 insertions, 13353 deletions
diff --git a/.clang-format b/.clang-format
index 1b9871712f1..0a0df0c152d 100644
--- a/.clang-format
+++ b/.clang-format
@@ -23,19 +23,19 @@ AlignEscapedNewlines: DontAlign
AlignOperands: true
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
-AllowShortBlocksOnASingleLine: false
+AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Inline
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
-AlwaysBreakTemplateDeclarations: true
+AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: false
BinPackParameters: false
BraceWrapping:
AfterClass: true
- AfterControlStatement: false
+ AfterControlStatement: Never
AfterEnum: false
AfterFunction: true
AfterNamespace: false
@@ -99,7 +99,7 @@ PenaltyExcessCharacter: 50
PenaltyReturnTypeOnItsOwnLine: 300
PointerAlignment: Right
ReflowComments: false
-SortIncludes: true
+SortIncludes: CaseSensitive
SortUsingDeclarations: true
SpaceAfterCStyleCast: true
SpaceAfterTemplateKeyword: false
@@ -112,6 +112,6 @@ SpacesInContainerLiterals: false
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
-Standard: Cpp11
+Standard: c++11
TabWidth: 4
UseTab: Never
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d70dab026ce..d52a1c8b65b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -65,11 +65,24 @@ if(MSVC)
add_compile_options(/wd4573)
endif()
-find_package(Qt5
+find_package(Qt6
${IDE_QT_VERSION_MIN}
COMPONENTS Concurrent Core Gui Network PrintSupport Qml Sql Widgets Xml Core5Compat ${QT_TEST_COMPONENT}
REQUIRED
)
+# hack for Qbs which still supports Qt5 and Qt6
+if (TARGET Qt6::Core5CompatPrivate)
+ if (CMAKE_VERSION VERSION_LESS 3.18)
+ set_property(TARGET Qt6::Core5CompatPrivate PROPERTY IMPORTED_GLOBAL TRUE)
+ endif()
+ add_library(Qt6Core5CompatPrivate ALIAS Qt6::Core5CompatPrivate)
+endif()
+if (TARGET Qt6::Core5Compat)
+ if (CMAKE_VERSION VERSION_LESS 3.18)
+ set_property(TARGET Qt6::Core5Compat PROPERTY IMPORTED_GLOBAL TRUE)
+ endif()
+ add_library(Qt6Core5Compat ALIAS Qt6::Core5Compat)
+endif()
# Common intermediate directory for QML modules which are defined via qt_add_qml_module()
set(QT_QML_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/qml_modules")
@@ -82,8 +95,8 @@ if (MSVC AND QT_FEATURE_static_runtime)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
endif()
-find_package(Qt5 COMPONENTS LinguistTools QUIET)
-find_package(Qt5 COMPONENTS Quick QuickWidgets Designer DesignerComponents Help SerialPort Svg Tools QUIET)
+find_package(Qt6 OPTIONAL_COMPONENTS Quick QuickWidgets Designer DesignerComponentsPrivate
+ Help SerialPort Svg Tools LinguistTools QUIET)
find_package(Threads)
find_package(Clang QUIET)
diff --git a/README.md b/README.md
index 590361d1ef6..4a97db2efcc 100644
--- a/README.md
+++ b/README.md
@@ -747,3 +747,123 @@ SQLite (https://www.sqlite.org) is in the Public Domain.
public domain worldwide. This software is distributed without any warranty.
http://creativecommons.org/publicdomain/zero/1.0/
+
+### WinPty
+
+ Implementation of a pseudo terminal for Windows.
+
+ https://github.com/rprichard/winpty
+
+ The MIT License (MIT)
+
+ Copyright (c) 2011-2016 Ryan Prichard
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to
+ deal in the Software without restriction, including without limitation the
+ rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ sell copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ IN THE SOFTWARE.
+
+
+### ptyqt
+
+ Pty-Qt is small library for access to console applications by pseudo-terminal interface on Mac,
+ Linux and Windows. On Mac and Linux it uses standard PseudoTerminal API and on Windows it uses
+ WinPty(prefer) or ConPty.
+
+ https://github.com/kafeg/ptyqt
+
+ MIT License
+
+ Copyright (c) 2019 Vitaly Petrov, [email protected]
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+
+### libvterm
+
+ An abstract C99 library which implements a VT220 or xterm-like terminal emulator.
+ It doesn't use any particular graphics toolkit or output system, instead it invokes callback
+ function pointers that its embedding program should provide it to draw on its behalf.
+ It avoids calling malloc() during normal running state, allowing it to be used in embedded kernel
+ situations.
+
+ https://www.leonerd.org.uk/code/libvterm/
+
+ The MIT License
+
+ Copyright (c) 2008 Paul Evans <[email protected]>
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+
+### terminal/shellintegrations
+
+ The Terminal plugin uses scripts to integrate with the shell. The scripts are
+ located in the Qt Creator source tree in src/plugins/terminal/shellintegrations.
+
+ https://github.com/microsoft/vscode/tree/main/src/vs/workbench/contrib/terminal/browser/media
+
+ MIT License
+
+ Copyright (c) 2015 - present Microsoft Corporation
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
diff --git a/cmake/FindQt5.cmake b/cmake/FindQt5.cmake
deleted file mode 100644
index fc16ec17237..00000000000
--- a/cmake/FindQt5.cmake
+++ /dev/null
@@ -1,103 +0,0 @@
-#.rst:
-# FindQt5
-# -------
-#
-# Qt5 wrapper around Qt6 CMake code.
-#
-
-unset(__arguments)
-if (Qt5_FIND_QUIETLY)
- list(APPEND __arguments QUIET)
-endif()
-if (Qt5_FIND_REQUIRED)
- list(APPEND __arguments REQUIRED)
-endif()
-
-if (Qt5_FIND_COMPONENTS)
- # for some reason QUIET doesn't really work when passed to the arguments list
- if (Qt5_FIND_QUIETLY)
- list(APPEND __arguments OPTIONAL_COMPONENTS)
- else()
- list(APPEND __arguments COMPONENTS)
- endif()
-endif()
-
-find_package(Qt6 ${Qt5_FIND_VERSION} CONFIG COMPONENTS Core QUIET)
-if (NOT Qt6_FOUND)
- # remove Core5Compat from components to find in Qt5, but add a dummy target,
- # which unfortunately cannot start with "Qt6::"
- # also remove Tools, where some tools have moved in Qt6, e.g. from Help
- list(REMOVE_ITEM Qt5_FIND_COMPONENTS Core5Compat)
- list(REMOVE_ITEM Qt5_FIND_COMPONENTS Tools)
- find_package(Qt5 ${Qt5_FIND_VERSION} CONFIG ${__arguments} ${Qt5_FIND_COMPONENTS})
- if (NOT TARGET Qt6Core5Compat)
- add_library(Qt6Core5Compat INTERFACE)
- endif()
-
- # Remove Qt6 from the not found packages in Qt5 mode
- get_property(not_found_packages GLOBAL PROPERTY "PACKAGES_NOT_FOUND")
- if(not_found_packages)
- list(REMOVE_ITEM not_found_packages Qt6)
- set_property(GLOBAL PROPERTY "PACKAGES_NOT_FOUND" "${not_found_packages}")
- endif()
- return()
-else()
- # since Qt 6.2 some components are renamed to *Private
- foreach(possible_private_libs DesignerComponents QmlDebug)
- list(FIND Qt5_FIND_COMPONENTS ${possible_private_libs} dcIndex)
- if(dcIndex GREATER_EQUAL 0)
- find_package(Qt6${possible_private_libs}Private CONFIG QUIET)
- if(TARGET Qt6::${possible_private_libs}Private)
- set_property(TARGET Qt6::${possible_private_libs}Private PROPERTY IMPORTED_GLOBAL TRUE)
- add_library(Qt5::${possible_private_libs} ALIAS Qt6::${possible_private_libs}Private)
- list(REMOVE_AT Qt5_FIND_COMPONENTS ${dcIndex})
- endif()
- endif()
- endforeach()
- find_package(Qt6 CONFIG ${__arguments} ${Qt5_FIND_COMPONENTS})
-endif()
-
-set(__additional_imported_components ATSPI2_nolink) # Work around QTBUG-97023
-foreach(comp IN LISTS Qt5_FIND_COMPONENTS __additional_imported_components)
- if(TARGET Qt6::${comp})
- if (NOT TARGET Qt5::${comp})
- if (NOT QT_FEATURE_static)
- set_property(TARGET Qt6::${comp} PROPERTY IMPORTED_GLOBAL TRUE)
- endif()
- add_library(Qt5::${comp} ALIAS Qt6::${comp})
- endif()
- if (TARGET Qt6::${comp}Private AND NOT TARGET Qt5::${comp}Private)
- if (NOT QT_FEATURE_static)
- set_property(TARGET Qt6::${comp}Private PROPERTY IMPORTED_GLOBAL TRUE)
- endif()
- add_library(Qt5::${comp}Private ALIAS Qt6::${comp}Private)
- endif()
- endif()
-endforeach()
-
-# alias Qt6::Core5Compat to Qt6Core5Compat to make consistent with Qt5 path
-if (TARGET Qt6::Core5Compat AND NOT TARGET Qt6Core5Compat)
- add_library(Qt6Core5Compat ALIAS Qt6::Core5Compat)
-endif()
-
-set(Qt5_FOUND ${Qt6_FOUND})
-set(Qt5_VERSION ${Qt6_VERSION})
-
-foreach(tool qmake lrelease lupdate moc rcc qhelpgenerator)
- if (TARGET Qt6::${tool} AND NOT TARGET Qt5::${tool})
- add_executable(Qt5::${tool} IMPORTED GLOBAL)
- get_target_property(imported_location Qt6::${tool} IMPORTED_LOCATION)
- # handle separate tools for each configuration
- if (NOT imported_location)
- get_target_property(imported_location Qt6::${tool} IMPORTED_LOCATION_RELEASE)
- endif()
- set_target_properties(Qt5::${tool} PROPERTIES IMPORTED_LOCATION "${imported_location}")
- endif()
-endforeach()
-
-if (NOT DEFINED qt5_wrap_cpp)
- function(qt5_wrap_cpp outfiles)
- qt6_wrap_cpp(${outfiles} ${ARGN})
- set(${outfiles} ${${outfiles}} PARENT_SCOPE)
- endfunction()
-endif()
diff --git a/cmake/QtCreatorAPI.cmake b/cmake/QtCreatorAPI.cmake
index 70b8114fb9f..7b8a5c47f72 100644
--- a/cmake/QtCreatorAPI.cmake
+++ b/cmake/QtCreatorAPI.cmake
@@ -628,6 +628,7 @@ function(add_qtc_executable name)
update_cached_list(__QTC_EXECUTABLES "${name}")
+ condition_info(_extra_text _arg_CONDITION)
if (NOT _arg_CONDITION)
set(_arg_CONDITION ON)
endif()
@@ -648,6 +649,9 @@ function(add_qtc_executable name)
else()
set(_executable_enabled OFF)
endif()
+ if (NOT _arg_INTERNAL_ONLY)
+ add_feature_info("Executable ${name}" _executable_enabled "${_extra_text}")
+ endif()
if (NOT _executable_enabled)
return()
endif()
diff --git a/cmake/QtCreatorAPIInternal.cmake b/cmake/QtCreatorAPIInternal.cmake
index efe7efd9ce7..479a6fb179b 100644
--- a/cmake/QtCreatorAPIInternal.cmake
+++ b/cmake/QtCreatorAPIInternal.cmake
@@ -224,7 +224,7 @@ function(set_explicit_moc target_name file)
set(file_dependencies DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${target_name}.json")
endif()
set_property(SOURCE "${file}" PROPERTY SKIP_AUTOMOC ON)
- qt5_wrap_cpp(file_moc "${file}" ${file_dependencies})
+ qt_wrap_cpp(file_moc "${file}" ${file_dependencies})
target_sources(${target_name} PRIVATE "${file_moc}")
endfunction()
@@ -413,6 +413,7 @@ function(enable_pch target)
CXX_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN ON
CXX_EXTENSIONS OFF
+ POSITION_INDEPENDENT_CODE ON
)
target_link_libraries(${pch_target} PRIVATE ${pch_dependency})
endif()
diff --git a/cmake/QtCreatorDocumentation.cmake b/cmake/QtCreatorDocumentation.cmake
index bc18f798289..8fa0c58d93e 100644
--- a/cmake/QtCreatorDocumentation.cmake
+++ b/cmake/QtCreatorDocumentation.cmake
@@ -10,7 +10,7 @@ add_feature_info("Build online documentation" WITH_ONLINE_DOCS "")
# Used for QT_INSTALL_DOCS
function(qt5_query_qmake)
if (NOT TARGET Qt::qmake)
- message(FATAL_ERROR "Qmake was not found. Add find_package(Qt5 COMPONENTS Core) to CMake to enable.")
+ message(FATAL_ERROR "Qmake was not found. Add find_package(Qt6 COMPONENTS Core) to CMake to enable.")
endif()
# dummy check for if we already queried qmake
if (QT_INSTALL_BINS)
@@ -142,7 +142,7 @@ function(_setup_qhelpgenerator_targets _qdocconf_file _html_outputdir)
endif()
if (NOT TARGET Qt::qhelpgenerator)
- message(WARNING "qhelpgenerator missing: No QCH documentation targets were generated. Add find_package(Qt5 COMPONENTS Help) to CMake to enable.")
+ message(WARNING "qhelpgenerator missing: No QCH documentation targets were generated. Add find_package(Qt6 COMPONENTS Help) to CMake to enable.")
return()
endif()
diff --git a/cmake/QtCreatorIDEBranding.cmake b/cmake/QtCreatorIDEBranding.cmake
index fd065a43571..0d9e4b81860 100644
--- a/cmake/QtCreatorIDEBranding.cmake
+++ b/cmake/QtCreatorIDEBranding.cmake
@@ -1,6 +1,6 @@
-set(IDE_VERSION "10.0.0") # The IDE version.
-set(IDE_VERSION_COMPAT "10.0.0") # The IDE Compatibility version.
-set(IDE_VERSION_DISPLAY "10.0.0") # The IDE display version.
+set(IDE_VERSION "10.0.82") # The IDE version.
+set(IDE_VERSION_COMPAT "10.0.82") # The IDE Compatibility version.
+set(IDE_VERSION_DISPLAY "11.0.0-beta1") # The IDE display version.
set(IDE_COPYRIGHT_YEAR "2023") # The IDE current copyright year.
set(IDE_SETTINGSVARIANT "QtProject") # The IDE settings variation.
diff --git a/cmake/QtCreatorTranslations.cmake b/cmake/QtCreatorTranslations.cmake
index 51f33260232..98ede46582e 100644
--- a/cmake/QtCreatorTranslations.cmake
+++ b/cmake/QtCreatorTranslations.cmake
@@ -30,7 +30,7 @@ function(_extract_ts_data_from_targets outprefix)
set(_target_sources "")
if(_source_files)
- list(FILTER _source_files EXCLUDE REGEX ".*[.]json[.]in|.*[.]svg")
+ list(FILTER _source_files EXCLUDE REGEX ".*[.]json[.]in|.*[.]svg|.*[.]pro|.*[.]css")
list(APPEND _target_sources ${_source_files})
endif()
if(_extra_translations)
@@ -130,7 +130,7 @@ endfunction()
function(add_translation_targets file_prefix)
if (NOT TARGET Qt::lrelease OR NOT TARGET Qt::lupdate)
# No Qt translation tools were found: Skip this directory
- message(WARNING "No Qt translation tools found, skipping translation targets. Add find_package(Qt5 COMPONENTS LinguistTools) to CMake to enable.")
+ message(WARNING "No Qt translation tools found, skipping translation targets. Add find_package(Qt6 COMPONENTS LinguistTools) to CMake to enable.")
return()
endif()
diff --git a/doc/qtcreator/src/overview/creator-acknowledgements.qdoc b/doc/qtcreator/src/overview/creator-acknowledgements.qdoc
index 2ef5125bab2..820e97247d5 100644
--- a/doc/qtcreator/src/overview/creator-acknowledgements.qdoc
+++ b/doc/qtcreator/src/overview/creator-acknowledgements.qdoc
@@ -1002,5 +1002,62 @@
https://creativecommons.org/publicdomain/zero/1.0/
+ \li \b WinPty
+
+ Implementation of a pseudo terminal for Windows.
+
+ The sources can be found in:
+ \list
+ \li \l https://github.com/rprichard/winpty
+ \endlist
+
+ Distributed under the MIT license.
+
+ \include license-mit.qdocinc
+
+ \li \b ptyqt
+
+ Pty-Qt is small library for access to console applications by a
+ pseudo-terminal interface on \macos, Linux and Windows. On \macos and
+ Linux it uses standard PseudoTerminal API and on Windows it uses
+ WinPty or ConPty.
+
+ \list
+ \li \l https://github.com/kafeg/ptyqt
+ \endlist
+
+ Distributed under the MIT license.
+
+ \include license-mit.qdocinc
+
+ \li \b libvterm
+
+ An abstract C99 library, which implements a VT220 or xterm-like terminal
+ emulator. It doesn't use any particular graphics toolkit or output
+ system. Instead it invokes callback function pointers that its embedding
+ program should provide to draw on its behalf. It avoids calling malloc()
+ during normal running state, allowing it to be used in embedded kernels.
+
+ \list
+ \li \l https://www.leonerd.org.uk/code/libvterm/
+ \endlist
+
+ Distributed under the MIT license.
+
+ \include license-mit.qdocinc
+
+ \li \b terminal/shellintegrations
+
+ The Terminal plugin uses scripts to integrate with the shell. The scripts are
+ located in the Qt Creator source tree in src/plugins/terminal/shellintegrations.
+
+ \list
+ \li \l https://github.com/microsoft/vscode/tree/main/src/vs/workbench/contrib/terminal/browser/media
+ \endlist
+
+ Distributed under the MIT license.
+
+ \include license-mit.qdocinc
+
\endlist
*/
diff --git a/qbs/imports/QtcTestFiles.qbs b/qbs/imports/QtcTestFiles.qbs
new file mode 100644
index 00000000000..ab27a8df8a1
--- /dev/null
+++ b/qbs/imports/QtcTestFiles.qbs
@@ -0,0 +1,6 @@
+import qbs 1.0
+
+Group {
+ name: "Unit tests"
+ condition: qtc.testsEnabled
+}
diff --git a/qbs/modules/qtc/qtc.qbs b/qbs/modules/qtc/qtc.qbs
index dbd8d8a2b2f..1e028f62fca 100644
--- a/qbs/modules/qtc/qtc.qbs
+++ b/qbs/modules/qtc/qtc.qbs
@@ -6,16 +6,16 @@ import qbs.Utilities
Module {
Depends { name: "cpp"; required: false }
- property string qtcreator_display_version: '10.0.0'
+ property string qtcreator_display_version: '11.0.0-beta1'
property string ide_version_major: '10'
property string ide_version_minor: '0'
- property string ide_version_release: '0'
+ property string ide_version_release: '82'
property string qtcreator_version: ide_version_major + '.' + ide_version_minor + '.'
+ ide_version_release
property string ide_compat_version_major: '10'
property string ide_compat_version_minor: '0'
- property string ide_compat_version_release: '0'
+ property string ide_compat_version_release: '82'
property string qtcreator_compat_version: ide_compat_version_major + '.'
+ ide_compat_version_minor + '.' + ide_compat_version_release
diff --git a/share/qtcreator/CMakeLists.txt b/share/qtcreator/CMakeLists.txt
index 0a868915a3d..49b556b5c52 100644
--- a/share/qtcreator/CMakeLists.txt
+++ b/share/qtcreator/CMakeLists.txt
@@ -37,10 +37,6 @@ set(resource_files
debugger/utils.py
)
-if (APPLE)
- set(resource_directories ${resource_directories} scripts)
-endif()
-
# copy resource directories during build
qtc_copy_to_builddir(copy_share_to_builddir
DIRECTORIES ${resource_directories}
diff --git a/share/qtcreator/debugger/creatortypes.py b/share/qtcreator/debugger/creatortypes.py
index af790544601..a11377b0db5 100644
--- a/share/qtcreator/debugger/creatortypes.py
+++ b/share/qtcreator/debugger/creatortypes.py
@@ -235,7 +235,7 @@ def qdump__Utils__Port(d, value):
-def qdump__Utils__Environment(d, value):
+def x_qdump__Utils__Environment(d, value):
qdump__Utils__NameValueDictionary(d, value)
@@ -243,7 +243,7 @@ def qdump__Utils__DictKey(d, value):
d.putStringValue(value["name"])
-def qdump__Utils__NameValueDictionary(d, value):
+def x_qdump__Utils__NameValueDictionary(d, value):
dptr = d.extractPointer(value)
if d.qtVersion() >= 0x60000:
if dptr == 0:
diff --git a/share/qtcreator/debugger/dumper.py b/share/qtcreator/debugger/dumper.py
index 84008943031..136961db3e7 100644
--- a/share/qtcreator/debugger/dumper.py
+++ b/share/qtcreator/debugger/dumper.py
@@ -106,7 +106,7 @@ class Children():
self.d.putNumChild(0)
if self.d.currentMaxNumChild is not None:
if self.d.currentMaxNumChild < self.d.currentNumChild:
- self.d.put('{name="<incomplete>",value="",type="",numchild="0"},')
+ self.d.put('{name="<load more>",value="",type="",numchild="1"},')
self.d.currentChildType = self.savedChildType
self.d.currentChildNumChild = self.savedChildNumChild
self.d.currentNumChild = self.savedNumChild
@@ -215,7 +215,7 @@ class DumperBase():
def setVariableFetchingOptions(self, args):
self.resultVarName = args.get('resultvarname', '')
- self.expandedINames = set(args.get('expanded', []))
+ self.expandedINames = args.get('expanded', {})
self.stringCutOff = int(args.get('stringcutoff', 10000))
self.displayStringLimit = int(args.get('displaystringlimit', 100))
self.typeformats = args.get('typeformats', {})
@@ -298,6 +298,11 @@ class DumperBase():
return range(0, self.currentNumChild)
return range(min(self.currentMaxNumChild, self.currentNumChild))
+ def maxArrayCount(self):
+ if self.currentIName in self.expandedINames:
+ return self.expandedINames[self.currentIName]
+ return 100
+
def enterSubItem(self, item):
if self.useTimeStamps:
item.startTime = time.time()
@@ -872,7 +877,7 @@ class DumperBase():
self.output.append(stuff)
def takeOutput(self):
- res = '\n'.join(self.output)
+ res = ''.join(self.output)
self.output = []
return res
@@ -2233,7 +2238,7 @@ class DumperBase():
res = self.currentValue
return res # The 'short' display.
- def putArrayData(self, base, n, innerType, childNumChild=None, maxNumChild=10000):
+ def putArrayData(self, base, n, innerType, childNumChild=None):
self.checkIntType(base)
self.checkIntType(n)
addrBase = base
@@ -2241,6 +2246,7 @@ class DumperBase():
self.putNumChild(n)
#DumperBase.warn('ADDRESS: 0x%x INNERSIZE: %s INNERTYPE: %s' % (addrBase, innerSize, innerType))
enc = innerType.simpleEncoding()
+ maxNumChild = self.maxArrayCount()
if enc:
self.put('childtype="%s",' % innerType.name)
self.put('addrbase="0x%x",' % addrBase)
@@ -2248,7 +2254,7 @@ class DumperBase():
self.put('arrayencoding="%s",' % enc)
self.put('endian="%s",' % self.packCode)
if n > maxNumChild:
- self.put('childrenelided="%s",' % n) # FIXME: Act on that in frontend
+ self.put('childrenelided="%s",' % n)
n = maxNumChild
self.put('arraydata="')
self.put(self.readMemory(addrBase, n * innerSize))
@@ -2282,7 +2288,7 @@ class DumperBase():
def putPlotData(self, base, n, innerType, maxNumChild=1000 * 1000):
self.putPlotDataHelper(base, n, innerType, maxNumChild=maxNumChild)
if self.isExpanded():
- self.putArrayData(base, n, innerType, maxNumChild=maxNumChild)
+ self.putArrayData(base, n, innerType)
def putSpecialArgv(self, value):
"""
diff --git a/share/qtcreator/debugger/lldbbridge.py b/share/qtcreator/debugger/lldbbridge.py
index c14bf30286f..bef97482af6 100644
--- a/share/qtcreator/debugger/lldbbridge.py
+++ b/share/qtcreator/debugger/lldbbridge.py
@@ -870,6 +870,7 @@ class Dumper(DumperBase):
self.startMode_ = args.get('startmode', 1)
self.breakOnMain_ = args.get('breakonmain', 0)
self.useTerminal_ = args.get('useterminal', 0)
+ self.firstStop_ = True
pargs = self.hexdecode(args.get('processargs', ''))
self.processArgs_ = pargs.split('\0') if len(pargs) else []
self.environment_ = args.get('environment', [])
@@ -930,6 +931,8 @@ class Dumper(DumperBase):
if self.startMode_ == DebuggerStartMode.AttachExternal:
attach_info = lldb.SBAttachInfo(self.attachPid_)
+ if self.breakOnMain_:
+ self.createBreakpointAtMain()
self.process = self.target.Attach(attach_info, error)
if not error.Success():
self.reportState('enginerunfailed')
@@ -1474,6 +1477,12 @@ class Dumper(DumperBase):
self.reportState("inferiorstopok")
else:
self.reportState("stopped")
+ if self.firstStop_:
+ self.firstStop_ = False
+ if self.useTerminal_:
+ # When using a terminal, the process will be interrupted on startup.
+ # We therefore need to continue it here.
+ self.process.Continue()
else:
self.reportState(self.stateName(state))
@@ -2120,7 +2129,7 @@ class SummaryDumper(Dumper, LogMixin):
# Expand variable if we need synthetic children
oldExpanded = self.expandedINames
- self.expandedINames = [value.name] if expanded else []
+ self.expandedINames = {value.name: 100} if expanded else {}
savedOutput = self.output
self.output = []
diff --git a/share/qtcreator/debugger/pdbbridge.py b/share/qtcreator/debugger/pdbbridge.py
index e91c56e7163..228f4c8c1fc 100644
--- a/share/qtcreator/debugger/pdbbridge.py
+++ b/share/qtcreator/debugger/pdbbridge.py
@@ -1445,7 +1445,7 @@ class QtcInternalDumper():
self.updateData(args)
def updateData(self, args):
- self.expandedINames = __builtins__.set(args.get('expanded', []))
+ self.expandedINames = args.get('expanded', {})
self.typeformats = args.get('typeformats', {})
self.formats = args.get('formats', {})
self.output = ''
diff --git a/share/qtcreator/debugger/utils.py b/share/qtcreator/debugger/utils.py
index fe2e558e711..8019d1e530a 100644
--- a/share/qtcreator/debugger/utils.py
+++ b/share/qtcreator/debugger/utils.py
@@ -4,6 +4,7 @@
# Debugger start modes. Keep in sync with DebuggerStartMode in debuggerconstants.h
+# MT: Why does this not match (anymore?) to debuggerconstants.h : DebuggerStartMode ?
class DebuggerStartMode():
(
NoStartMode,
diff --git a/share/qtcreator/scripts/openTerminal.py b/share/qtcreator/scripts/openTerminal.py
deleted file mode 100755
index 3dd1bcb1b92..00000000000
--- a/share/qtcreator/scripts/openTerminal.py
+++ /dev/null
@@ -1,112 +0,0 @@
-#!/usr/bin/env python3
-
-# Copyright (C) 2018 The Qt Company Ltd.
-# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-import os
-import pipes
-import stat
-import subprocess
-import sys
-from tempfile import NamedTemporaryFile
-
-def quote_shell(arg):
- return pipes.quote(arg)
-
-def clean_environment_script():
- # keep some basic environment settings to ensure functioning terminal and config files
- env_to_keep = ' '.join(['_', 'HOME', 'LOGNAME', 'PWD', 'SHELL', 'TMPDIR', 'USER', 'TERM',
- 'TERM_PROGRAM', 'TERM_PROGRAM_VERSION', 'TERM_SESSION_CLASS_ID',
- 'TERM_SESSION_ID'])
- return r'''
-function ignore() {
- local keys=(''' + env_to_keep + ''')
- local v=$1
- for e in "${keys[@]}"; do [[ "$e" == "$v" ]] && return 0; done
-}
-while read -r line; do
- key=$(echo $line | /usr/bin/cut -d '=' -f 1)
- ignore $key || unset $key
-done < <(env)
-'''
-
-def system_login_script_bash():
- return r'''
-[ -r /etc/profile ] && source /etc/profile
-# fake behavior of /etc/profile as if BASH was set. It isn't for non-interactive shell
-export PS1='\h:\W \u\$ '
-[ -r /etc/bashrc ] && source /etc/bashrc
-'''
-
-def login_script_bash():
- return r'''
-if [ -f $HOME/.bash_profile ]; then
- source $HOME/.bash_profile
-elif [ -f $HOME/.bash_login ]; then
- source $HOME/.bash_login ]
-elif [ -f $HOME/.profile ]; then
- source $HOME/.profile
-fi
-'''
-
-def system_login_script_zsh():
- return '[ -r /etc/profile ] && source /etc/profile\n'
-
-def login_script_zsh():
- return r'''
-[ -r $HOME/.zprofile ] && source $HOME/.zprofile
-[ -r $HOME/.zshrc ] && source $HOME/.zshrc
-[ -r $HOME/.zlogin ] && source $HOME/.zlogin
-'''
-
-def environment_script():
- return ''.join(['export ' + quote_shell(key + '=' + os.environ[key]) + '\n'
- for key in os.environ])
-
-def zsh_setup(shell):
- return (shell,
- system_login_script_zsh,
- login_script_zsh,
- shell + ' -c',
- shell + ' -d -f')
-
-def bash_setup(shell):
- bash = shell if shell is not None and shell.endswith('/bash') else '/bin/bash'
- return (bash,
- system_login_script_bash,
- login_script_bash,
- bash + ' -c',
- bash + ' --noprofile -l')
-
-def main():
- # create temporary file to be sourced into bash that deletes itself
- with NamedTemporaryFile(mode='wt', delete=False) as shell_script:
- shell = os.environ.get('SHELL')
- shell, system_login_script, login_script, non_interactive_shell, interactive_shell = (
- zsh_setup(shell) if shell is not None and shell.endswith('/zsh')
- else bash_setup(shell))
-
- commands = ('#!' + shell + '\n' +
- 'rm ' + quote_shell(shell_script.name) + '\n' +
- clean_environment_script() +
- system_login_script() + # /etc/(z)profile by default resets the path, so do first
- environment_script() +
- login_script() +
- 'cd ' + quote_shell(os.getcwd()) + '\n' +
- ('exec ' + non_interactive_shell + ' ' +
- quote_shell(' '.join([quote_shell(arg) for arg in sys.argv[1:]])) + '\n'
- if len(sys.argv) > 1 else 'exec ' + interactive_shell + '\n')
- )
- shell_script.write(commands)
- shell_script.flush()
- os.chmod(shell_script.name, stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR)
- # TODO /usr/bin/open doesn't work with notarized app in macOS 13,
- # use osascript instead (QTCREATORBUG-28683).
- # This has the disadvantage that the Terminal windows doesn't close
- # automatically anymore.
- # subprocess.call(['/usr/bin/open', '-a', 'Terminal', shell_script.name])
- subprocess.call(['/usr/bin/osascript', '-e', 'tell app "Terminal" to activate'])
- subprocess.call(['/usr/bin/osascript', '-e', 'tell app "Terminal" to do script "' + shell_script.name + '"'])
-
-if __name__ == "__main__":
- main()
diff --git a/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_collection.cpp b/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_collection.cpp
index 7e64374bab7..c5438f45ca2 100644
--- a/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_collection.cpp
+++ b/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_collection.cpp
@@ -10,5 +10,3 @@ QList<QDesignerCustomWidgetInterface*> @COLLECTION_PLUGIN_CLASS@::customWidgets(
{
return m_widgets;
}
-
-@COLLECTION_PLUGIN_EXPORT@
diff --git a/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_collection.h b/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_collection.h
index f9a898e6a8a..e8e792915bb 100644
--- a/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_collection.h
+++ b/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_collection.h
@@ -15,9 +15,9 @@ class @COLLECTION_PLUGIN_CLASS@ : public QObject, public QDesignerCustomWidgetCo
@COLLECTION_PLUGIN_METADATA@
public:
- explicit @COLLECTION_PLUGIN_CLASS@(QObject *parent = 0);
+ explicit @COLLECTION_PLUGIN_CLASS@(QObject *parent = nullptr);
- virtual QList<QDesignerCustomWidgetInterface*> customWidgets() const;
+ QList<QDesignerCustomWidgetInterface*> customWidgets() const override;
private:
QList<QDesignerCustomWidgetInterface*> m_widgets;
diff --git a/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_plugin.pro b/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_plugin.pro
index 53d12b7397f..ab125bfb499 100644
--- a/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_plugin.pro
+++ b/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_plugin.pro
@@ -7,11 +7,7 @@ SOURCES =@PLUGIN_SOURCES@
RESOURCES = @PLUGIN_RESOURCES@
LIBS += -L. @WIDGET_LIBS@
-greaterThan(QT_MAJOR_VERSION, 4) {
- QT += designer
-} else {
- CONFIG += designer
-}
+QT += designer
target.path = $$[QT_INSTALL_PLUGINS]/designer
INSTALLS += target
diff --git a/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_single.cpp b/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_single.cpp
index 6ffc8481da2..06790ac5478 100644
--- a/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_single.cpp
+++ b/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_single.cpp
@@ -6,7 +6,6 @@
@PLUGIN_CLASS@::@PLUGIN_CLASS@(QObject *parent)
: QObject(parent)
{
- m_initialized = false;
}
void @PLUGIN_CLASS@::initialize(QDesignerFormEditorInterface * /* core */)
@@ -61,11 +60,10 @@ bool @PLUGIN_CLASS@::isContainer() const
QString @PLUGIN_CLASS@::domXml() const
{
- return QLatin1String("@WIDGET_DOMXML@");
+ return QLatin1String(@WIDGET_DOMXML@);
}
QString @PLUGIN_CLASS@::includeFile() const
{
return QLatin1String("@WIDGET_HEADER@");
}
-@SINGLE_PLUGIN_EXPORT@
diff --git a/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_single.h b/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_single.h
index 0767c0581df..2402458092e 100644
--- a/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_single.h
+++ b/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_single.h
@@ -14,22 +14,22 @@ class @PLUGIN_CLASS@ : public QObject, public QDesignerCustomWidgetInterface
@SINGLE_PLUGIN_METADATA@
public:
- @PLUGIN_CLASS@(QObject *parent = 0);
+ explicit @PLUGIN_CLASS@(QObject *parent = nullptr);
- bool isContainer() const;
- bool isInitialized() const;
- QIcon icon() const;
- QString domXml() const;
- QString group() const;
- QString includeFile() const;
- QString name() const;
- QString toolTip() const;
- QString whatsThis() const;
- QWidget *createWidget(QWidget *parent);
- void initialize(QDesignerFormEditorInterface *core);
+ bool isContainer() const override;
+ bool isInitialized() const override;
+ QIcon icon() const override;
+ QString domXml() const override;
+ QString group() const override;
+ QString includeFile() const override;
+ QString name() const override;
+ QString toolTip() const override;
+ QString whatsThis() const override;
+ QWidget *createWidget(QWidget *parent) override;
+ void initialize(QDesignerFormEditorInterface *core) override;
private:
- bool m_initialized;
+ bool m_initialized = false;
};
@if ! '%{Cpp:PragmaOnce}'
diff --git a/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_widget.h b/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_widget.h
index 50256c5ff54..3ec192c6d31 100644
--- a/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_widget.h
+++ b/share/qtcreator/templates/qt4project/customwidgetwizard/tpl_widget.h
@@ -12,7 +12,7 @@ class @WIDGET_CLASS@ : public @WIDGET_BASE_CLASS@
Q_OBJECT
public:
- @WIDGET_CLASS@(QWidget *parent = 0);
+ explicit @WIDGET_CLASS@(QWidget *parent = nullptr);
};
@if ! '%{Cpp:PragmaOnce}'
diff --git a/share/qtcreator/templates/wizards/projects/qtforpythonapplication/empty/wizard.json b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/empty/wizard.json
index 2d566dc1f66..8ca7c0cdcc5 100644
--- a/share/qtcreator/templates/wizards/projects/qtforpythonapplication/empty/wizard.json
+++ b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/empty/wizard.json
@@ -26,20 +26,28 @@
{
"trDisplayName": "Define Project Details",
"trShortTitle": "Details",
- "typeId": "Fields",
- "data" :
- [
- {
- "name": "PySideVersion",
- "trDisplayName": "PySide version:",
- "type": "ComboBox",
- "data":
+ "typeId": "PythonConfiguration",
+ "data":
+ {
+ "index": 0,
+ "items":
+ [
{
- "index": 1,
- "items": [ "PySide2", "PySide6" ]
+ "trKey": "PySide 6",
+ "value":
+ {
+ "PySideVersion": "PySide6"
+ }
+ },
+ {
+ "trKey": "PySide 2",
+ "value":
+ {
+ "PySideVersion": "PySide2"
+ }
}
- }
- ]
+ ]
+ }
},
{
"trDisplayName": "Project Management",
diff --git a/share/qtcreator/templates/wizards/projects/qtforpythonapplication/mainwindow/wizard.json b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/mainwindow/wizard.json
index 58e9f8cd526..9156caaffd8 100644
--- a/share/qtcreator/templates/wizards/projects/qtforpythonapplication/mainwindow/wizard.json
+++ b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/mainwindow/wizard.json
@@ -31,16 +31,6 @@
"data" :
[
{
- "name": "PySideVersion",
- "trDisplayName": "PySide version:",
- "type": "ComboBox",
- "data":
- {
- "index": 1,
- "items": [ "PySide2", "PySide6" ]
- }
- },
- {
"name": "Class",
"trDisplayName": "Class name:",
"mandatory": true,
@@ -78,6 +68,32 @@
]
},
{
+ "trDisplayName": "Define Project Details",
+ "trShortTitle": "Details",
+ "typeId": "PythonConfiguration",
+ "data":
+ {
+ "index": 0,
+ "items":
+ [
+ {
+ "trKey": "PySide 6",
+ "value":
+ {
+ "PySideVersion": "PySide6"
+ }
+ },
+ {
+ "trKey": "PySide 2",
+ "value":
+ {
+ "PySideVersion": "PySide2"
+ }
+ }
+ ]
+ }
+ },
+ {
"trDisplayName": "Project Management",
"trShortTitle": "Summary",
"typeId": "Summary"
diff --git a/share/qtcreator/templates/wizards/projects/qtforpythonapplication/qtquickapplication/wizard.json b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/qtquickapplication/wizard.json
index c04407347c8..b1b75fc0885 100644
--- a/share/qtcreator/templates/wizards/projects/qtforpythonapplication/qtquickapplication/wizard.json
+++ b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/qtquickapplication/wizard.json
@@ -30,67 +30,59 @@
{
"trDisplayName": "Define Project Details",
"trShortTitle": "Details",
- "typeId": "Fields",
+ "typeId": "PythonConfiguration",
"data":
- [
- {
- "name": "QtVersion",
- "trDisplayName": "PySide version:",
- "type": "ComboBox",
- "data":
+ {
+ "index": 0,
+ "items":
+ [
+ {
+ "trKey": "PySide 6",
+ "value":
+ {
+ "QtQuickVersion": "",
+ "QtQuickWindowVersion": "",
+ "PySideVersion": "PySide6"
+ }
+ },
+ {
+ "trKey": "PySide 5.15",
+ "value":
+ {
+ "QtQuickVersion": "2.15",
+ "QtQuickWindowVersion": "2.15",
+ "PySideVersion": "PySide2"
+ }
+ },
{
- "index": 0,
- "items":
- [
- {
- "trKey": "PySide 6",
- "value":
- {
- "QtQuickVersion": "",
- "QtQuickWindowVersion": "",
- "PySideVersion": "PySide6"
- }
- },
- {
- "trKey": "PySide 5.15",
- "value":
- {
- "QtQuickVersion": "2.15",
- "QtQuickWindowVersion": "2.15",
- "PySideVersion": "PySide2"
- }
- },
- {
- "trKey": "PySide 5.14",
- "value":
- {
- "QtQuickVersion": "2.14",
- "QtQuickWindowVersion": "2.14",
- "PySideVersion": "PySide2"
- }
- },
- {
- "trKey": "PySide 5.13",
- "value":
- {
- "QtQuickVersion": "2.13",
- "QtQuickWindowVersion": "2.13",
- "PySideVersion": "PySide2"
- }
- },
- {
- "trKey": "PySide 5.12",
- "value":
- {
- "QtQuickVersion": "2.12",
- "QtQuickWindowVersion": "2.12",
- "PySideVersion": "PySide2"
- }
- }
- ]
+ "trKey": "PySide 5.14",
+ "value":
+ {
+ "QtQuickVersion": "2.14",
+ "QtQuickWindowVersion": "2.14",
+ "PySideVersion": "PySide2"
+ }
+ },
+ {
+ "trKey": "PySide 5.13",
+ "value":
+ {
+ "QtQuickVersion": "2.13",
+ "QtQuickWindowVersion": "2.13",
+ "PySideVersion": "PySide2"
+ }
+ },
+ {
+ "trKey": "PySide 5.12",
+ "value":
+ {
+ "QtQuickVersion": "2.12",
+ "QtQuickWindowVersion": "2.12",
+ "PySideVersion": "PySide2"
+ }
}
- }
- ]
+ ]
+ }
},
{
"trDisplayName": "Project Management",
diff --git a/share/qtcreator/templates/wizards/projects/qtforpythonapplication/widget/wizard.json b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/widget/wizard.json
index 951be734757..8362cd55ecc 100644
--- a/share/qtcreator/templates/wizards/projects/qtforpythonapplication/widget/wizard.json
+++ b/share/qtcreator/templates/wizards/projects/qtforpythonapplication/widget/wizard.json
@@ -31,16 +31,6 @@
"data" :
[
{
- "name": "PySideVersion",
- "trDisplayName": "PySide version:",
- "type": "ComboBox",
- "data":
- {
- "index": 1,
- "items": [ "PySide2", "PySide6" ]
- }
- },
- {
"name": "Class",
"trDisplayName": "Class name:",
"mandatory": true,
@@ -78,6 +68,32 @@
]
},
{
+ "trDisplayName": "Define Python Interpreter",
+ "trShortTitle": "Interpreter",
+ "typeId": "PythonConfiguration",
+ "data":
+ {
+ "index": 0,
+ "items":
+ [
+ {
+ "trKey": "PySide 6",
+ "value":
+ {
+ "PySideVersion": "PySide6"
+ }
+ },
+ {
+ "trKey": "PySide 2",
+ "value":
+ {
+ "PySideVersion": "PySide2"
+ }
+ }
+ ]
+ }
+ },
+ {
"trDisplayName": "Project Management",
"trShortTitle": "Summary",
"typeId": "Summary"
diff --git a/share/qtcreator/templates/wizards/qtcreatorplugin/CMakeLists.txt b/share/qtcreator/templates/wizards/qtcreatorplugin/CMakeLists.txt
index 48884eae644..a60e46cb7cd 100644
--- a/share/qtcreator/templates/wizards/qtcreatorplugin/CMakeLists.txt
+++ b/share/qtcreator/templates/wizards/qtcreatorplugin/CMakeLists.txt
@@ -22,6 +22,23 @@ find_package(QtCreator REQUIRED COMPONENTS Core)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
set(QtX Qt${QT_VERSION_MAJOR})
+# Add a CMake option that enables building your plugin with tests.
+# You don't want your released plugin binaries to contain tests,
+# so make that default to 'NO'.
+# Enable tests by passing -DWITH_TESTS=ON to CMake.
+option(WITH_TESTS "Builds with tests" NO)
+
+if(WITH_TESTS)
+ # Look for QtTest
+ find_package(${QtX} REQUIRED COMPONENTS Test)
+
+ # Tell CMake functions like add_qtc_plugin about the QtTest component.
+ set(IMPLICIT_DEPENDS Qt::Test)
+
+ # Enable ctest for auto tests.
+ enable_testing()
+endif()
+
add_qtc_plugin(%{PluginName}
PLUGIN_DEPENDS
QtCreator::Core
diff --git a/share/qtcreator/templates/wizards/qtcreatorplugin/myplugin_global.h b/share/qtcreator/templates/wizards/qtcreatorplugin/myplugin_global.h
index b8b387bc01f..ad863f17945 100644
--- a/share/qtcreator/templates/wizards/qtcreatorplugin/myplugin_global.h
+++ b/share/qtcreator/templates/wizards/qtcreatorplugin/myplugin_global.h
@@ -6,6 +6,8 @@
#define %{GLOBAL_GUARD}
@endif
+#include <qglobal.h>
+
#if defined(%{LibraryDefine})
# define %{LibraryExport} Q_DECL_EXPORT
#else
diff --git a/share/qtcreator/themes/dark.creatortheme b/share/qtcreator/themes/dark.creatortheme
index 82fa00f6142..b862ac5fc94 100644
--- a/share/qtcreator/themes/dark.creatortheme
+++ b/share/qtcreator/themes/dark.creatortheme
@@ -184,7 +184,6 @@ BadgeLabelBackgroundColorChecked=normalBackground
BadgeLabelBackgroundColorUnchecked=selectedBackground
BadgeLabelTextColorChecked=text
BadgeLabelTextColorUnchecked=text
-CanceledSearchTextColor=ff0000
ComboBoxArrowColor=text
ComboBoxArrowColorDisabled=text
ComboBoxTextColor=text
@@ -385,6 +384,27 @@ QmlDesigner_FormeditorBackgroundColor=qmlDesignerButtonColor
QmlDesigner_AlternateBackgroundColor=qmlDesignerButtonColor
QmlDesigner_ScrollBarHandleColor=ff505050
+TerminalForeground=ffffffff
+TerminalBackground=normalBackground
+TerminalSelection=7fffffff
+TerminalFindMatch=7fffff00
+TerminalAnsi0=000000
+TerminalAnsi1=8b1b10
+TerminalAnsi2=4aa32e
+TerminalAnsi3=9a9a2f
+TerminalAnsi4=0058D1
+TerminalAnsi5=a320ac
+TerminalAnsi6=49a3b0
+TerminalAnsi7=bfbfbf
+TerminalAnsi8=666666
+TerminalAnsi9=d22d1f
+TerminalAnsi10=62d63f
+TerminalAnsi11=e5e54b
+TerminalAnsi12=003EFF
+TerminalAnsi13=d22dde
+TerminalAnsi14=69e2e4
+TerminalAnsi15=e5e5e6
+
[Flags]
ComboBoxDrawTextShadow=false
DerivePaletteFromTheme=true
diff --git a/share/qtcreator/themes/default.creatortheme b/share/qtcreator/themes/default.creatortheme
index 06d36f669e9..31a7dcc8ea2 100644
--- a/share/qtcreator/themes/default.creatortheme
+++ b/share/qtcreator/themes/default.creatortheme
@@ -175,7 +175,6 @@ BadgeLabelBackgroundColorChecked=ffe0e0e0
BadgeLabelBackgroundColorUnchecked=ff808080
BadgeLabelTextColorChecked=ff606060
BadgeLabelTextColorUnchecked=ffffffff
-CanceledSearchTextColor=ffff0000
ComboBoxArrowColor=ffb8b5b2
ComboBoxArrowColorDisabled=ffdcdcdc
ComboBoxTextColor=ffffffff
@@ -353,6 +352,27 @@ QmlDesigner_FormeditorBackgroundColor=qmlDesignerButtonColor
QmlDesigner_AlternateBackgroundColor=qmlDesignerButtonColor
QmlDesigner_ScrollBarHandleColor=ff7a7a7a
+TerminalForeground=ff000000
+TerminalBackground=ffffffff
+TerminalSelection=3f000000
+TerminalFindMatch=7fffff00
+TerminalAnsi0=000000
+TerminalAnsi1=8b1b10
+TerminalAnsi2=4aa32e
+TerminalAnsi3=9a9a2f
+TerminalAnsi4=0000ab
+TerminalAnsi5=a320ac
+TerminalAnsi6=49a3b0
+TerminalAnsi7=bfbfbf
+TerminalAnsi8=666666
+TerminalAnsi9=d22d1f
+TerminalAnsi10=62d63f
+TerminalAnsi11=dac911
+TerminalAnsi12=0000fe
+TerminalAnsi13=d22dde
+TerminalAnsi14=69e2e4
+TerminalAnsi15=e5e5e6
+
[Flags]
ComboBoxDrawTextShadow=true
DerivePaletteFromTheme=false
diff --git a/share/qtcreator/themes/design-light.creatortheme b/share/qtcreator/themes/design-light.creatortheme
index 12691562788..253cb5f96a6 100644
--- a/share/qtcreator/themes/design-light.creatortheme
+++ b/share/qtcreator/themes/design-light.creatortheme
@@ -189,7 +189,6 @@ BadgeLabelBackgroundColorChecked=ffe0e0e0
BadgeLabelBackgroundColorUnchecked=ff808080
BadgeLabelTextColorChecked=ff606060
BadgeLabelTextColorUnchecked=ffffffff
-CanceledSearchTextColor=ff0000
ComboBoxArrowColor=toolBarItem
ComboBoxArrowColorDisabled=toolBarItemDisabled
ComboBoxTextColor=fancyBarsNormalTextColor
@@ -397,6 +396,27 @@ PaletteWindowTextDisabled=textDisabled
PaletteBaseDisabled=backgroundColorDisabled
PaletteTextDisabled=textDisabled
+TerminalForeground=ff000000
+TerminalBackground=normalBackground
+TerminalSelection=3f000000
+TerminalFindMatch=7fffff00
+TerminalAnsi0=000000
+TerminalAnsi1=8b1b10
+TerminalAnsi2=4aa32e
+TerminalAnsi3=9a9a2f
+TerminalAnsi4=0000ab
+TerminalAnsi5=a320ac
+TerminalAnsi6=49a3b0
+TerminalAnsi7=bfbfbf
+TerminalAnsi8=666666
+TerminalAnsi9=d22d1f
+TerminalAnsi10=62d63f
+TerminalAnsi11=dac911
+TerminalAnsi12=0000fe
+TerminalAnsi13=d22dde
+TerminalAnsi14=69e2e4
+TerminalAnsi15=e5e5e6
+
[Flags]
ComboBoxDrawTextShadow=false
DerivePaletteFromTheme=true
diff --git a/share/qtcreator/themes/design.creatortheme b/share/qtcreator/themes/design.creatortheme
index 93355132175..462d49f7f87 100644
--- a/share/qtcreator/themes/design.creatortheme
+++ b/share/qtcreator/themes/design.creatortheme
@@ -186,7 +186,6 @@ BadgeLabelBackgroundColorChecked=ffe0e0e0
BadgeLabelBackgroundColorUnchecked=ff808080
BadgeLabelTextColorChecked=ff606060
BadgeLabelTextColorUnchecked=ffffffff
-CanceledSearchTextColor=ff0000
ComboBoxArrowColor=toolBarItem
ComboBoxArrowColorDisabled=toolBarItemDisabled
ComboBoxTextColor=fancyBarsNormalTextColor
@@ -497,6 +496,27 @@ PaletteTextDisabled=textDisabled
PaletteMid=ffafafaf
PalettePlaceholderText=ff808081
+TerminalForeground=ffffffff
+TerminalBackground=normalBackground
+TerminalSelection=7fffffff
+TerminalFindMatch=7fffff00
+TerminalAnsi0=000000
+TerminalAnsi1=8b1b10
+TerminalAnsi2=4aa32e
+TerminalAnsi3=9a9a2f
+TerminalAnsi4=0058D1
+TerminalAnsi5=a320ac
+TerminalAnsi6=49a3b0
+TerminalAnsi7=bfbfbf
+TerminalAnsi8=666666
+TerminalAnsi9=d22d1f
+TerminalAnsi10=62d63f
+TerminalAnsi11=e5e54b
+TerminalAnsi12=003EFF
+TerminalAnsi13=d22dde
+TerminalAnsi14=69e2e4
+TerminalAnsi15=e5e5e6
+
[Flags]
ComboBoxDrawTextShadow=false
DerivePaletteFromTheme=true
diff --git a/share/qtcreator/themes/flat-dark.creatortheme b/share/qtcreator/themes/flat-dark.creatortheme
index 1e282303310..b05539d9635 100644
--- a/share/qtcreator/themes/flat-dark.creatortheme
+++ b/share/qtcreator/themes/flat-dark.creatortheme
@@ -188,7 +188,6 @@ BadgeLabelBackgroundColorChecked=ffe0e0e0
BadgeLabelBackgroundColorUnchecked=ff808080
BadgeLabelTextColorChecked=ff606060
BadgeLabelTextColorUnchecked=ffffffff
-CanceledSearchTextColor=ff0000
ComboBoxArrowColor=toolBarItem
ComboBoxArrowColorDisabled=toolBarItemDisabled
ComboBoxTextColor=fancyBarsNormalTextColor
@@ -389,6 +388,27 @@ PaletteTextDisabled=textDisabled
PaletteMid=ffa0a0a0
PalettePlaceholderText=ff7f7f80
+TerminalForeground=ffffffff
+TerminalBackground=normalBackground
+TerminalSelection=7fffffff
+TerminalFindMatch=7fffff00
+TerminalAnsi0=000000
+TerminalAnsi1=8b1b10
+TerminalAnsi2=4aa32e
+TerminalAnsi3=9a9a2f
+TerminalAnsi4=0058D1
+TerminalAnsi5=a320ac
+TerminalAnsi6=49a3b0
+TerminalAnsi7=bfbfbf
+TerminalAnsi8=666666
+TerminalAnsi9=d22d1f
+TerminalAnsi10=62d63f
+TerminalAnsi11=e5e54b
+TerminalAnsi12=003EFF
+TerminalAnsi13=d22dde
+TerminalAnsi14=69e2e4
+TerminalAnsi15=e5e5e6
+
[Flags]
ComboBoxDrawTextShadow=false
DerivePaletteFromTheme=true
diff --git a/share/qtcreator/themes/flat-light.creatortheme b/share/qtcreator/themes/flat-light.creatortheme
index d3ff03a7985..23c4d97ffb8 100644
--- a/share/qtcreator/themes/flat-light.creatortheme
+++ b/share/qtcreator/themes/flat-light.creatortheme
@@ -184,7 +184,6 @@ BadgeLabelBackgroundColorChecked=ffe0e0e0
BadgeLabelBackgroundColorUnchecked=ff808080
BadgeLabelTextColorChecked=ff606060
BadgeLabelTextColorUnchecked=ffffffff
-CanceledSearchTextColor=ff0000
ComboBoxArrowColor=toolBarItem
ComboBoxArrowColorDisabled=toolBarItemDisabled
ComboBoxTextColor=fancyBarsNormalTextColor
@@ -362,6 +361,27 @@ QmlDesigner_FormeditorBackgroundColor=qmlDesignerButtonColor
QmlDesigner_AlternateBackgroundColor=qmlDesignerButtonColor
QmlDesigner_ScrollBarHandleColor=ffcccccc
+TerminalForeground=ff000000
+TerminalBackground=normalBackground
+TerminalSelection=3f000000
+TerminalFindMatch=7fffff00
+TerminalAnsi0=000000
+TerminalAnsi1=8b1b10
+TerminalAnsi2=4aa32e
+TerminalAnsi3=9a9a2f
+TerminalAnsi4=0000ab
+TerminalAnsi5=a320ac
+TerminalAnsi6=49a3b0
+TerminalAnsi7=bfbfbf
+TerminalAnsi8=666666
+TerminalAnsi9=d22d1f
+TerminalAnsi10=62d63f
+TerminalAnsi11=dac911
+TerminalAnsi12=0000fe
+TerminalAnsi13=d22dde
+TerminalAnsi14=69e2e4
+TerminalAnsi15=e5e5e6
+
[Flags]
ComboBoxDrawTextShadow=false
DerivePaletteFromTheme=false
diff --git a/share/qtcreator/themes/flat.creatortheme b/share/qtcreator/themes/flat.creatortheme
index 45ffe8537df..a3d04ba9c5d 100644
--- a/share/qtcreator/themes/flat.creatortheme
+++ b/share/qtcreator/themes/flat.creatortheme
@@ -182,7 +182,6 @@ BadgeLabelBackgroundColorChecked=ffe0e0e0
BadgeLabelBackgroundColorUnchecked=ff808080
BadgeLabelTextColorChecked=ff606060
BadgeLabelTextColorUnchecked=ffffffff
-CanceledSearchTextColor=ff0000
ComboBoxArrowColor=toolBarItem
ComboBoxArrowColorDisabled=toolBarItemDisabled
ComboBoxTextColor=fancyBarsNormalTextColor
@@ -360,6 +359,27 @@ QmlDesigner_FormeditorBackgroundColor=qmlDesignerButtonColor
QmlDesigner_AlternateBackgroundColor=qmlDesignerButtonColor
QmlDesigner_ScrollBarHandleColor=ff595b5c
+TerminalForeground=ff000000
+TerminalBackground=normalBackground
+TerminalSelection=3f000000
+TerminalFindMatch=7fffff00
+TerminalAnsi0=000000
+TerminalAnsi1=8b1b10
+TerminalAnsi2=4aa32e
+TerminalAnsi3=9a9a2f
+TerminalAnsi4=0000ab
+TerminalAnsi5=a320ac
+TerminalAnsi6=49a3b0
+TerminalAnsi7=bfbfbf
+TerminalAnsi8=666666
+TerminalAnsi9=d22d1f
+TerminalAnsi10=62d63f
+TerminalAnsi11=dac911
+TerminalAnsi12=0000fe
+TerminalAnsi13=d22dde
+TerminalAnsi14=69e2e4
+TerminalAnsi15=e5e5e6
+
[Flags]
ComboBoxDrawTextShadow=false
DerivePaletteFromTheme=false
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index b567bbe120e..792b3012f27 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -36,9 +36,6 @@ install(EXPORT QtCreator
)
file(WRITE ${CMAKE_BINARY_DIR}/cmake/QtCreatorConfig.cmake "
-\# add module path for special FindQt5.cmake that considers Qt6 too
-list(APPEND CMAKE_MODULE_PATH \${CMAKE_CURRENT_LIST_DIR})
-
\# force plugins to same path naming conventions as Qt Creator
\# otherwise plugins will not be found
if(UNIX AND NOT APPLE)
@@ -50,10 +47,10 @@ if(UNIX AND NOT APPLE)
endif()
include(CMakeFindDependencyMacro)
-find_dependency(Qt5 ${IDE_QT_VERSION_MIN}
+find_dependency(Qt6 ${IDE_QT_VERSION_MIN}
COMPONENTS Concurrent Core Gui Widgets Core5Compat Network PrintSupport Qml Sql REQUIRED
)
-find_dependency(Qt5 COMPONENTS Quick QuickWidgets QUIET)
+find_dependency(Qt6 COMPONENTS Quick QuickWidgets QUIET)
if (NOT IDE_VERSION)
include(\${CMAKE_CURRENT_LIST_DIR}/QtCreatorIDEBranding.cmake)
@@ -87,7 +84,6 @@ file(COPY
${PROJECT_SOURCE_DIR}/cmake/QtCreatorDocumentation.cmake
${PROJECT_SOURCE_DIR}/cmake/QtCreatorAPI.cmake
${PROJECT_SOURCE_DIR}/cmake/QtCreatorAPIInternal.cmake
- ${PROJECT_SOURCE_DIR}/cmake/FindQt5.cmake
${PROJECT_SOURCE_DIR}/cmake/Config.cmake.in
${PROJECT_SOURCE_DIR}/cmake/QtcSeparateDebugInfo.cmake
${PROJECT_SOURCE_DIR}/cmake/QtcSeparateDebugInfo.Info.plist.in
@@ -102,7 +98,6 @@ install(
${PROJECT_SOURCE_DIR}/cmake/QtCreatorDocumentation.cmake
${PROJECT_SOURCE_DIR}/cmake/QtCreatorAPI.cmake
${PROJECT_SOURCE_DIR}/cmake/QtCreatorAPIInternal.cmake
- ${PROJECT_SOURCE_DIR}/cmake/FindQt5.cmake
${PROJECT_SOURCE_DIR}/cmake/Config.cmake.in
${PROJECT_SOURCE_DIR}/cmake/QtcSeparateDebugInfo.cmake
${PROJECT_SOURCE_DIR}/cmake/QtcSeparateDebugInfo.Info.plist.in
diff --git a/src/libs/3rdparty/CMakeLists.txt b/src/libs/3rdparty/CMakeLists.txt
index 7cf97ab87fc..0cf9818ed54 100644
--- a/src/libs/3rdparty/CMakeLists.txt
+++ b/src/libs/3rdparty/CMakeLists.txt
@@ -1,2 +1,8 @@
add_subdirectory(cplusplus)
add_subdirectory(syntax-highlighting)
+add_subdirectory(libvterm)
+add_subdirectory(libptyqt)
+
+if(WIN32)
+ add_subdirectory(winpty)
+endif()
diff --git a/src/libs/3rdparty/cplusplus/AST.cpp b/src/libs/3rdparty/cplusplus/AST.cpp
index 5d7c9e2a90c..5c59bb683ca 100644
--- a/src/libs/3rdparty/cplusplus/AST.cpp
+++ b/src/libs/3rdparty/cplusplus/AST.cpp
@@ -911,6 +911,8 @@ int DeclaratorAST::lastToken() const
return candidate;
if (equal_token)
return equal_token + 1;
+ if (requiresClause)
+ return requiresClause->lastToken();
if (post_attribute_list)
if (int candidate = post_attribute_list->lastToken())
return candidate;
@@ -1657,12 +1659,18 @@ int LambdaDeclaratorAST::firstToken() const
if (trailing_return_type)
if (int candidate = trailing_return_type->firstToken())
return candidate;
+ if (requiresClause)
+ if (int candidate = requiresClause->firstToken())
+ return candidate;
return 0;
}
/** \generated */
int LambdaDeclaratorAST::lastToken() const
{
+ if (requiresClause)
+ if (int candidate = requiresClause->firstToken())
+ return candidate;
if (trailing_return_type)
if (int candidate = trailing_return_type->lastToken())
return candidate;
@@ -1690,6 +1698,15 @@ int LambdaExpressionAST::firstToken() const
if (lambda_introducer)
if (int candidate = lambda_introducer->firstToken())
return candidate;
+ if (templateParameters)
+ if (int candidate = templateParameters->firstToken())
+ return candidate;
+ if (requiresClause)
+ if (int candidate = requiresClause->firstToken())
+ return candidate;
+ if (attributes)
+ if (int candidate = attributes->firstToken())
+ return candidate;
if (lambda_declarator)
if (int candidate = lambda_declarator->firstToken())
return candidate;
@@ -1708,6 +1725,15 @@ int LambdaExpressionAST::lastToken() const
if (lambda_declarator)
if (int candidate = lambda_declarator->lastToken())
return candidate;
+ if (attributes)
+ if (int candidate = attributes->firstToken())
+ return candidate;
+ if (requiresClause)
+ if (int candidate = requiresClause->firstToken())
+ return candidate;
+ if (templateParameters)
+ if (int candidate = templateParameters->firstToken())
+ return candidate;
if (lambda_introducer)
if (int candidate = lambda_introducer->lastToken())
return candidate;
@@ -3761,6 +3787,8 @@ int TemplateTypeParameterAST::firstToken() const
{
if (template_token)
return template_token;
+ if (typeConstraint)
+ return typeConstraint->firstToken();
if (less_token)
return less_token;
if (template_parameter_list)
@@ -3805,6 +3833,8 @@ int TemplateTypeParameterAST::lastToken() const
return candidate;
if (less_token)
return less_token + 1;
+ if (typeConstraint)
+ return typeConstraint->lastToken();
if (template_token)
return template_token + 1;
return 1;
@@ -4634,3 +4664,33 @@ int NoExceptOperatorExpressionAST::lastToken() const
return noexcept_token + 1;
return 1;
}
+
+int TypeConstraintAST::firstToken() const
+{
+ if (nestedName)
+ return nestedName->firstToken();
+ return conceptName->firstToken();
+}
+
+int TypeConstraintAST::lastToken() const
+{
+ if (greaterToken)
+ return greaterToken + 1;
+ return conceptName->lastToken();
+}
+
+int PlaceholderTypeSpecifierAST::firstToken() const
+{
+ if (typeConstraint)
+ return typeConstraint->firstToken();
+ if (declTypetoken)
+ return declTypetoken;
+ return autoToken;
+}
+
+int PlaceholderTypeSpecifierAST::lastToken() const
+{
+ if (rparenToken)
+ return rparenToken + 1;
+ return autoToken + 1;
+}
diff --git a/src/libs/3rdparty/cplusplus/AST.h b/src/libs/3rdparty/cplusplus/AST.h
index 9aa81ccdf66..0f40424464e 100644
--- a/src/libs/3rdparty/cplusplus/AST.h
+++ b/src/libs/3rdparty/cplusplus/AST.h
@@ -184,6 +184,7 @@ public:
virtual ArrayInitializerAST *asArrayInitializer() { return nullptr; }
virtual AsmDefinitionAST *asAsmDefinition() { return nullptr; }
virtual AttributeSpecifierAST *asAttributeSpecifier() { return nullptr; }
+ virtual AwaitExpressionAST *asAwaitExpression() { return nullptr; }
virtual BaseSpecifierAST *asBaseSpecifier() { return nullptr; }
virtual BinaryExpressionAST *asBinaryExpression() { return nullptr; }
virtual BoolLiteralAST *asBoolLiteral() { return nullptr; }
@@ -290,6 +291,7 @@ public:
virtual OperatorFunctionIdAST *asOperatorFunctionId() { return nullptr; }
virtual ParameterDeclarationAST *asParameterDeclaration() { return nullptr; }
virtual ParameterDeclarationClauseAST *asParameterDeclarationClause() { return nullptr; }
+ virtual PlaceholderTypeSpecifierAST *asPlaceholderTypeSpecifier() { return nullptr; }
virtual PointerAST *asPointer() { return nullptr; }
virtual PointerLiteralAST *asPointerLiteral() { return nullptr; }
virtual PointerToMemberAST *asPointerToMember() { return nullptr; }
@@ -322,6 +324,7 @@ public:
virtual StringLiteralAST *asStringLiteral() { return nullptr; }
virtual SwitchStatementAST *asSwitchStatement() { return nullptr; }
virtual TemplateDeclarationAST *asTemplateDeclaration() { return nullptr; }
+ virtual ConceptDeclarationAST *asConceptDeclaration() { return nullptr; }
virtual TemplateIdAST *asTemplateId() { return nullptr; }
virtual TemplateTypeParameterAST *asTemplateTypeParameter() { return nullptr; }
virtual ThisExpressionAST *asThisExpression() { return nullptr; }
@@ -329,6 +332,7 @@ public:
virtual TrailingReturnTypeAST *asTrailingReturnType() { return nullptr; }
virtual TranslationUnitAST *asTranslationUnit() { return nullptr; }
virtual TryBlockStatementAST *asTryBlockStatement() { return nullptr; }
+ virtual TypeConstraintAST *asTypeConstraint() { return nullptr; }
virtual TypeConstructorCallAST *asTypeConstructorCall() { return nullptr; }
virtual TypeIdAST *asTypeId() { return nullptr; }
virtual TypeidExpressionAST *asTypeidExpression() { return nullptr; }
@@ -336,9 +340,12 @@ public:
virtual TypenameTypeParameterAST *asTypenameTypeParameter() { return nullptr; }
virtual TypeofSpecifierAST *asTypeofSpecifier() { return nullptr; }
virtual UnaryExpressionAST *asUnaryExpression() { return nullptr; }
+ virtual RequiresExpressionAST *asRequiresExpression() { return nullptr; }
+ virtual RequiresClauseAST *asRequiresClause() { return nullptr; }
virtual UsingAST *asUsing() { return nullptr; }
virtual UsingDirectiveAST *asUsingDirective() { return nullptr; }
virtual WhileStatementAST *asWhileStatement() { return nullptr; }
+ virtual YieldExpressionAST *asYieldExpression() { return nullptr; }
protected:
virtual void accept0(ASTVisitor *visitor) = 0;
@@ -636,6 +643,49 @@ protected:
bool match0(AST *, ASTMatcher *) override;
};
+class CPLUSPLUS_EXPORT TypeConstraintAST: public AST
+{
+public:
+ NestedNameSpecifierListAST *nestedName = nullptr;
+ NameAST *conceptName = nullptr;
+ int lessToken = 0;
+ ExpressionListAST *templateArgs = nullptr;
+ int greaterToken = 0;
+
+ TypeConstraintAST *asTypeConstraint() override { return this; }
+
+ int firstToken() const override;
+ int lastToken() const override;
+
+ TypeConstraintAST *clone(MemoryPool *pool) const override;
+
+ void accept0(ASTVisitor *visitor) override;
+ bool match0(AST *, ASTMatcher *) override;
+};
+
+class CPLUSPLUS_EXPORT PlaceholderTypeSpecifierAST: public SpecifierAST
+{
+public:
+ TypeConstraintAST *typeConstraint = nullptr;
+ int declTypetoken = 0;
+ int lparenToken = 0;
+ int decltypeToken = 0;
+ int autoToken = 0;
+ int rparenToken = 0;
+
+public:
+ PlaceholderTypeSpecifierAST *asPlaceholderTypeSpecifier() override { return this; }
+
+ int firstToken() const override;
+ int lastToken() const override;
+
+ PlaceholderTypeSpecifierAST *clone(MemoryPool *pool) const override;
+
+protected:
+ void accept0(ASTVisitor *visitor) override;
+ bool match0(AST *, ASTMatcher *) override;
+};
+
class CPLUSPLUS_EXPORT DeclaratorAST: public AST
{
public:
@@ -646,6 +696,7 @@ public:
SpecifierListAST *post_attribute_list = nullptr;
int equal_token = 0;
ExpressionAST *initializer = nullptr;
+ RequiresClauseAST *requiresClause = nullptr;
public:
DeclaratorAST *asDeclarator() override { return this; }
@@ -2716,6 +2767,7 @@ public:
int less_token = 0;
DeclarationListAST *template_parameter_list = nullptr;
int greater_token = 0;
+ RequiresClauseAST *requiresClause = nullptr;
DeclarationAST *declaration = nullptr;
public: // annotations
@@ -2734,6 +2786,29 @@ protected:
bool match0(AST *, ASTMatcher *) override;
};
+class CPLUSPLUS_EXPORT ConceptDeclarationAST: public DeclarationAST
+{
+public:
+ int concept_token = 0;
+ NameAST *name = nullptr;
+ SpecifierListAST *attributes = nullptr;
+ int equals_token = 0;
+ ExpressionAST *constraint = nullptr;
+ int semicolon_token = 0;
+
+public:
+ ConceptDeclarationAST *asConceptDeclaration() override { return this; }
+
+ int firstToken() const override { return concept_token; }
+ int lastToken() const override { return semicolon_token + 1; }
+
+ ConceptDeclarationAST *clone(MemoryPool *pool) const override;
+
+protected:
+ void accept0(ASTVisitor *visitor) override;
+ bool match0(AST *, ASTMatcher *) override;
+};
+
class CPLUSPLUS_EXPORT ThrowExpressionAST: public ExpressionAST
{
public:
@@ -2753,6 +2828,44 @@ protected:
bool match0(AST *, ASTMatcher *) override;
};
+class CPLUSPLUS_EXPORT YieldExpressionAST: public ExpressionAST
+{
+public:
+ int yield_token = 0;
+ ExpressionAST *expression = nullptr;
+
+public:
+ YieldExpressionAST *asYieldExpression() override { return this; }
+
+ int firstToken() const override { return yield_token; }
+ int lastToken() const override { return expression->lastToken(); }
+
+ YieldExpressionAST *clone(MemoryPool *pool) const override;
+
+protected:
+ void accept0(ASTVisitor *visitor) override;
+ bool match0(AST *, ASTMatcher *) override;
+};
+
+class CPLUSPLUS_EXPORT AwaitExpressionAST: public ExpressionAST
+{
+public:
+ int await_token = 0;
+ ExpressionAST *castExpression = nullptr;
+
+public:
+ AwaitExpressionAST *asAwaitExpression() override { return this; }
+
+ int firstToken() const override { return await_token; }
+ int lastToken() const override { return castExpression->lastToken(); }
+
+ AwaitExpressionAST *clone(MemoryPool *pool) const override;
+
+protected:
+ void accept0(ASTVisitor *visitor) override;
+ bool match0(AST *, ASTMatcher *) override;
+};
+
class CPLUSPLUS_EXPORT NoExceptOperatorExpressionAST: public ExpressionAST
{
public:
@@ -2883,6 +2996,7 @@ class CPLUSPLUS_EXPORT TemplateTypeParameterAST: public DeclarationAST
{
public:
int template_token = 0;
+ TypeConstraintAST *typeConstraint = nullptr;
int less_token = 0;
DeclarationListAST *template_parameter_list = nullptr;
int greater_token = 0;
@@ -2927,6 +3041,48 @@ protected:
bool match0(AST *, ASTMatcher *) override;
};
+class CPLUSPLUS_EXPORT RequiresExpressionAST: public ExpressionAST
+{
+public:
+ int requires_token = 0;
+ int lparen_token = 0;
+ ParameterDeclarationClauseAST *parameters = nullptr;
+ int rparen_token = 0;
+ int lbrace_token = 0;
+ int rbrace_token = 0;
+
+public:
+ RequiresExpressionAST *asRequiresExpression() override { return this; }
+
+ int firstToken() const override { return requires_token; }
+ int lastToken() const override { return rbrace_token + 1; }
+
+ RequiresExpressionAST *clone(MemoryPool *pool) const override;
+
+protected:
+ void accept0(ASTVisitor *visitor) override;
+ bool match0(AST *, ASTMatcher *) override;
+};
+
+class CPLUSPLUS_EXPORT RequiresClauseAST: public AST
+{
+public:
+ int requires_token = 0;
+ ExpressionAST *constraint = nullptr;
+
+public:
+ RequiresClauseAST *asRequiresClause() override { return this; }
+
+ int firstToken() const override { return requires_token; }
+ int lastToken() const override { return constraint->lastToken(); }
+
+ RequiresClauseAST *clone(MemoryPool *pool) const override;
+
+protected:
+ void accept0(ASTVisitor *visitor) override;
+ bool match0(AST *, ASTMatcher *) override;
+};
+
class CPLUSPLUS_EXPORT UsingAST: public DeclarationAST
{
public:
@@ -3522,6 +3678,9 @@ class LambdaExpressionAST: public ExpressionAST
{
public:
LambdaIntroducerAST *lambda_introducer = nullptr;
+ DeclarationListAST *templateParameters = nullptr;
+ RequiresClauseAST *requiresClause = nullptr;
+ SpecifierListAST *attributes = nullptr;
LambdaDeclaratorAST *lambda_declarator = nullptr;
StatementAST *statement = nullptr;
@@ -3602,6 +3761,7 @@ public:
int mutable_token = 0;
ExceptionSpecificationAST *exception_specification = nullptr;
TrailingReturnTypeAST *trailing_return_type = nullptr;
+ RequiresClauseAST *requiresClause = nullptr;
public: // annotations
Function *symbol = nullptr;
diff --git a/src/libs/3rdparty/cplusplus/ASTClone.cpp b/src/libs/3rdparty/cplusplus/ASTClone.cpp
index 9e5a773bdb6..e494ad71ac6 100644
--- a/src/libs/3rdparty/cplusplus/ASTClone.cpp
+++ b/src/libs/3rdparty/cplusplus/ASTClone.cpp
@@ -141,6 +141,34 @@ DecltypeSpecifierAST *DecltypeSpecifierAST::clone(MemoryPool *pool) const
return ast;
}
+TypeConstraintAST *TypeConstraintAST::clone(MemoryPool *pool) const
+{
+ const auto ast = new (pool) TypeConstraintAST;
+ for (NestedNameSpecifierListAST *iter = nestedName, **ast_iter = &ast->nestedName; iter;
+ iter = iter->next, ast_iter = &(*ast_iter)->next)
+ *ast_iter = new (pool) NestedNameSpecifierListAST((iter->value) ? iter->value->clone(pool) : nullptr);
+ if (conceptName)
+ ast->conceptName = conceptName->clone(pool);
+ ast->lessToken = lessToken;
+ for (ExpressionListAST *iter = templateArgs, **ast_iter = &ast->templateArgs;
+ iter; iter = iter->next, ast_iter = &(*ast_iter)->next)
+ *ast_iter = new (pool) ExpressionListAST((iter->value) ? iter->value->clone(pool) : nullptr);
+ ast->greaterToken = greaterToken;
+ return ast;
+}
+
+PlaceholderTypeSpecifierAST *PlaceholderTypeSpecifierAST::clone(MemoryPool *pool) const
+{
+ const auto ast = new (pool) PlaceholderTypeSpecifierAST;
+ if (typeConstraint)
+ ast->typeConstraint = typeConstraint->clone(pool);
+ ast->lparenToken = lparenToken;
+ ast->declTypetoken = declTypetoken;
+ ast->autoToken = autoToken;
+ ast->rparenToken = rparenToken;
+ return ast;
+}
+
DeclaratorAST *DeclaratorAST::clone(MemoryPool *pool) const
{
DeclaratorAST *ast = new (pool) DeclaratorAST;
@@ -1279,11 +1307,50 @@ TemplateDeclarationAST *TemplateDeclarationAST::clone(MemoryPool *pool) const
iter; iter = iter->next, ast_iter = &(*ast_iter)->next)
*ast_iter = new (pool) DeclarationListAST((iter->value) ? iter->value->clone(pool) : nullptr);
ast->greater_token = greater_token;
+ if (requiresClause)
+ ast->requiresClause = requiresClause->clone(pool);
if (declaration)
ast->declaration = declaration->clone(pool);
return ast;
}
+ConceptDeclarationAST *ConceptDeclarationAST::clone(MemoryPool *pool) const
+{
+ const auto ast = new (pool) ConceptDeclarationAST;
+ ast->concept_token = concept_token;
+ ast->name = name->clone(pool);
+ ast->equals_token = equals_token;
+ ast->semicolon_token = semicolon_token;
+ for (SpecifierListAST *iter = attributes, **ast_iter = &ast->attributes;
+ iter; iter = iter->next, ast_iter = &(*ast_iter)->next) {
+ *ast_iter = new (pool) SpecifierListAST((iter->value) ? iter->value->clone(pool) : nullptr);
+ }
+ ast->constraint = constraint->clone(pool);
+ return ast;
+}
+
+RequiresExpressionAST *RequiresExpressionAST::clone(MemoryPool *pool) const
+{
+ const auto ast = new (pool) RequiresExpressionAST;
+ ast->requires_token = requires_token;
+ ast->lparen_token = lparen_token;
+ if (parameters)
+ ast->parameters = parameters->clone(pool);
+ ast->rparen_token = rparen_token;
+ ast->lbrace_token = lbrace_token;
+ ast->rbrace_token = rbrace_token;
+ return ast;
+}
+
+RequiresClauseAST *RequiresClauseAST::clone(MemoryPool *pool) const
+{
+ const auto ast = new (pool) RequiresClauseAST;
+ ast->requires_token = requires_token;
+ if (constraint)
+ ast->constraint = constraint->clone(pool);
+ return ast;
+}
+
ThrowExpressionAST *ThrowExpressionAST::clone(MemoryPool *pool) const
{
ThrowExpressionAST *ast = new (pool) ThrowExpressionAST;
@@ -1293,6 +1360,24 @@ ThrowExpressionAST *ThrowExpressionAST::clone(MemoryPool *pool) const
return ast;
}
+YieldExpressionAST *YieldExpressionAST::clone(MemoryPool *pool) const
+{
+ const auto ast = new (pool) YieldExpressionAST;
+ ast->yield_token = yield_token;
+ if (expression)
+ ast->expression = expression->clone(pool);
+ return ast;
+}
+
+AwaitExpressionAST *AwaitExpressionAST::clone(MemoryPool *pool) const
+{
+ const auto ast = new (pool) AwaitExpressionAST;
+ ast->await_token = await_token;
+ if (castExpression)
+ ast->castExpression = castExpression->clone(pool);
+ return ast;
+}
+
NoExceptOperatorExpressionAST *NoExceptOperatorExpressionAST::clone(MemoryPool *pool) const
{
NoExceptOperatorExpressionAST *ast = new (pool) NoExceptOperatorExpressionAST;
@@ -1364,6 +1449,8 @@ TemplateTypeParameterAST *TemplateTypeParameterAST::clone(MemoryPool *pool) cons
{
TemplateTypeParameterAST *ast = new (pool) TemplateTypeParameterAST;
ast->template_token = template_token;
+ if (typeConstraint)
+ ast->typeConstraint = typeConstraint->clone(pool);
ast->less_token = less_token;
for (DeclarationListAST *iter = template_parameter_list, **ast_iter = &ast->template_parameter_list;
iter; iter = iter->next, ast_iter = &(*ast_iter)->next)
@@ -1729,6 +1816,14 @@ LambdaExpressionAST *LambdaExpressionAST::clone(MemoryPool *pool) const
LambdaExpressionAST *ast = new (pool) LambdaExpressionAST;
if (lambda_introducer)
ast->lambda_introducer = lambda_introducer->clone(pool);
+ for (DeclarationListAST *iter = templateParameters, **ast_iter = &ast->templateParameters;
+ iter; iter = iter->next, ast_iter = &(*ast_iter)->next)
+ *ast_iter = new (pool) DeclarationListAST((iter->value) ? iter->value->clone(pool) : nullptr);
+ if (requiresClause)
+ ast->requiresClause = requiresClause->clone(pool);
+ for (SpecifierListAST *iter = attributes, **ast_iter = &ast->attributes;
+ iter; iter = iter->next, ast_iter = &(*ast_iter)->next)
+ *ast_iter = new (pool) SpecifierListAST((iter->value) ? iter->value->clone(pool) : nullptr);
if (lambda_declarator)
ast->lambda_declarator = lambda_declarator->clone(pool);
if (statement)
@@ -1780,6 +1875,8 @@ LambdaDeclaratorAST *LambdaDeclaratorAST::clone(MemoryPool *pool) const
ast->exception_specification = exception_specification->clone(pool);
if (trailing_return_type)
ast->trailing_return_type = trailing_return_type->clone(pool);
+ if (requiresClause)
+ ast->requiresClause = requiresClause->clone(pool);
return ast;
}
diff --git a/src/libs/3rdparty/cplusplus/ASTMatch0.cpp b/src/libs/3rdparty/cplusplus/ASTMatch0.cpp
index 4105ec3c52e..b58bd59efe0 100644
--- a/src/libs/3rdparty/cplusplus/ASTMatch0.cpp
+++ b/src/libs/3rdparty/cplusplus/ASTMatch0.cpp
@@ -112,6 +112,21 @@ bool DecltypeSpecifierAST::match0(AST *pattern, ASTMatcher *matcher)
return false;
}
+bool TypeConstraintAST::match0(AST *pattern, ASTMatcher *matcher)
+{
+ if (const auto _other = pattern->asTypeConstraint())
+ return matcher->match(this, _other);
+
+ return false;
+}
+
+bool PlaceholderTypeSpecifierAST::match0(AST *pattern, ASTMatcher *matcher)
+{
+ if (const auto _other = pattern->asPlaceholderTypeSpecifier())
+ return matcher->match(this, _other);
+ return false;
+}
+
bool DeclaratorAST::match0(AST *pattern, ASTMatcher *matcher)
{
if (DeclaratorAST *_other = pattern->asDeclarator())
@@ -896,6 +911,28 @@ bool TemplateDeclarationAST::match0(AST *pattern, ASTMatcher *matcher)
return false;
}
+bool ConceptDeclarationAST::match0(AST *pattern, ASTMatcher *matcher)
+{
+ if (ConceptDeclarationAST *_other = pattern->asConceptDeclaration())
+ return matcher->match(this, _other);
+
+ return false;
+}
+
+bool RequiresExpressionAST::match0(AST *pattern, ASTMatcher *matcher)
+{
+ if (const auto other = pattern->asRequiresExpression())
+ return matcher->match(this, other);
+ return false;
+}
+
+bool RequiresClauseAST::match0(AST *pattern, ASTMatcher *matcher)
+{
+ if (const auto other = pattern->asRequiresClause())
+ return matcher->match(this, other);
+ return false;
+}
+
bool ThrowExpressionAST::match0(AST *pattern, ASTMatcher *matcher)
{
if (ThrowExpressionAST *_other = pattern->asThrowExpression())
@@ -904,6 +941,20 @@ bool ThrowExpressionAST::match0(AST *pattern, ASTMatcher *matcher)
return false;
}
+bool YieldExpressionAST::match0(AST *pattern, ASTMatcher *matcher)
+{
+ if (const auto other = pattern->asYieldExpression())
+ return matcher->match(this, other);
+ return false;
+}
+
+bool AwaitExpressionAST::match0(AST *pattern, ASTMatcher *matcher)
+{
+ if (const auto other = pattern->asAwaitExpression())
+ return matcher->match(this, other);
+ return false;
+}
+
bool NoExceptOperatorExpressionAST::match0(AST *pattern, ASTMatcher *matcher)
{
if (NoExceptOperatorExpressionAST *_other = pattern->asNoExceptOperatorExpression())
diff --git a/src/libs/3rdparty/cplusplus/ASTMatcher.cpp b/src/libs/3rdparty/cplusplus/ASTMatcher.cpp
index fcfe86ab571..de6f6fc87c0 100644
--- a/src/libs/3rdparty/cplusplus/ASTMatcher.cpp
+++ b/src/libs/3rdparty/cplusplus/ASTMatcher.cpp
@@ -216,6 +216,25 @@ bool ASTMatcher::match(DecltypeSpecifierAST *node, DecltypeSpecifierAST *pattern
return true;
}
+bool ASTMatcher::match(TypeConstraintAST *node, TypeConstraintAST *pattern)
+{
+ if (!pattern->nestedName)
+ pattern->nestedName = node->nestedName;
+ else if (!AST::match(node->nestedName, pattern->nestedName, this))
+ return false;
+ if (!pattern->conceptName)
+ pattern->conceptName = node->conceptName;
+ else if (!AST::match(node->conceptName, pattern->conceptName, this))
+ return false;
+ pattern->lessToken = node->lessToken;
+ if (!pattern->templateArgs)
+ pattern->templateArgs = node->templateArgs;
+ else if (!AST::match(node->templateArgs, pattern->templateArgs, this))
+ return false;
+ pattern->greaterToken = node->greaterToken;
+ return true;
+}
+
bool ASTMatcher::match(DeclaratorAST *node, DeclaratorAST *pattern)
{
(void) node;
@@ -1749,6 +1768,19 @@ bool ASTMatcher::match(ParameterDeclarationClauseAST *node, ParameterDeclaration
return true;
}
+bool ASTMatcher::match(PlaceholderTypeSpecifierAST *node, PlaceholderTypeSpecifierAST *pattern)
+{
+ if (!pattern->typeConstraint)
+ pattern->typeConstraint = node->typeConstraint;
+ else if (!AST::match(node->typeConstraint, pattern->typeConstraint, this))
+ return false;
+ pattern->declTypetoken = node->declTypetoken;
+ pattern->lparenToken = node->lparenToken;
+ pattern->autoToken = node->autoToken;
+ pattern->rparenToken = node->rparenToken;
+ return true;
+}
+
bool ASTMatcher::match(CallAST *node, CallAST *pattern)
{
(void) node;
@@ -2173,6 +2205,11 @@ bool ASTMatcher::match(TemplateDeclarationAST *node, TemplateDeclarationAST *pat
pattern->greater_token = node->greater_token;
+ if (! pattern->requiresClause)
+ pattern->requiresClause = node->requiresClause;
+ else if (! AST::match(node->requiresClause, pattern->requiresClause, this))
+ return false;
+
if (! pattern->declaration)
pattern->declaration = node->declaration;
else if (! AST::match(node->declaration, pattern->declaration, this))
@@ -2181,6 +2218,50 @@ bool ASTMatcher::match(TemplateDeclarationAST *node, TemplateDeclarationAST *pat
return true;
}
+bool ASTMatcher::match(ConceptDeclarationAST *node, ConceptDeclarationAST *pattern)
+{
+ pattern->concept_token = node->concept_token;
+ pattern->equals_token = node->equals_token;
+ pattern->semicolon_token = node->semicolon_token;
+
+ if (!pattern->attributes)
+ pattern->attributes = node->attributes;
+ else if (!AST::match(node->attributes, pattern->attributes, this))
+ return false;
+
+ if (!pattern->constraint)
+ pattern->constraint = node->constraint;
+ else if (! AST::match(node->constraint, pattern->constraint, this))
+ return false;
+
+ return true;
+}
+
+bool ASTMatcher::match(RequiresExpressionAST *node, RequiresExpressionAST *pattern)
+{
+ pattern->requires_token = node->requires_token;
+ pattern->lparen_token = node->lparen_token;
+ pattern->lbrace_token = node->lbrace_token;
+ pattern->rbrace_token = node->rbrace_token;
+
+ if (!pattern->parameters)
+ pattern->parameters = node->parameters;
+ else if (!AST::match(node->parameters, pattern->parameters, this))
+ return false;
+
+ return true;
+}
+
+bool ASTMatcher::match(RequiresClauseAST *node, RequiresClauseAST *pattern)
+{
+ pattern->requires_token = node->requires_token;
+ if (!pattern->constraint)
+ pattern->constraint = node->constraint;
+ else if (!AST::match(node->constraint, pattern->constraint, this))
+ return false;
+ return true;
+}
+
bool ASTMatcher::match(ThrowExpressionAST *node, ThrowExpressionAST *pattern)
{
(void) node;
@@ -2196,6 +2277,26 @@ bool ASTMatcher::match(ThrowExpressionAST *node, ThrowExpressionAST *pattern)
return true;
}
+bool ASTMatcher::match(YieldExpressionAST *node, YieldExpressionAST *pattern)
+{
+ pattern->yield_token = node->yield_token;
+ if (!pattern->expression)
+ pattern->expression = node->expression;
+ else if (!AST::match(node->expression, pattern->expression, this))
+ return false;
+ return true;
+}
+
+bool ASTMatcher::match(AwaitExpressionAST *node, AwaitExpressionAST *pattern)
+{
+ pattern->await_token = node->await_token;
+ if (!pattern->castExpression)
+ pattern->castExpression = node->castExpression;
+ else if (!AST::match(node->castExpression, pattern->castExpression, this))
+ return false;
+ return true;
+}
+
bool ASTMatcher::match(NoExceptOperatorExpressionAST *node, NoExceptOperatorExpressionAST *pattern)
{
(void) node;
@@ -2317,6 +2418,11 @@ bool ASTMatcher::match(TemplateTypeParameterAST *node, TemplateTypeParameterAST
pattern->template_token = node->template_token;
+ if (!pattern->typeConstraint)
+ pattern->typeConstraint = node->typeConstraint;
+ else if (!AST::match(node->typeConstraint, pattern->typeConstraint, this))
+ return false;
+
pattern->less_token = node->less_token;
if (! pattern->template_parameter_list)
@@ -2950,6 +3056,21 @@ bool ASTMatcher::match(LambdaExpressionAST *node, LambdaExpressionAST *pattern)
else if (! AST::match(node->lambda_introducer, pattern->lambda_introducer, this))
return false;
+ if (! pattern->templateParameters)
+ pattern->templateParameters = node->templateParameters;
+ else if (! AST::match(node->templateParameters, pattern->templateParameters, this))
+ return false;
+
+ if (! pattern->requiresClause)
+ pattern->requiresClause = node->requiresClause;
+ else if (! AST::match(node->requiresClause, pattern->requiresClause, this))
+ return false;
+
+ if (! pattern->attributes)
+ pattern->attributes = node->attributes;
+ else if (! AST::match(node->attributes, pattern->attributes, this))
+ return false;
+
if (! pattern->lambda_declarator)
pattern->lambda_declarator = node->lambda_declarator;
else if (! AST::match(node->lambda_declarator, pattern->lambda_declarator, this))
@@ -3041,6 +3162,11 @@ bool ASTMatcher::match(LambdaDeclaratorAST *node, LambdaDeclaratorAST *pattern)
else if (! AST::match(node->trailing_return_type, pattern->trailing_return_type, this))
return false;
+ if (! pattern->requiresClause)
+ pattern->requiresClause = node->requiresClause;
+ else if (! AST::match(node->requiresClause, pattern->requiresClause, this))
+ return false;
+
return true;
}
diff --git a/src/libs/3rdparty/cplusplus/ASTMatcher.h b/src/libs/3rdparty/cplusplus/ASTMatcher.h
index c243e18b7bb..fb6109a07dc 100644
--- a/src/libs/3rdparty/cplusplus/ASTMatcher.h
+++ b/src/libs/3rdparty/cplusplus/ASTMatcher.h
@@ -38,6 +38,7 @@ public:
virtual bool match(ArrayAccessAST *node, ArrayAccessAST *pattern);
virtual bool match(ArrayDeclaratorAST *node, ArrayDeclaratorAST *pattern);
virtual bool match(ArrayInitializerAST *node, ArrayInitializerAST *pattern);
+ virtual bool match(AwaitExpressionAST *node, AwaitExpressionAST *pattern);
virtual bool match(AsmDefinitionAST *node, AsmDefinitionAST *pattern);
virtual bool match(BaseSpecifierAST *node, BaseSpecifierAST *pattern);
virtual bool match(BinaryExpressionAST *node, BinaryExpressionAST *pattern);
@@ -54,6 +55,7 @@ public:
virtual bool match(CompoundExpressionAST *node, CompoundExpressionAST *pattern);
virtual bool match(CompoundLiteralAST *node, CompoundLiteralAST *pattern);
virtual bool match(CompoundStatementAST *node, CompoundStatementAST *pattern);
+ virtual bool match(ConceptDeclarationAST *node, ConceptDeclarationAST *pattern);
virtual bool match(ConditionAST *node, ConditionAST *pattern);
virtual bool match(ConditionalExpressionAST *node, ConditionalExpressionAST *pattern);
virtual bool match(ContinueStatementAST *node, ContinueStatementAST *pattern);
@@ -139,6 +141,7 @@ public:
virtual bool match(OperatorFunctionIdAST *node, OperatorFunctionIdAST *pattern);
virtual bool match(ParameterDeclarationAST *node, ParameterDeclarationAST *pattern);
virtual bool match(ParameterDeclarationClauseAST *node, ParameterDeclarationClauseAST *pattern);
+ virtual bool match(PlaceholderTypeSpecifierAST *node, PlaceholderTypeSpecifierAST *pattern);
virtual bool match(PointerAST *node, PointerAST *pattern);
virtual bool match(PointerLiteralAST *node, PointerLiteralAST *pattern);
virtual bool match(PointerToMemberAST *node, PointerToMemberAST *pattern);
@@ -156,6 +159,8 @@ public:
virtual bool match(QualifiedNameAST *node, QualifiedNameAST *pattern);
virtual bool match(RangeBasedForStatementAST *node, RangeBasedForStatementAST *pattern);
virtual bool match(ReferenceAST *node, ReferenceAST *pattern);
+ virtual bool match(RequiresClauseAST *node, RequiresClauseAST *pattern);
+ virtual bool match(RequiresExpressionAST *node, RequiresExpressionAST *pattern);
virtual bool match(ReturnStatementAST *node, ReturnStatementAST *pattern);
virtual bool match(SimpleDeclarationAST *node, SimpleDeclarationAST *pattern);
virtual bool match(SimpleNameAST *node, SimpleNameAST *pattern);
@@ -173,6 +178,7 @@ public:
virtual bool match(TrailingReturnTypeAST *node, TrailingReturnTypeAST *pattern);
virtual bool match(TranslationUnitAST *node, TranslationUnitAST *pattern);
virtual bool match(TryBlockStatementAST *node, TryBlockStatementAST *pattern);
+ virtual bool match(TypeConstraintAST *node, TypeConstraintAST *pattern);
virtual bool match(TypeConstructorCallAST *node, TypeConstructorCallAST *pattern);
virtual bool match(TypeIdAST *node, TypeIdAST *pattern);
virtual bool match(TypeidExpressionAST *node, TypeidExpressionAST *pattern);
@@ -183,6 +189,7 @@ public:
virtual bool match(UsingAST *node, UsingAST *pattern);
virtual bool match(UsingDirectiveAST *node, UsingDirectiveAST *pattern);
virtual bool match(WhileStatementAST *node, WhileStatementAST *pattern);
+ virtual bool match(YieldExpressionAST *node, YieldExpressionAST *pattern);
};
} // namespace CPlusPlus
diff --git a/src/libs/3rdparty/cplusplus/ASTVisit.cpp b/src/libs/3rdparty/cplusplus/ASTVisit.cpp
index 2248f51a501..17941d8122f 100644
--- a/src/libs/3rdparty/cplusplus/ASTVisit.cpp
+++ b/src/libs/3rdparty/cplusplus/ASTVisit.cpp
@@ -110,6 +110,23 @@ void DecltypeSpecifierAST::accept0(ASTVisitor *visitor)
visitor->endVisit(this);
}
+void TypeConstraintAST::accept0(ASTVisitor *visitor)
+{
+ if (visitor->visit(this)) {
+ accept(nestedName, visitor);
+ accept(conceptName, visitor);
+ accept(templateArgs, visitor);
+ }
+ visitor->endVisit(this);
+}
+
+void PlaceholderTypeSpecifierAST::accept0(ASTVisitor *visitor)
+{
+ if (visitor->visit(this))
+ accept(typeConstraint, visitor);
+ visitor->endVisit(this);
+}
+
void DeclaratorAST::accept0(ASTVisitor *visitor)
{
if (visitor->visit(this)) {
@@ -941,11 +958,36 @@ void TemplateDeclarationAST::accept0(ASTVisitor *visitor)
{
if (visitor->visit(this)) {
accept(template_parameter_list, visitor);
+ accept(requiresClause, visitor);
accept(declaration, visitor);
}
visitor->endVisit(this);
}
+void ConceptDeclarationAST::accept0(ASTVisitor *visitor)
+{
+ if (visitor->visit(this)) {
+ accept(name, visitor);
+ accept(attributes, visitor);
+ accept(constraint, visitor);
+ }
+ visitor->endVisit(this);
+}
+
+void RequiresExpressionAST::accept0(ASTVisitor *visitor)
+{
+ if (visitor->visit(this))
+ accept(parameters, visitor);
+ visitor->endVisit(this);
+}
+
+void RequiresClauseAST::accept0(ASTVisitor *visitor)
+{
+ if (visitor->visit(this))
+ accept(constraint, visitor);
+ visitor->endVisit(this);
+}
+
void ThrowExpressionAST::accept0(ASTVisitor *visitor)
{
if (visitor->visit(this)) {
@@ -954,6 +996,20 @@ void ThrowExpressionAST::accept0(ASTVisitor *visitor)
visitor->endVisit(this);
}
+void YieldExpressionAST::accept0(ASTVisitor *visitor)
+{
+ if (visitor->visit(this))
+ accept(expression, visitor);
+ visitor->endVisit(this);
+}
+
+void AwaitExpressionAST::accept0(ASTVisitor *visitor)
+{
+ if (visitor->visit(this))
+ accept(castExpression, visitor);
+ visitor->endVisit(this);
+}
+
void NoExceptOperatorExpressionAST::accept0(ASTVisitor *visitor)
{
if (visitor->visit(this)) {
@@ -1009,6 +1065,7 @@ void TypenameTypeParameterAST::accept0(ASTVisitor *visitor)
void TemplateTypeParameterAST::accept0(ASTVisitor *visitor)
{
if (visitor->visit(this)) {
+ accept(typeConstraint, visitor);
accept(template_parameter_list, visitor);
accept(name, visitor);
accept(type_id, visitor);
@@ -1260,6 +1317,9 @@ void LambdaExpressionAST::accept0(ASTVisitor *visitor)
{
if (visitor->visit(this)) {
accept(lambda_introducer, visitor);
+ accept(templateParameters, visitor);
+ accept(requiresClause, visitor);
+ accept(attributes, visitor);
accept(lambda_declarator, visitor);
accept(statement, visitor);
}
@@ -1297,6 +1357,7 @@ void LambdaDeclaratorAST::accept0(ASTVisitor *visitor)
accept(attributes, visitor);
accept(exception_specification, visitor);
accept(trailing_return_type, visitor);
+ accept(requiresClause, visitor);
}
visitor->endVisit(this);
}
diff --git a/src/libs/3rdparty/cplusplus/ASTVisitor.h b/src/libs/3rdparty/cplusplus/ASTVisitor.h
index 691b57e9ac5..9d9fec75b1d 100644
--- a/src/libs/3rdparty/cplusplus/ASTVisitor.h
+++ b/src/libs/3rdparty/cplusplus/ASTVisitor.h
@@ -81,6 +81,7 @@ public:
virtual bool visit(ArrayDeclaratorAST *) { return true; }
virtual bool visit(ArrayInitializerAST *) { return true; }
virtual bool visit(AsmDefinitionAST *) { return true; }
+ virtual bool visit(AwaitExpressionAST *) { return true; }
virtual bool visit(BaseSpecifierAST *) { return true; }
virtual bool visit(BinaryExpressionAST *) { return true; }
virtual bool visit(BoolLiteralAST *) { return true; }
@@ -96,6 +97,7 @@ public:
virtual bool visit(CompoundExpressionAST *) { return true; }
virtual bool visit(CompoundLiteralAST *) { return true; }
virtual bool visit(CompoundStatementAST *) { return true; }
+ virtual bool visit(ConceptDeclarationAST *) { return true; }
virtual bool visit(ConditionAST *) { return true; }
virtual bool visit(ConditionalExpressionAST *) { return true; }
virtual bool visit(ContinueStatementAST *) { return true; }
@@ -181,6 +183,7 @@ public:
virtual bool visit(OperatorFunctionIdAST *) { return true; }
virtual bool visit(ParameterDeclarationAST *) { return true; }
virtual bool visit(ParameterDeclarationClauseAST *) { return true; }
+ virtual bool visit(PlaceholderTypeSpecifierAST *) { return true; }
virtual bool visit(PointerAST *) { return true; }
virtual bool visit(PointerLiteralAST *) { return true; }
virtual bool visit(PointerToMemberAST *) { return true; }
@@ -198,6 +201,8 @@ public:
virtual bool visit(QualifiedNameAST *) { return true; }
virtual bool visit(RangeBasedForStatementAST *) { return true; }
virtual bool visit(ReferenceAST *) { return true; }
+ virtual bool visit(RequiresExpressionAST *) { return true; }
+ virtual bool visit(RequiresClauseAST *) { return true; }
virtual bool visit(ReturnStatementAST *) { return true; }
virtual bool visit(SimpleDeclarationAST *) { return true; }
virtual bool visit(SimpleNameAST *) { return true; }
@@ -215,6 +220,7 @@ public:
virtual bool visit(TrailingReturnTypeAST *) { return true; }
virtual bool visit(TranslationUnitAST *) { return true; }
virtual bool visit(TryBlockStatementAST *) { return true; }
+ virtual bool visit(TypeConstraintAST *) { return true; }
virtual bool visit(TypeConstructorCallAST *) { return true; }
virtual bool visit(TypeIdAST *) { return true; }
virtual bool visit(TypeidExpressionAST *) { return true; }
@@ -225,6 +231,7 @@ public:
virtual bool visit(UsingAST *) { return true; }
virtual bool visit(UsingDirectiveAST *) { return true; }
virtual bool visit(WhileStatementAST *) { return true; }
+ virtual bool visit(YieldExpressionAST *) { return true; }
virtual void endVisit(AccessDeclarationAST *) {}
virtual void endVisit(AliasDeclarationAST *) {}
@@ -235,6 +242,7 @@ public:
virtual void endVisit(ArrayDeclaratorAST *) {}
virtual void endVisit(ArrayInitializerAST *) {}
virtual void endVisit(AsmDefinitionAST *) {}
+ virtual void endVisit(AwaitExpressionAST *) {}
virtual void endVisit(BaseSpecifierAST *) {}
virtual void endVisit(BinaryExpressionAST *) {}
virtual void endVisit(BoolLiteralAST *) {}
@@ -250,6 +258,7 @@ public:
virtual void endVisit(CompoundExpressionAST *) {}
virtual void endVisit(CompoundLiteralAST *) {}
virtual void endVisit(CompoundStatementAST *) {}
+ virtual void endVisit(ConceptDeclarationAST *) {}
virtual void endVisit(ConditionAST *) {}
virtual void endVisit(ConditionalExpressionAST *) {}
virtual void endVisit(ContinueStatementAST *) {}
@@ -335,6 +344,7 @@ public:
virtual void endVisit(OperatorFunctionIdAST *) {}
virtual void endVisit(ParameterDeclarationAST *) {}
virtual void endVisit(ParameterDeclarationClauseAST *) {}
+ virtual void endVisit(PlaceholderTypeSpecifierAST *) {}
virtual void endVisit(PointerAST *) {}
virtual void endVisit(PointerLiteralAST *) {}
virtual void endVisit(PointerToMemberAST *) {}
@@ -352,6 +362,8 @@ public:
virtual void endVisit(QualifiedNameAST *) {}
virtual void endVisit(RangeBasedForStatementAST *) {}
virtual void endVisit(ReferenceAST *) {}
+ virtual void endVisit(RequiresExpressionAST *) {}
+ virtual void endVisit(RequiresClauseAST *) {}
virtual void endVisit(ReturnStatementAST *) {}
virtual void endVisit(SimpleDeclarationAST *) {}
virtual void endVisit(SimpleNameAST *) {}
@@ -369,6 +381,7 @@ public:
virtual void endVisit(TrailingReturnTypeAST *) {}
virtual void endVisit(TranslationUnitAST *) {}
virtual void endVisit(TryBlockStatementAST *) {}
+ virtual void endVisit(TypeConstraintAST *) {}
virtual void endVisit(TypeConstructorCallAST *) {}
virtual void endVisit(TypeIdAST *) {}
virtual void endVisit(TypeidExpressionAST *) {}
@@ -379,6 +392,7 @@ public:
virtual void endVisit(UsingAST *) {}
virtual void endVisit(UsingDirectiveAST *) {}
virtual void endVisit(WhileStatementAST *) {}
+ virtual void endVisit(YieldExpressionAST *) {}
private:
TranslationUnit *_translationUnit;
diff --git a/src/libs/3rdparty/cplusplus/ASTfwd.h b/src/libs/3rdparty/cplusplus/ASTfwd.h
index 4b31aaf5902..102fda75fb5 100644
--- a/src/libs/3rdparty/cplusplus/ASTfwd.h
+++ b/src/libs/3rdparty/cplusplus/ASTfwd.h
@@ -40,6 +40,7 @@ class ArrayDeclaratorAST;
class ArrayInitializerAST;
class AsmDefinitionAST;
class AttributeSpecifierAST;
+class AwaitExpressionAST;
class BaseSpecifierAST;
class BinaryExpressionAST;
class BoolLiteralAST;
@@ -146,6 +147,7 @@ class OperatorAST;
class OperatorFunctionIdAST;
class ParameterDeclarationAST;
class ParameterDeclarationClauseAST;
+class PlaceholderTypeSpecifierAST;
class PointerAST;
class PointerLiteralAST;
class PointerToMemberAST;
@@ -166,6 +168,8 @@ class QtPropertyDeclarationItemAST;
class QualifiedNameAST;
class RangeBasedForStatementAST;
class ReferenceAST;
+class RequiresClauseAST;
+class RequiresExpressionAST;
class ReturnStatementAST;
class SimpleDeclarationAST;
class SimpleNameAST;
@@ -178,6 +182,7 @@ class StdAttributeSpecifierAST;
class StringLiteralAST;
class SwitchStatementAST;
class TemplateDeclarationAST;
+class ConceptDeclarationAST;
class TemplateIdAST;
class TemplateTypeParameterAST;
class ThisExpressionAST;
@@ -185,6 +190,7 @@ class ThrowExpressionAST;
class TrailingReturnTypeAST;
class TranslationUnitAST;
class TryBlockStatementAST;
+class TypeConstraintAST;
class TypeConstructorCallAST;
class TypeIdAST;
class TypeidExpressionAST;
@@ -195,6 +201,7 @@ class UnaryExpressionAST;
class UsingAST;
class UsingDirectiveAST;
class WhileStatementAST;
+class YieldExpressionAST;
typedef List<ExpressionAST *> ExpressionListAST;
typedef List<DeclarationAST *> DeclarationListAST;
diff --git a/src/libs/3rdparty/cplusplus/Bind.cpp b/src/libs/3rdparty/cplusplus/Bind.cpp
index 7f10c79428c..202c36a51fa 100644
--- a/src/libs/3rdparty/cplusplus/Bind.cpp
+++ b/src/libs/3rdparty/cplusplus/Bind.cpp
@@ -933,6 +933,11 @@ bool Bind::visit(ParameterDeclarationClauseAST *ast)
return false;
}
+bool Bind::visit(RequiresExpressionAST *)
+{
+ return false;
+}
+
void Bind::parameterDeclarationClause(ParameterDeclarationClauseAST *ast, int lparen_token, Function *fun)
{
if (! ast)
@@ -2527,6 +2532,11 @@ bool Bind::visit(TemplateTypeParameterAST *ast)
return false;
}
+bool Bind::visit(TypeConstraintAST *)
+{
+ return false;
+}
+
bool Bind::visit(UsingAST *ast)
{
int sourceLocation = location(ast->name, ast->firstToken());
diff --git a/src/libs/3rdparty/cplusplus/Bind.h b/src/libs/3rdparty/cplusplus/Bind.h
index 3947e57026d..867ed7baaa8 100644
--- a/src/libs/3rdparty/cplusplus/Bind.h
+++ b/src/libs/3rdparty/cplusplus/Bind.h
@@ -128,6 +128,7 @@ protected:
bool visit(NewTypeIdAST *ast) override;
bool visit(OperatorAST *ast) override;
bool visit(ParameterDeclarationClauseAST *ast) override;
+ bool visit(RequiresExpressionAST *ast) override;
bool visit(TranslationUnitAST *ast) override;
bool visit(ObjCProtocolRefsAST *ast) override;
bool visit(ObjCMessageArgumentAST *ast) override;
@@ -229,6 +230,7 @@ protected:
bool visit(TemplateDeclarationAST *ast) override;
bool visit(TypenameTypeParameterAST *ast) override;
bool visit(TemplateTypeParameterAST *ast) override;
+ bool visit(TypeConstraintAST *ast) override;
bool visit(UsingAST *ast) override;
bool visit(UsingDirectiveAST *ast) override;
bool visit(ObjCClassForwardDeclarationAST *ast) override;
diff --git a/src/libs/3rdparty/cplusplus/Keywords.cpp b/src/libs/3rdparty/cplusplus/Keywords.cpp
index 1637333b420..a60ec24aec9 100644
--- a/src/libs/3rdparty/cplusplus/Keywords.cpp
+++ b/src/libs/3rdparty/cplusplus/Keywords.cpp
@@ -603,6 +603,34 @@ static inline int classify7(const char *s, LanguageFeatures features)
}
}
}
+ else if (features.cxx20Enabled && s[0] == 'c') {
+ if (s[1] == 'h') {
+ if (s[2] == 'a') {
+ if (s[3] == 'r') {
+ if (s[4] == '8') {
+ if (s[5] == '_') {
+ if (s[6] == 't') {
+ return T_CHAR8_T;
+ }
+ }
+ }
+ }
+ }
+ }
+ else if (s[1] == 'o') {
+ if (s[2] == 'n') {
+ if (s[3] == 'c') {
+ if (s[4] == 'e') {
+ if (s[5] == 'p') {
+ if (s[6] == 't') {
+ return T_CONCEPT;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
else if (s[0] == 'd') {
if (s[1] == 'e') {
if (s[2] == 'f') {
@@ -847,7 +875,31 @@ static inline int classify8(const char *s, LanguageFeatures features)
}
}
else if (s[1] == 'o') {
- if (s[2] == 'n') {
+ if (features.cxx20Enabled && s[2] == '_') {
+ if (s[3] == 'a') {
+ if (s[4] == 'w') {
+ if (s[5] == 'a') {
+ if (s[6] == 'i') {
+ if (s[7] == 't') {
+ return T_CO_AWAIT;
+ }
+ }
+ }
+ }
+ }
+ else if (s[3] == 'y') {
+ if (s[4] == 'i') {
+ if (s[5] == 'e') {
+ if (s[6] == 'l') {
+ if (s[7] == 'd') {
+ return T_CO_YIELD;
+ }
+ }
+ }
+ }
+ }
+ }
+ else if (s[2] == 'n') {
if (s[3] == 't') {
if (s[4] == 'i') {
if (s[5] == 'n') {
@@ -945,6 +997,19 @@ static inline int classify8(const char *s, LanguageFeatures features)
}
}
}
+ else if (features.cxx20Enabled && s[2] == 'q') {
+ if (s[3] == 'u') {
+ if (s[4] == 'i') {
+ if (s[5] == 'r') {
+ if (s[6] == 'e') {
+ if (s[7] == 's') {
+ return T_REQUIRES;
+ }
+ }
+ }
+ }
+ }
+ }
}
}
else if (features.cxxEnabled && s[0] == 't') {
@@ -1097,13 +1162,35 @@ static inline int classify9(const char *s, LanguageFeatures features)
}
}
}
- else if (features.cxx11Enabled && s[0] == 'c') {
+ else if (s[0] == 'c') {
if (s[1] == 'o') {
- if (s[2] == 'n') {
+ if (features.cxx20Enabled && s[2] == '_') {
+ if (s[3] == 'r') {
+ if (s[4] == 'e') {
+ if (s[5] == 't') {
+ if (s[6] == 'u') {
+ if (s[7] == 'r') {
+ if (s[8] == 'n') {
+ return T_CO_RETURN;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ else if (s[2] == 'n') {
if (s[3] == 's') {
if (s[4] == 't') {
if (s[5] == 'e') {
- if (s[6] == 'x') {
+ if (features.cxx20Enabled && s[6] == 'v') {
+ if (s[7] == 'a') {
+ if (s[8] == 'l') {
+ return T_CONSTEVAL;
+ }
+ }
+ }
+ else if (features.cxx11Enabled && s[6] == 'x') {
if (s[7] == 'p') {
if (s[8] == 'r') {
return T_CONSTEXPR;
@@ -1111,6 +1198,15 @@ static inline int classify9(const char *s, LanguageFeatures features)
}
}
}
+ else if (features.cxx20Enabled && s[5] == 'i') {
+ if (s[6] == 'n') {
+ if (s[7] == 'i') {
+ if (s[8] == 't') {
+ return T_CONSTINIT;
+ }
+ }
+ }
+ }
}
}
}
diff --git a/src/libs/3rdparty/cplusplus/Keywords.kwgen b/src/libs/3rdparty/cplusplus/Keywords.kwgen
index 36300a87766..70deeb648d8 100644
--- a/src/libs/3rdparty/cplusplus/Keywords.kwgen
+++ b/src/libs/3rdparty/cplusplus/Keywords.kwgen
@@ -128,6 +128,16 @@ nullptr
static_assert
thread_local
+%pre-check=features.cxx20Enabled
+char8_t
+concept
+consteval
+constinit
+co_await
+co_return
+co_yield
+requires
+
%pre-check=features.qtKeywordsEnabled
emit
foreach
diff --git a/src/libs/3rdparty/cplusplus/Parser.cpp b/src/libs/3rdparty/cplusplus/Parser.cpp
index cb7bfa3a1b6..f8c737c0978 100644
--- a/src/libs/3rdparty/cplusplus/Parser.cpp
+++ b/src/libs/3rdparty/cplusplus/Parser.cpp
@@ -413,6 +413,7 @@ bool Parser::skipUntilStatement()
case T_BREAK:
case T_CONTINUE:
case T_RETURN:
+ case T_CO_RETURN:
case T_GOTO:
case T_TRY:
case T_CATCH:
@@ -1247,6 +1248,8 @@ bool Parser::parseTemplateDeclaration(DeclarationAST *&node)
ast->less_token = consumeToken();
if (maybeSplitGreaterGreaterToken() || LA() == T_GREATER || parseTemplateParameterList(ast->template_parameter_list))
match(T_GREATER, &ast->greater_token);
+ if (!parseRequiresClauseOpt(ast->requiresClause))
+ return false;
}
while (LA()) {
@@ -1255,6 +1258,8 @@ bool Parser::parseTemplateDeclaration(DeclarationAST *&node)
ast->declaration = nullptr;
if (parseDeclaration(ast->declaration))
break;
+ if (parseConceptDeclaration(ast->declaration))
+ break;
error(start_declaration, "expected a declaration");
rewind(start_declaration + 1);
@@ -1265,6 +1270,205 @@ bool Parser::parseTemplateDeclaration(DeclarationAST *&node)
return true;
}
+bool Parser::parseConceptDeclaration(DeclarationAST *&node)
+{
+ if (!_languageFeatures.cxx20Enabled)
+ return false;
+ if (LA() != T_CONCEPT)
+ return false;
+
+ const auto ast = new (_pool) ConceptDeclarationAST;
+ ast->concept_token = consumeToken();
+ if (!parseName(ast->name))
+ return false;
+ parseAttributeSpecifier(ast->attributes);
+ if (LA() != T_EQUAL)
+ return false;
+ ast->equals_token = consumeToken();
+ if (!parseLogicalOrExpression(ast->constraint))
+ return false;
+ if (LA() != T_SEMICOLON)
+ return false;
+ ast->semicolon_token = consumeToken();
+ node = ast;
+ return true;
+}
+
+bool Parser::parsePlaceholderTypeSpecifier(PlaceholderTypeSpecifierAST *&node)
+{
+ if ((lookAtBuiltinTypeSpecifier() || _translationUnit->tokenAt(_tokenIndex).isKeyword())
+ && (LA() != T_AUTO && LA() != T_DECLTYPE)) {
+ return false;
+ }
+
+ TypeConstraintAST *typeConstraint = nullptr;
+ const int savedCursor = cursor();
+ parseTypeConstraint(typeConstraint);
+ if (LA() != T_AUTO && (LA() != T_DECLTYPE || LA(1) != T_LPAREN || LA(2) != T_AUTO)) {
+ rewind(savedCursor);
+ return false;
+ }
+ const auto spec = new (_pool) PlaceholderTypeSpecifierAST;
+ spec->typeConstraint = typeConstraint;
+ if (LA() == T_DECLTYPE) {
+ spec->declTypetoken = consumeToken();
+ if (LA() != T_LPAREN)
+ return false;
+ spec->lparenToken = consumeToken();
+ if (LA() != T_AUTO)
+ return false;
+ spec->autoToken = consumeToken();
+ if (LA() != T_RPAREN)
+ return false;
+ spec->rparenToken = consumeToken();
+ } else {
+ spec->autoToken = consumeToken();
+ }
+ node = spec;
+ return true;
+}
+
+bool Parser::parseTypeConstraint(TypeConstraintAST *&node)
+{
+ if (!_languageFeatures.cxx20Enabled)
+ return false;
+ NestedNameSpecifierListAST *nestedName = nullptr;
+ parseNestedNameSpecifierOpt(nestedName, true);
+ NameAST *conceptName = nullptr;
+ if (!parseUnqualifiedName(conceptName, false))
+ return false;
+ const auto typeConstraint = new (_pool) TypeConstraintAST;
+ typeConstraint->nestedName = nestedName;
+ typeConstraint->conceptName = conceptName;
+ if (LA() != T_LESS) {
+ node = typeConstraint;
+ return true;
+ }
+ typeConstraint->lessToken = consumeToken();
+ if (LA() != T_GREATER) {
+ if (!parseTemplateArgumentList(typeConstraint->templateArgs))
+ return false;
+ }
+ if (LA() != T_GREATER)
+ return false;
+ typeConstraint->greaterToken = consumeToken();
+ node = typeConstraint;
+ return true;
+}
+
+bool Parser::parseRequirement()
+{
+ if (LA() == T_TYPENAME) { // type-requirement
+ consumeToken();
+ NameAST *name = nullptr;
+ if (!parseName(name, true))
+ return false;
+ if (LA() != T_SEMICOLON)
+ return false;
+ consumeToken();
+ return true;
+ }
+ if (LA() == T_LBRACE) { // compound-requirement
+ consumeToken();
+ ExpressionAST *expr = nullptr;
+ if (!parseExpression(expr))
+ return false;
+ if (LA() != T_RBRACE)
+ return false;
+ consumeToken();
+ if (LA() == T_NOEXCEPT)
+ consumeToken();
+ if (LA() == T_SEMICOLON) {
+ consumeToken();
+ return true;
+ }
+ TypeConstraintAST *typeConstraint = nullptr;
+ if (!parseTypeConstraint(typeConstraint))
+ return false;
+ if (LA() != T_SEMICOLON)
+ return false;
+ consumeToken();
+ return true;
+ }
+ if (LA() == T_REQUIRES) { // nested-requirement
+ consumeToken();
+ ExpressionAST *constraintExpr = nullptr;
+ if (!parseLogicalOrExpression(constraintExpr))
+ return false;
+ if (LA() != T_SEMICOLON)
+ return false;
+ consumeToken();
+ return true;
+ }
+ ExpressionAST *simpleExpr;
+ if (!parseExpression(simpleExpr)) // simple-requirement
+ return false;
+ if (LA() != T_SEMICOLON)
+ return false;
+ consumeToken();
+ return true;
+}
+
+bool Parser::parseRequiresClauseOpt(RequiresClauseAST *&node)
+{
+ if (!_languageFeatures.cxx20Enabled)
+ return true;
+ if (LA() != T_REQUIRES)
+ return true;
+ const auto ast = new (_pool) RequiresClauseAST;
+ ast->requires_token = consumeToken();
+ if (!parsePrimaryExpression(ast->constraint))
+ return false;
+ while (true) {
+ if (LA() != T_PIPE_PIPE && LA() != T_AMPER_AMPER)
+ break;
+ ExpressionAST *next = nullptr;
+ if (!parsePrimaryExpression(next))
+ return false;
+
+ // This won't yield the right precedence, but I don't care.
+ BinaryExpressionAST *expr = new (_pool) BinaryExpressionAST;
+ expr->left_expression = ast->constraint;
+ expr->binary_op_token = consumeToken();
+ expr->right_expression = next;
+ ast->constraint = expr;
+ }
+ node = ast;
+ return true;
+}
+
+bool Parser::parseRequiresExpression(ExpressionAST *&node)
+{
+ if (!_languageFeatures.cxx20Enabled)
+ return false;
+ if (LA() != T_REQUIRES)
+ return false;
+
+ const auto ast = new (_pool) RequiresExpressionAST;
+ ast->requires_token = consumeToken();
+ if (LA() == T_LPAREN) {
+ ast->lparen_token = consumeToken();
+ if (!parseParameterDeclarationClause(ast->parameters))
+ return false;
+ if (LA() != T_RPAREN)
+ return false;
+ ast->rparen_token = consumeToken();
+ }
+ if (LA() != T_LBRACE)
+ return false;
+ ast->lbrace_token = consumeToken();
+ if (!parseRequirement())
+ return false;
+ while (LA() != T_RBRACE) {
+ if (!parseRequirement())
+ return false;
+ }
+ ast->rbrace_token = consumeToken();
+
+ node = ast;
+ return true;
+}
+
bool Parser::parseOperator(OperatorAST *&node) // ### FIXME
{
DEBUG_THIS_RULE();
@@ -1500,6 +1704,14 @@ bool Parser::parseDeclSpecifierSeq(SpecifierListAST *&decl_specifier_seq,
NameAST *named_type_specifier = nullptr;
SpecifierListAST **decl_specifier_seq_ptr = &decl_specifier_seq;
for (;;) {
+ PlaceholderTypeSpecifierAST *placeholderSpec = nullptr;
+ // A simple auto is also technically a placeholder-type-specifier, but for historical
+ // reasons, it is handled further below.
+ if (LA() != T_AUTO && parsePlaceholderTypeSpecifier(placeholderSpec)) {
+ *decl_specifier_seq_ptr = new (_pool) SpecifierListAST(placeholderSpec);
+ decl_specifier_seq_ptr = &(*decl_specifier_seq_ptr)->next;
+ continue;
+ }
if (! noStorageSpecifiers && ! onlySimpleTypeSpecifiers && lookAtStorageClassSpecifier()) {
// storage-class-specifier
SimpleSpecifierAST *spec = new (_pool) SimpleSpecifierAST;
@@ -1550,8 +1762,9 @@ bool Parser::parseDeclSpecifierSeq(SpecifierListAST *&decl_specifier_seq,
}
decl_specifier_seq_ptr = &(*decl_specifier_seq_ptr)->next;
has_type_specifier = true;
- } else
+ } else {
break;
+ }
}
return decl_specifier_seq != nullptr;
@@ -1694,6 +1907,8 @@ bool Parser::hasAuto(SpecifierListAST *decl_specifier_list) const
if (_translationUnit->tokenKind(simpleSpec->specifier_token) == T_AUTO)
return true;
}
+ if (spec->asPlaceholderTypeSpecifier())
+ return true;
}
return false;
}
@@ -2012,8 +2227,8 @@ bool Parser::parseTypenameTypeParameter(DeclarationAST *&node)
bool Parser::parseTemplateTypeParameter(DeclarationAST *&node)
{
DEBUG_THIS_RULE();
+ TemplateTypeParameterAST *ast = new (_pool) TemplateTypeParameterAST;
if (LA() == T_TEMPLATE) {
- TemplateTypeParameterAST *ast = new (_pool) TemplateTypeParameterAST;
ast->template_token = consumeToken();
if (LA() == T_LESS)
ast->less_token = consumeToken();
@@ -2022,20 +2237,21 @@ bool Parser::parseTemplateTypeParameter(DeclarationAST *&node)
ast->greater_token = consumeToken();
if (LA() == T_CLASS)
ast->class_token = consumeToken();
- if (_languageFeatures.cxx11Enabled && LA() == T_DOT_DOT_DOT)
- ast->dot_dot_dot_token = consumeToken();
+ } else if (!parseTypeConstraint(ast->typeConstraint)) {
+ return false;
+ }
+ if (_languageFeatures.cxx11Enabled && LA() == T_DOT_DOT_DOT)
+ ast->dot_dot_dot_token = consumeToken();
- // parse optional name
- parseName(ast->name);
+ // parse optional name
+ parseName(ast->name);
- if (LA() == T_EQUAL) {
- ast->equal_token = consumeToken();
- parseTypeId(ast->type_id);
- }
- node = ast;
- return true;
+ if (LA() == T_EQUAL) {
+ ast->equal_token = consumeToken();
+ parseTypeId(ast->type_id);
}
- return false;
+ node = ast;
+ return true;
}
bool Parser::lookAtTypeParameter()
@@ -2071,10 +2287,9 @@ bool Parser::parseTypeParameter(DeclarationAST *&node)
if (lookAtTypeParameter())
return parseTypenameTypeParameter(node);
- else if (LA() == T_TEMPLATE)
+ if (LA() == T_TEMPLATE)
return parseTemplateTypeParameter(node);
- else
- return false;
+ return parseTemplateTypeParameter(node);
}
bool Parser::parseTypeId(ExpressionAST *&node)
@@ -2819,6 +3034,8 @@ bool Parser::parseInitDeclarator(DeclaratorAST *&node, SpecifierListAST *decl_sp
} else if (node->core_declarator && node->core_declarator->asDecompositionDeclarator()) {
error(cursor(), "structured binding needs initializer");
return false;
+ } else if (!parseRequiresClauseOpt(node->requiresClause)) {
+ return false;
}
return true;
}
@@ -3358,6 +3575,7 @@ bool Parser::parseStatement(StatementAST *&node, bool blockLabeledStatement)
return parseGotoStatement(node);
case T_RETURN:
+ case T_CO_RETURN:
return parseReturnStatement(node);
case T_LBRACE:
@@ -3468,7 +3686,7 @@ bool Parser::parseGotoStatement(StatementAST *&node)
bool Parser::parseReturnStatement(StatementAST *&node)
{
DEBUG_THIS_RULE();
- if (LA() == T_RETURN) {
+ if (LA() == T_RETURN || LA() == T_CO_RETURN) {
ReturnStatementAST *ast = new (_pool) ReturnStatementAST;
ast->return_token = consumeToken();
if (_languageFeatures.cxx11Enabled && LA() == T_LBRACE)
@@ -4731,6 +4949,9 @@ bool Parser::parsePrimaryExpression(ExpressionAST *&node)
case T_AT_SELECTOR:
return parseObjCExpression(node);
+ case T_REQUIRES:
+ return parseRequiresExpression(node);
+
default: {
NameAST *name = nullptr;
if (parseNameId(name)) {
@@ -5436,6 +5657,11 @@ bool Parser::parseUnaryExpression(ExpressionAST *&node)
return parseNoExceptOperatorExpression(node);
}
+ case T_CO_AWAIT:
+ if (!_languageFeatures.cxx20Enabled)
+ break;
+ return parseAwaitExpression(node);
+
default:
break;
} // switch
@@ -5694,6 +5920,8 @@ bool Parser::parseAssignmentExpression(ExpressionAST *&node)
DEBUG_THIS_RULE();
if (LA() == T_THROW)
return parseThrowExpression(node);
+ else if (LA() == T_CO_YIELD)
+ return parseYieldExpression(node);
else
PARSE_EXPRESSION_WITH_OPERATOR_PRECEDENCE(node, Prec::Assignment)
}
@@ -5822,6 +6050,34 @@ bool Parser::parseThrowExpression(ExpressionAST *&node)
return false;
}
+bool Parser::parseYieldExpression(ExpressionAST *&node)
+{
+ DEBUG_THIS_RULE();
+ if (LA() != T_CO_YIELD)
+ return false;
+ const auto ast = new (_pool) YieldExpressionAST;
+ ast->yield_token = consumeToken();
+ if (parseBracedInitList0x(ast->expression) || parseAssignmentExpression(ast->expression)) {
+ node = ast;
+ return true;
+ }
+ return false;
+}
+
+bool Parser::parseAwaitExpression(ExpressionAST *&node)
+{
+ DEBUG_THIS_RULE();
+ if (LA() != T_CO_AWAIT)
+ return false;
+ const auto ast = new (_pool) AwaitExpressionAST;
+ ast->await_token = consumeToken();
+ if (parseCastExpression(ast->castExpression)) {
+ node = ast;
+ return true;
+ }
+ return false;
+}
+
bool Parser::parseNoExceptOperatorExpression(ExpressionAST *&node)
{
DEBUG_THIS_RULE();
@@ -6757,6 +7013,15 @@ bool Parser::parseLambdaExpression(ExpressionAST *&node)
if (parseLambdaIntroducer(lambda_introducer)) {
LambdaExpressionAST *ast = new (_pool) LambdaExpressionAST;
ast->lambda_introducer = lambda_introducer;
+ if (_languageFeatures.cxx20Enabled && LA() == T_LESS) {
+ consumeToken();
+ parseTemplateParameterList(ast->templateParameters);
+ if (LA() != T_GREATER)
+ return false;
+ consumeToken();
+ parseRequiresClauseOpt(ast->requiresClause);
+ }
+ parseOptionalAttributeSpecifierSequence(ast->attributes);
parseLambdaDeclarator(ast->lambda_declarator);
parseCompoundStatement(ast->statement);
node = ast;
@@ -6781,7 +7046,9 @@ bool Parser::parseLambdaIntroducer(LambdaIntroducerAST *&node)
if (LA() == T_RBRACKET) {
ast->rbracket_token = consumeToken();
- if (LA() == T_LPAREN || LA() == T_LBRACE) {
+ // FIXME: Attributes are also allowed ...
+ if (LA() == T_LPAREN || LA() == T_LBRACE
+ || (_languageFeatures.cxx20Enabled && LA() == T_LESS)) {
node = ast;
return true;
}
@@ -6897,6 +7164,7 @@ bool Parser::parseLambdaDeclarator(LambdaDeclaratorAST *&node)
parseExceptionSpecification(ast->exception_specification);
parseTrailingReturnType(ast->trailing_return_type);
+ parseRequiresClauseOpt(ast->requiresClause);
node = ast;
return true;
diff --git a/src/libs/3rdparty/cplusplus/Parser.h b/src/libs/3rdparty/cplusplus/Parser.h
index ba101ccd46e..e34a4ff5223 100644
--- a/src/libs/3rdparty/cplusplus/Parser.h
+++ b/src/libs/3rdparty/cplusplus/Parser.h
@@ -143,9 +143,17 @@ public:
bool parseTemplateArgument(ExpressionAST *&node);
bool parseTemplateArgumentList(ExpressionListAST *&node);
bool parseTemplateDeclaration(DeclarationAST *&node);
+ bool parseConceptDeclaration(DeclarationAST *&node);
+ bool parsePlaceholderTypeSpecifier(PlaceholderTypeSpecifierAST *&node);
+ bool parseTypeConstraint(TypeConstraintAST *&node);
+ bool parseRequirement();
+ bool parseRequiresClauseOpt(RequiresClauseAST *&node);
+ bool parseRequiresExpression(ExpressionAST *&node);
bool parseTemplateParameter(DeclarationAST *&node);
bool parseTemplateParameterList(DeclarationListAST *&node);
bool parseThrowExpression(ExpressionAST *&node);
+ bool parseYieldExpression(ExpressionAST *&node);
+ bool parseAwaitExpression(ExpressionAST *&node);
bool parseNoExceptOperatorExpression(ExpressionAST *&node);
bool parseTryBlockStatement(StatementAST *&node, CtorInitializerAST **placeholder);
bool parseCatchClause(CatchClauseListAST *&node);
diff --git a/src/libs/3rdparty/cplusplus/Symbol.h b/src/libs/3rdparty/cplusplus/Symbol.h
index 497226dcc1a..dc07ced907d 100644
--- a/src/libs/3rdparty/cplusplus/Symbol.h
+++ b/src/libs/3rdparty/cplusplus/Symbol.h
@@ -66,10 +66,10 @@ public:
/// Returns this Symbol's source location.
int sourceLocation() const { return _sourceLocation; }
- /// \returns this Symbol's line number. The line number is 1-based.
+ /// Returns this Symbol's line number. The line number is 1-based.
int line() const { return _line; }
- /// \returns this Symbol's column number. The column number is 1-based.
+ /// Returns this Symbol's column number. The column number is 1-based.
int column() const { return _column; }
/// Returns this Symbol's file name.
diff --git a/src/libs/3rdparty/cplusplus/Token.cpp b/src/libs/3rdparty/cplusplus/Token.cpp
index 31fdb2036c2..2e8a0ce7f24 100644
--- a/src/libs/3rdparty/cplusplus/Token.cpp
+++ b/src/libs/3rdparty/cplusplus/Token.cpp
@@ -120,9 +120,15 @@ const char *token_names[] = {
("case"),
("catch"),
("class"),
+ ("co_await"),
+ ("co_return"),
+ ("co_yield"),
+ ("concept"),
("const"),
("const_cast"),
+ ("consteval"),
("constexpr"),
+ ("constinit"),
("continue"),
("decltype"),
("default"),
@@ -151,6 +157,7 @@ const char *token_names[] = {
("public"),
("register"),
("reinterpret_cast"),
+ ("requires"),
("return"),
("sizeof"),
("static"),
@@ -210,6 +217,7 @@ const char *token_names[] = {
// Primitive types
("bool"),
("char"),
+ ("char8_t"),
("char16_t"),
("char32_t"),
("double"),
diff --git a/src/libs/3rdparty/cplusplus/Token.h b/src/libs/3rdparty/cplusplus/Token.h
index 3a04a44a86a..204096893ad 100644
--- a/src/libs/3rdparty/cplusplus/Token.h
+++ b/src/libs/3rdparty/cplusplus/Token.h
@@ -130,9 +130,15 @@ enum Kind {
T_CASE,
T_CATCH,
T_CLASS,
+ T_CO_AWAIT,
+ T_CO_RETURN,
+ T_CO_YIELD,
+ T_CONCEPT,
T_CONST,
T_CONST_CAST,
+ T_CONSTEVAL,
T_CONSTEXPR,
+ T_CONSTINIT,
T_CONTINUE,
T_DECLTYPE,
T_DEFAULT,
@@ -161,6 +167,7 @@ enum Kind {
T_PUBLIC,
T_REGISTER,
T_REINTERPRET_CAST,
+ T_REQUIRES,
T_RETURN,
T_SIZEOF,
T_STATIC,
@@ -223,6 +230,7 @@ enum Kind {
T_FIRST_PRIMITIVE,
T_BOOL = T_FIRST_PRIMITIVE,
T_CHAR,
+ T_CHAR8_T,
T_CHAR16_T,
T_CHAR32_T,
T_DOUBLE,
diff --git a/src/libs/3rdparty/libptyqt/.clang-format b/src/libs/3rdparty/libptyqt/.clang-format
new file mode 100644
index 00000000000..b861ff7a951
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/.clang-format
@@ -0,0 +1 @@
+{ "DisableFormat" : true } \ No newline at end of file
diff --git a/src/libs/3rdparty/libptyqt/CMakeLists.txt b/src/libs/3rdparty/libptyqt/CMakeLists.txt
new file mode 100644
index 00000000000..c6e8b745737
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/CMakeLists.txt
@@ -0,0 +1,28 @@
+set(SOURCES
+ iptyprocess.h
+ ptyqt.cpp ptyqt.h
+)
+
+if (WIN32)
+ list(APPEND SOURCES
+ winptyprocess.cpp winptyprocess.h
+ conptyprocess.cpp conptyprocess.h
+ )
+else()
+ list(APPEND SOURCES unixptyprocess.cpp unixptyprocess.h)
+endif()
+
+add_library(ptyqt STATIC ${SOURCES})
+target_link_libraries(ptyqt PUBLIC Qt::Core)
+
+if (WIN32)
+ target_link_libraries(ptyqt PRIVATE winpty Qt::Network)
+ #target_compile_definitions(ptyqt PRIVATE PTYQT_DEBUG)
+endif()
+
+set_target_properties(ptyqt
+ PROPERTIES
+ INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR}
+ QT_COMPILE_OPTIONS_DISABLE_WARNINGS ON
+ POSITION_INDEPENDENT_CODE ON
+)
diff --git a/src/libs/3rdparty/libptyqt/LICENSE b/src/libs/3rdparty/libptyqt/LICENSE
new file mode 100644
index 00000000000..73996c7c90e
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Vitaly Petrov, [email protected]
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE. \ No newline at end of file
diff --git a/src/libs/3rdparty/libptyqt/conptyprocess.cpp b/src/libs/3rdparty/libptyqt/conptyprocess.cpp
new file mode 100644
index 00000000000..687d3116f5a
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/conptyprocess.cpp
@@ -0,0 +1,341 @@
+#include "conptyprocess.h"
+#include <QFile>
+#include <QFileInfo>
+#include <QThread>
+#include <sstream>
+#include <QTimer>
+#include <QMutexLocker>
+#include <QCoreApplication>
+#include <QWinEventNotifier>
+
+#include <qt_windows.h>
+
+#define READ_INTERVAL_MSEC 500
+
+HRESULT ConPtyProcess::createPseudoConsoleAndPipes(HPCON* phPC, HANDLE* phPipeIn, HANDLE* phPipeOut, qint16 cols, qint16 rows)
+{
+ HRESULT hr{ E_UNEXPECTED };
+ HANDLE hPipePTYIn{ INVALID_HANDLE_VALUE };
+ HANDLE hPipePTYOut{ INVALID_HANDLE_VALUE };
+
+ // Create the pipes to which the ConPTY will connect
+ if (CreatePipe(&hPipePTYIn, phPipeOut, NULL, 0) &&
+ CreatePipe(phPipeIn, &hPipePTYOut, NULL, 0))
+ {
+ // Create the Pseudo Console of the required size, attached to the PTY-end of the pipes
+ hr = m_winContext.createPseudoConsole({cols, rows}, hPipePTYIn, hPipePTYOut, 0, phPC);
+
+ // Note: We can close the handles to the PTY-end of the pipes here
+ // because the handles are dup'ed into the ConHost and will be released
+ // when the ConPTY is destroyed.
+ if (INVALID_HANDLE_VALUE != hPipePTYOut) CloseHandle(hPipePTYOut);
+ if (INVALID_HANDLE_VALUE != hPipePTYIn) CloseHandle(hPipePTYIn);
+ }
+
+ return hr;
+}
+
+// Initializes the specified startup info struct with the required properties and
+// updates its thread attribute list with the specified ConPTY handle
+HRESULT ConPtyProcess::initializeStartupInfoAttachedToPseudoConsole(STARTUPINFOEX* pStartupInfo, HPCON hPC)
+{
+ HRESULT hr{ E_UNEXPECTED };
+
+ if (pStartupInfo)
+ {
+ SIZE_T attrListSize{};
+
+ pStartupInfo->StartupInfo.hStdInput = m_hPipeIn;
+ pStartupInfo->StartupInfo.hStdError = m_hPipeOut;
+ pStartupInfo->StartupInfo.hStdOutput = m_hPipeOut;
+ pStartupInfo->StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
+
+ pStartupInfo->StartupInfo.cb = sizeof(STARTUPINFOEX);
+
+ // Get the size of the thread attribute list.
+ InitializeProcThreadAttributeList(NULL, 1, 0, &attrListSize);
+
+ // Allocate a thread attribute list of the correct size
+ pStartupInfo->lpAttributeList = reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(
+ HeapAlloc(GetProcessHeap(), 0, attrListSize));
+
+ // Initialize thread attribute list
+ if (pStartupInfo->lpAttributeList
+ && InitializeProcThreadAttributeList(pStartupInfo->lpAttributeList, 1, 0, &attrListSize))
+ {
+ // Set Pseudo Console attribute
+ hr = UpdateProcThreadAttribute(
+ pStartupInfo->lpAttributeList,
+ 0,
+ PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
+ hPC,
+ sizeof(HPCON),
+ NULL,
+ NULL)
+ ? S_OK
+ : HRESULT_FROM_WIN32(GetLastError());
+ }
+ else
+ {
+ hr = HRESULT_FROM_WIN32(GetLastError());
+ }
+ }
+ return hr;
+}
+
+ConPtyProcess::ConPtyProcess()
+ : IPtyProcess()
+ , m_ptyHandler { INVALID_HANDLE_VALUE }
+ , m_hPipeIn { INVALID_HANDLE_VALUE }
+ , m_hPipeOut { INVALID_HANDLE_VALUE }
+ , m_readThread(nullptr)
+{
+
+}
+
+ConPtyProcess::~ConPtyProcess()
+{
+ kill();
+}
+
+bool ConPtyProcess::startProcess(const QString &executable,
+ const QStringList &arguments,
+ const QString &workingDir,
+ QStringList environment,
+ qint16 cols,
+ qint16 rows)
+{
+ if (!isAvailable()) {
+ m_lastError = m_winContext.lastError();
+ return false;
+ }
+
+ //already running
+ if (m_ptyHandler != INVALID_HANDLE_VALUE)
+ return false;
+
+ QFileInfo fi(executable);
+ if (fi.isRelative() || !QFile::exists(executable)) {
+ //todo add auto-find executable in PATH env var
+ m_lastError = QString("ConPty Error: shell file path '%1' must be absolute").arg(executable);
+ return false;
+ }
+
+ m_shellPath = executable;
+ m_shellPath.replace('/', '\\');
+ m_size = QPair<qint16, qint16>(cols, rows);
+
+ //env
+ const QString env = environment.join(QChar(QChar::Null)) + QChar(QChar::Null);
+ LPVOID envPtr = env.isEmpty() ? nullptr : (LPVOID) env.utf16();
+
+ LPCWSTR workingDirPtr = workingDir.isEmpty() ? nullptr : (LPCWSTR) workingDir.utf16();
+
+ QStringList exeAndArgs = arguments;
+ exeAndArgs.prepend(m_shellPath);
+ std::wstring cmdArg{(LPCWSTR) (exeAndArgs.join(QLatin1String(" ")).utf16())};
+
+ HRESULT hr{E_UNEXPECTED};
+
+ // Create the Pseudo Console and pipes to it
+ hr = createPseudoConsoleAndPipes(&m_ptyHandler, &m_hPipeIn, &m_hPipeOut, cols, rows);
+
+ if (S_OK != hr) {
+ m_lastError = QString("ConPty Error: CreatePseudoConsoleAndPipes fail");
+ return false;
+ }
+
+ // Initialize the necessary startup info struct
+ if (S_OK != initializeStartupInfoAttachedToPseudoConsole(&m_shellStartupInfo, m_ptyHandler)) {
+ m_lastError = QString("ConPty Error: InitializeStartupInfoAttachedToPseudoConsole fail");
+ return false;
+ }
+
+ hr = CreateProcessW(nullptr, // No module name - use Command Line
+ cmdArg.data(), // Command Line
+ nullptr, // Process handle not inheritable
+ nullptr, // Thread handle not inheritable
+ FALSE, // Inherit handles
+ EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT, // Creation flags
+ envPtr, // Environment block
+ workingDirPtr, // Use parent's starting directory
+ &m_shellStartupInfo.StartupInfo, // Pointer to STARTUPINFO
+ &m_shellProcessInformation) // Pointer to PROCESS_INFORMATION
+ ? S_OK
+ : GetLastError();
+
+ if (S_OK != hr) {
+ m_lastError = QString("ConPty Error: Cannot create process -> %1").arg(hr);
+ return false;
+ }
+
+ m_pid = m_shellProcessInformation.dwProcessId;
+
+ // Notify when the shell process has been terminated
+ m_shellCloseWaitNotifier = new QWinEventNotifier(m_shellProcessInformation.hProcess, notifier());
+ QObject::connect(m_shellCloseWaitNotifier,
+ &QWinEventNotifier::activated,
+ notifier(),
+ [this](HANDLE hEvent) {
+ DWORD exitCode = 0;
+ GetExitCodeProcess(hEvent, &exitCode);
+ m_exitCode = exitCode;
+ // Do not respawn if the object is about to be destructed
+ if (!m_aboutToDestruct)
+ emit notifier()->aboutToClose();
+ m_shellCloseWaitNotifier->setEnabled(false);
+ });
+
+ //this code runned in separate thread
+ m_readThread = QThread::create([this]() {
+ //buffers
+ const DWORD BUFF_SIZE{1024};
+ char szBuffer[BUFF_SIZE]{};
+
+ forever {
+ DWORD dwBytesRead{};
+
+ // Read from the pipe
+ BOOL result = ReadFile(m_hPipeIn, szBuffer, BUFF_SIZE, &dwBytesRead, NULL);
+
+ const bool needMoreData = !result && GetLastError() == ERROR_MORE_DATA;
+ if (result || needMoreData) {
+ QMutexLocker locker(&m_bufferMutex);
+ m_buffer.m_readBuffer.append(szBuffer, dwBytesRead);
+ m_buffer.emitReadyRead();
+ }
+
+ const bool brokenPipe = !result && GetLastError() == ERROR_BROKEN_PIPE;
+ if (QThread::currentThread()->isInterruptionRequested() || brokenPipe)
+ break;
+ }
+
+ CancelIoEx(m_hPipeIn, nullptr);
+ });
+
+ //start read thread
+ m_readThread->start();
+
+ return true;
+}
+
+bool ConPtyProcess::resize(qint16 cols, qint16 rows)
+{
+ if (m_ptyHandler == nullptr)
+ {
+ return false;
+ }
+
+ bool res = SUCCEEDED(m_winContext.resizePseudoConsole(m_ptyHandler, {cols, rows}));
+
+ if (res)
+ {
+ m_size = QPair<qint16, qint16>(cols, rows);
+ }
+
+ return res;
+
+ return true;
+}
+
+bool ConPtyProcess::kill()
+{
+ bool exitCode = false;
+
+ if (m_ptyHandler != INVALID_HANDLE_VALUE) {
+ m_aboutToDestruct = true;
+
+ // Close ConPTY - this will terminate client process if running
+ m_winContext.closePseudoConsole(m_ptyHandler);
+
+ // Clean-up the pipes
+ if (INVALID_HANDLE_VALUE != m_hPipeOut)
+ CloseHandle(m_hPipeOut);
+ if (INVALID_HANDLE_VALUE != m_hPipeIn)
+ CloseHandle(m_hPipeIn);
+
+ if (m_readThread) {
+ m_readThread->requestInterruption();
+ if (!m_readThread->wait(1000))
+ m_readThread->terminate();
+ m_readThread->deleteLater();
+ m_readThread = nullptr;
+ }
+
+ delete m_shellCloseWaitNotifier;
+ m_shellCloseWaitNotifier = nullptr;
+
+ m_pid = 0;
+ m_ptyHandler = INVALID_HANDLE_VALUE;
+ m_hPipeIn = INVALID_HANDLE_VALUE;
+ m_hPipeOut = INVALID_HANDLE_VALUE;
+
+ CloseHandle(m_shellProcessInformation.hThread);
+ CloseHandle(m_shellProcessInformation.hProcess);
+
+ // Cleanup attribute list
+ if (m_shellStartupInfo.lpAttributeList) {
+ DeleteProcThreadAttributeList(m_shellStartupInfo.lpAttributeList);
+ HeapFree(GetProcessHeap(), 0, m_shellStartupInfo.lpAttributeList);
+ }
+
+ exitCode = true;
+ }
+
+ return exitCode;
+}
+
+IPtyProcess::PtyType ConPtyProcess::type()
+{
+ return PtyType::ConPty;
+}
+
+QString ConPtyProcess::dumpDebugInfo()
+{
+#ifdef PTYQT_DEBUG
+ return QString("PID: %1, Type: %2, Cols: %3, Rows: %4")
+ .arg(m_pid).arg(type())
+ .arg(m_size.first).arg(m_size.second);
+#else
+ return QString("Nothing...");
+#endif
+}
+
+QIODevice *ConPtyProcess::notifier()
+{
+ return &m_buffer;
+}
+
+QByteArray ConPtyProcess::readAll()
+{
+ QByteArray result;
+ {
+ QMutexLocker locker(&m_bufferMutex);
+ result.swap(m_buffer.m_readBuffer);
+ }
+ return result;
+}
+
+qint64 ConPtyProcess::write(const QByteArray &byteArray)
+{
+ DWORD dwBytesWritten{};
+ WriteFile(m_hPipeOut, byteArray.data(), byteArray.size(), &dwBytesWritten, NULL);
+ return dwBytesWritten;
+}
+
+bool ConPtyProcess::isAvailable()
+{
+#ifdef TOO_OLD_WINSDK
+ return false; //very importnant! ConPty can be built, but it doesn't work if built with old sdk and Win10 < 1903
+#endif
+
+ qint32 buildNumber = QSysInfo::kernelVersion().split(".").last().toInt();
+ if (buildNumber < CONPTY_MINIMAL_WINDOWS_VERSION)
+ return false;
+ return m_winContext.init();
+}
+
+void ConPtyProcess::moveToThread(QThread *targetThread)
+{
+ //nothing for now...
+}
diff --git a/src/libs/3rdparty/libptyqt/conptyprocess.h b/src/libs/3rdparty/libptyqt/conptyprocess.h
new file mode 100644
index 00000000000..ac940489816
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/conptyprocess.h
@@ -0,0 +1,169 @@
+#ifndef CONPTYPROCESS_H
+#define CONPTYPROCESS_H
+
+#include "iptyprocess.h"
+#include <windows.h>
+#include <process.h>
+#include <stdio.h>
+
+#include <QIODevice>
+#include <QLibrary>
+#include <QMutex>
+#include <QTimer>
+#include <QThread>
+
+//Taken from the RS5 Windows SDK, but redefined here in case we're targeting <= 17733
+//Just for compile, ConPty doesn't work with Windows SDK < 17733
+#ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
+#define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE \
+ ProcThreadAttributeValue(22, FALSE, TRUE, FALSE)
+
+typedef VOID* HPCON;
+
+#define TOO_OLD_WINSDK
+#endif
+
+class QWinEventNotifier;
+
+template <typename T>
+std::vector<T> vectorFromString(const std::basic_string<T> &str)
+{
+ return std::vector<T>(str.begin(), str.end());
+}
+
+//ConPTY available only on Windows 10 releazed after 1903 (19H1) Windows release
+class WindowsContext
+{
+public:
+ typedef HRESULT (*CreatePseudoConsolePtr)(
+ COORD size, // ConPty Dimensions
+ HANDLE hInput, // ConPty Input
+ HANDLE hOutput, // ConPty Output
+ DWORD dwFlags, // ConPty Flags
+ HPCON* phPC); // ConPty Reference
+
+ typedef HRESULT (*ResizePseudoConsolePtr)(HPCON hPC, COORD size);
+
+ typedef VOID (*ClosePseudoConsolePtr)(HPCON hPC);
+
+ WindowsContext()
+ : createPseudoConsole(nullptr)
+ , resizePseudoConsole(nullptr)
+ , closePseudoConsole(nullptr)
+ {
+
+ }
+
+ bool init()
+ {
+ //already initialized
+ if (createPseudoConsole)
+ return true;
+
+ //try to load symbols from library
+ //if it fails -> we can't use ConPty API
+ HANDLE kernel32Handle = LoadLibraryExW(L"kernel32.dll", 0, 0);
+
+ if (kernel32Handle != nullptr)
+ {
+ createPseudoConsole = (CreatePseudoConsolePtr)GetProcAddress((HMODULE)kernel32Handle, "CreatePseudoConsole");
+ resizePseudoConsole = (ResizePseudoConsolePtr)GetProcAddress((HMODULE)kernel32Handle, "ResizePseudoConsole");
+ closePseudoConsole = (ClosePseudoConsolePtr)GetProcAddress((HMODULE)kernel32Handle, "ClosePseudoConsole");
+ if (createPseudoConsole == NULL || resizePseudoConsole == NULL || closePseudoConsole == NULL)
+ {
+ m_lastError = QString("WindowsContext/ConPty error: %1").arg("Invalid on load API functions");
+ return false;
+ }
+ }
+ else
+ {
+ m_lastError = QString("WindowsContext/ConPty error: %1").arg("Unable to load kernel32");
+ return false;
+ }
+
+ return true;
+ }
+
+ QString lastError()
+ {
+ return m_lastError;
+ }
+
+public:
+ //vars
+ CreatePseudoConsolePtr createPseudoConsole;
+ ResizePseudoConsolePtr resizePseudoConsole;
+ ClosePseudoConsolePtr closePseudoConsole;
+
+private:
+ QString m_lastError;
+};
+
+class PtyBuffer : public QIODevice
+{
+ friend class ConPtyProcess;
+ Q_OBJECT
+public:
+
+ //just empty realization, we need only 'readyRead' signal of this class
+ qint64 readData(char *data, qint64 maxlen) { return 0; }
+ qint64 writeData(const char *data, qint64 len) { return 0; }
+
+ bool isSequential() const { return true; }
+ qint64 bytesAvailable() const { return m_readBuffer.size(); }
+ qint64 size() const { return m_readBuffer.size(); }
+
+ void emitReadyRead()
+ {
+ //for emit signal from PtyBuffer own thread
+ QTimer::singleShot(1, this, [this]()
+ {
+ emit readyRead();
+ });
+ }
+
+private:
+ QByteArray m_readBuffer;
+};
+
+class ConPtyProcess : public IPtyProcess
+{
+public:
+ ConPtyProcess();
+ ~ConPtyProcess();
+
+ bool startProcess(const QString &executable,
+ const QStringList &arguments,
+ const QString &workingDir,
+ QStringList environment,
+ qint16 cols,
+ qint16 rows);
+ bool resize(qint16 cols, qint16 rows);
+ bool kill();
+ PtyType type();
+ QString dumpDebugInfo();
+ virtual QIODevice *notifier();
+ virtual QByteArray readAll();
+ virtual qint64 write(const QByteArray &byteArray);
+ bool isAvailable();
+ void moveToThread(QThread *targetThread);
+
+private:
+ HRESULT createPseudoConsoleAndPipes(HPCON* phPC, HANDLE* phPipeIn, HANDLE* phPipeOut, qint16 cols, qint16 rows);
+ HRESULT initializeStartupInfoAttachedToPseudoConsole(STARTUPINFOEX* pStartupInfo, HPCON hPC);
+
+private:
+ WindowsContext m_winContext;
+ HPCON m_ptyHandler{INVALID_HANDLE_VALUE};
+ HANDLE m_hPipeIn{INVALID_HANDLE_VALUE}, m_hPipeOut{INVALID_HANDLE_VALUE};
+
+ QThread *m_readThread{nullptr};
+ QMutex m_bufferMutex;
+ PtyBuffer m_buffer;
+ bool m_aboutToDestruct{false};
+ PROCESS_INFORMATION m_shellProcessInformation{};
+ QWinEventNotifier *m_shellCloseWaitNotifier{nullptr};
+ STARTUPINFOEX m_shellStartupInfo{};
+};
+
+#endif // CONPTYPROCESS_H
diff --git a/src/libs/3rdparty/libptyqt/iptyprocess.h b/src/libs/3rdparty/libptyqt/iptyprocess.h
new file mode 100644
index 00000000000..3d974908c86
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/iptyprocess.h
@@ -0,0 +1,56 @@
+#ifndef IPTYPROCESS_H
+#define IPTYPROCESS_H
+
+#include <QDebug>
+#include <QString>
+#include <QThread>
+
+#define CONPTY_MINIMAL_WINDOWS_VERSION 18309
+
+class IPtyProcess
+{
+public:
+ enum PtyType { UnixPty = 0, WinPty = 1, ConPty = 2, AutoPty = 3 };
+
+ IPtyProcess() = default;
+ IPtyProcess(const IPtyProcess &) = delete;
+ IPtyProcess &operator=(const IPtyProcess &) = delete;
+
+ virtual ~IPtyProcess() {}
+
+ virtual bool startProcess(const QString &executable,
+ const QStringList &arguments,
+ const QString &workingDir,
+ QStringList environment,
+ qint16 cols,
+ qint16 rows)
+ = 0;
+ virtual bool resize(qint16 cols, qint16 rows) = 0;
+ virtual bool kill() = 0;
+ virtual PtyType type() = 0;
+ virtual QString dumpDebugInfo() = 0;
+ virtual QIODevice *notifier() = 0;
+ virtual QByteArray readAll() = 0;
+ virtual qint64 write(const QByteArray &byteArray) = 0;
+ virtual bool isAvailable() = 0;
+ virtual void moveToThread(QThread *targetThread) = 0;
+ qint64 pid() { return m_pid; }
+ QPair<qint16, qint16> size() { return m_size; }
+ const QString lastError() { return m_lastError; }
+ int exitCode() { return m_exitCode; }
+ bool toggleTrace()
+ {
+ m_trace = !m_trace;
+ return m_trace;
+ }
+
+protected:
+ QString m_shellPath;
+ QString m_lastError;
+ qint64 m_pid{0};
+ int m_exitCode{0};
+ QPair<qint16, qint16> m_size; //cols / rows
+ bool m_trace{false};
+};
+
+#endif // IPTYPROCESS_H
diff --git a/src/libs/3rdparty/libptyqt/ptyqt.cpp b/src/libs/3rdparty/libptyqt/ptyqt.cpp
new file mode 100644
index 00000000000..b3e7aa1b164
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/ptyqt.cpp
@@ -0,0 +1,45 @@
+#include "ptyqt.h"
+#include <utility>
+
+#ifdef Q_OS_WIN
+#include "winptyprocess.h"
+#include "conptyprocess.h"
+#endif
+
+#ifdef Q_OS_UNIX
+#include "unixptyprocess.h"
+#endif
+
+
+IPtyProcess *PtyQt::createPtyProcess(IPtyProcess::PtyType ptyType)
+{
+ switch (ptyType)
+ {
+#ifdef Q_OS_WIN
+ case IPtyProcess::PtyType::WinPty:
+ return new WinPtyProcess();
+ break;
+ case IPtyProcess::PtyType::ConPty:
+ return new ConPtyProcess();
+ break;
+#endif
+#ifdef Q_OS_UNIX
+ case IPtyProcess::PtyType::UnixPty:
+ return new UnixPtyProcess();
+ break;
+#endif
+ case IPtyProcess::PtyType::AutoPty:
+ default:
+ break;
+ }
+
+#ifdef Q_OS_WIN
+ if (ConPtyProcess().isAvailable() && qgetenv("QTC_USE_WINPTY").isEmpty())
+ return new ConPtyProcess();
+ else
+ return new WinPtyProcess();
+#endif
+#ifdef Q_OS_UNIX
+ return new UnixPtyProcess();
+#endif
+}
diff --git a/src/libs/3rdparty/libptyqt/ptyqt.h b/src/libs/3rdparty/libptyqt/ptyqt.h
new file mode 100644
index 00000000000..23b80d346bb
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/ptyqt.h
@@ -0,0 +1,12 @@
+#ifndef PTYQT_H
+#define PTYQT_H
+
+#include "iptyprocess.h"
+
+class PtyQt
+{
+public:
+ static IPtyProcess *createPtyProcess(IPtyProcess::PtyType ptyType);
+};
+
+#endif // PTYQT_H
diff --git a/src/libs/3rdparty/libptyqt/ptyqt.qbs b/src/libs/3rdparty/libptyqt/ptyqt.qbs
new file mode 100644
index 00000000000..7ff4da9f560
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/ptyqt.qbs
@@ -0,0 +1,45 @@
+import qbs
+
+Project {
+ name: "ptyqt"
+
+ QtcLibrary {
+ Depends { name: "Qt.core" }
+ Depends { name: "Qt.network"; condition: qbs.targetOS.contains("windows") }
+ Depends { name: "winpty"; condition: qbs.targetOS.contains("windows") }
+
+ type: "staticlibrary"
+
+ files: [
+ "iptyprocess.h",
+ "ptyqt.cpp",
+ "ptyqt.h",
+ ]
+
+ Group {
+ name: "ptyqt UNIX files"
+ condition: qbs.targetOS.contains("unix")
+ files: [
+ "unixptyprocess.cpp",
+ "unixptyprocess.h",
+ ]
+ }
+
+ Group {
+ name: "ptyqt Windows files"
+ condition: qbs.targetOS.contains("windows")
+ files: [
+ "conptyprocess.cpp",
+ "conptyprocess.h",
+ "winptyprocess.cpp",
+ "winptyprocess.h",
+ ]
+ }
+
+ Export {
+ Depends { name: "cpp" }
+ Depends { name: "winpty"; condition: qbs.targetOS.contains("windows") }
+ cpp.includePaths: base.concat(exportingProduct.sourceDirectory)
+ }
+ }
+}
diff --git a/src/libs/3rdparty/libptyqt/unixptyprocess.cpp b/src/libs/3rdparty/libptyqt/unixptyprocess.cpp
new file mode 100644
index 00000000000..8c018daf8c0
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/unixptyprocess.cpp
@@ -0,0 +1,374 @@
+#include "unixptyprocess.h"
+#include <QStandardPaths>
+
+#include <errno.h>
+#include <termios.h>
+#if !defined(Q_OS_ANDROID) && !defined(Q_OS_FREEBSD)
+#include <utmpx.h>
+#endif
+#include <fcntl.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <QCoreApplication>
+#include <QFileInfo>
+
+UnixPtyProcess::UnixPtyProcess()
+ : IPtyProcess()
+ , m_readMasterNotify(0)
+{
+ m_shellProcess.setWorkingDirectory(
+ QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
+}
+
+UnixPtyProcess::~UnixPtyProcess()
+{
+ kill();
+}
+
+bool UnixPtyProcess::startProcess(const QString &shellPath,
+ const QStringList &arguments,
+ const QString &workingDir,
+ QStringList environment,
+ qint16 cols,
+ qint16 rows)
+{
+ if (!isAvailable()) {
+ m_lastError = QString("UnixPty Error: unavailable");
+ return false;
+ }
+
+ if (m_shellProcess.state() == QProcess::Running)
+ return false;
+
+ QFileInfo fi(shellPath);
+ if (fi.isRelative() || !QFile::exists(shellPath)) {
+ //todo add auto-find executable in PATH env var
+ m_lastError = QString("UnixPty Error: shell file path must be absolute");
+ return false;
+ }
+
+ m_shellPath = shellPath;
+ m_size = QPair<qint16, qint16>(cols, rows);
+
+ int rc = 0;
+
+ m_shellProcess.m_handleMaster = ::posix_openpt(O_RDWR | O_NOCTTY);
+ if (m_shellProcess.m_handleMaster <= 0) {
+ m_lastError = QString("UnixPty Error: unable to open master -> %1").arg(QLatin1String(strerror(errno)));
+ kill();
+ return false;
+ }
+
+ m_shellProcess.m_handleSlaveName = QLatin1String(ptsname(m_shellProcess.m_handleMaster));
+ if (m_shellProcess.m_handleSlaveName.isEmpty()) {
+ m_lastError = QString("UnixPty Error: unable to get slave name -> %1").arg(QLatin1String(strerror(errno)));
+ kill();
+ return false;
+ }
+
+ rc = grantpt(m_shellProcess.m_handleMaster);
+ if (rc != 0) {
+ m_lastError
+ = QString("UnixPty Error: unable to change perms for slave -> %1").arg(QLatin1String(strerror(errno)));
+ kill();
+ return false;
+ }
+
+ rc = unlockpt(m_shellProcess.m_handleMaster);
+ if (rc != 0) {
+ m_lastError = QString("UnixPty Error: unable to unlock slave -> %1").arg(QLatin1String(strerror(errno)));
+ kill();
+ return false;
+ }
+
+ m_shellProcess.m_handleSlave = ::open(m_shellProcess.m_handleSlaveName.toLatin1().data(),
+ O_RDWR | O_NOCTTY);
+ if (m_shellProcess.m_handleSlave < 0) {
+ m_lastError = QString("UnixPty Error: unable to open slave -> %1").arg(QLatin1String(strerror(errno)));
+ kill();
+ return false;
+ }
+
+ rc = fcntl(m_shellProcess.m_handleMaster, F_SETFD, FD_CLOEXEC);
+ if (rc == -1) {
+ m_lastError
+ = QString("UnixPty Error: unable to set flags for master -> %1").arg(QLatin1String(strerror(errno)));
+ kill();
+ return false;
+ }
+
+ rc = fcntl(m_shellProcess.m_handleSlave, F_SETFD, FD_CLOEXEC);
+ if (rc == -1) {
+ m_lastError
+ = QString("UnixPty Error: unable to set flags for slave -> %1").arg(QLatin1String(strerror(errno)));
+ kill();
+ return false;
+ }
+
+ struct ::termios ttmode;
+ rc = tcgetattr(m_shellProcess.m_handleMaster, &ttmode);
+ if (rc != 0) {
+ m_lastError = QString("UnixPty Error: termios fail -> %1").arg(QLatin1String(strerror(errno)));
+ kill();
+ return false;
+ }
+
+ ttmode.c_iflag = ICRNL | IXON | IXANY | IMAXBEL | BRKINT;
+#if defined(IUTF8)
+ ttmode.c_iflag |= IUTF8;
+#endif
+
+ ttmode.c_oflag = OPOST | ONLCR;
+ ttmode.c_cflag = CREAD | CS8 | HUPCL;
+ ttmode.c_lflag = ICANON | ISIG | IEXTEN | ECHO | ECHOE | ECHOK | ECHOKE | ECHOCTL;
+
+ ttmode.c_cc[VEOF] = 4;
+ ttmode.c_cc[VEOL] = -1;
+ ttmode.c_cc[VEOL2] = -1;
+ ttmode.c_cc[VERASE] = 0x7f;
+ ttmode.c_cc[VWERASE] = 23;
+ ttmode.c_cc[VKILL] = 21;
+ ttmode.c_cc[VREPRINT] = 18;
+ ttmode.c_cc[VINTR] = 3;
+ ttmode.c_cc[VQUIT] = 0x1c;
+ ttmode.c_cc[VSUSP] = 26;
+ ttmode.c_cc[VSTART] = 17;
+ ttmode.c_cc[VSTOP] = 19;
+ ttmode.c_cc[VLNEXT] = 22;
+ ttmode.c_cc[VDISCARD] = 15;
+ ttmode.c_cc[VMIN] = 1;
+ ttmode.c_cc[VTIME] = 0;
+
+#if (__APPLE__)
+ ttmode.c_cc[VDSUSP] = 25;
+ ttmode.c_cc[VSTATUS] = 20;
+#endif
+
+ cfsetispeed(&ttmode, B38400);
+ cfsetospeed(&ttmode, B38400);
+
+ rc = tcsetattr(m_shellProcess.m_handleMaster, TCSANOW, &ttmode);
+ if (rc != 0) {
+ m_lastError
+ = QString("UnixPty Error: unabble to set associated params -> %1").arg(QLatin1String(strerror(errno)));
+ kill();
+ return false;
+ }
+
+ m_readMasterNotify = new QSocketNotifier(m_shellProcess.m_handleMaster,
+ QSocketNotifier::Read,
+ &m_shellProcess);
+ m_readMasterNotify->setEnabled(true);
+ m_readMasterNotify->moveToThread(m_shellProcess.thread());
+ QObject::connect(m_readMasterNotify, &QSocketNotifier::activated, [this](int socket) {
+ Q_UNUSED(socket)
+
+ const size_t maxRead = 16 * 1024;
+ static std::array<char, maxRead> buffer;
+
+ int len = ::read(m_shellProcess.m_handleMaster, buffer.data(), buffer.size());
+ if (len > 0) {
+ m_shellReadBuffer.append(buffer.data(), len);
+ m_shellProcess.emitReadyRead();
+ }
+ });
+
+ QObject::connect(&m_shellProcess, &QProcess::finished, &m_shellProcess, [this](int exitCode) {
+ m_exitCode = exitCode;
+ emit m_shellProcess.aboutToClose();
+ m_readMasterNotify->disconnect();
+ });
+
+ QStringList defaultVars;
+
+ defaultVars.append("TERM=xterm-256color");
+ defaultVars.append("ITERM_PROFILE=Default");
+ defaultVars.append("XPC_FLAGS=0x0");
+ defaultVars.append("XPC_SERVICE_NAME=0");
+ defaultVars.append("LANG=en_US.UTF-8");
+ defaultVars.append("LC_ALL=en_US.UTF-8");
+ defaultVars.append("LC_CTYPE=UTF-8");
+ defaultVars.append("INIT_CWD=" + QCoreApplication::applicationDirPath());
+ defaultVars.append("COMMAND_MODE=unix2003");
+ defaultVars.append("COLORTERM=truecolor");
+
+ QStringList varNames;
+ foreach (QString line, environment) {
+ varNames.append(line.split("=").first());
+ }
+
+ //append default env vars only if they don't exists in current env
+ foreach (QString defVar, defaultVars) {
+ if (!varNames.contains(defVar.split("=").first()))
+ environment.append(defVar);
+ }
+
+ QProcessEnvironment envFormat;
+ foreach (QString line, environment) {
+ envFormat.insert(line.split("=").first(), line.split("=").last());
+ }
+ m_shellProcess.setWorkingDirectory(workingDir);
+ m_shellProcess.setProcessEnvironment(envFormat);
+ m_shellProcess.setReadChannel(QProcess::StandardOutput);
+ m_shellProcess.start(m_shellPath, arguments);
+ if (!m_shellProcess.waitForStarted())
+ return false;
+
+ m_pid = m_shellProcess.processId();
+
+ resize(cols, rows);
+
+ return true;
+}
+
+bool UnixPtyProcess::resize(qint16 cols, qint16 rows)
+{
+ struct winsize winp;
+ winp.ws_col = cols;
+ winp.ws_row = rows;
+ winp.ws_xpixel = 0;
+ winp.ws_ypixel = 0;
+
+ bool res = ((ioctl(m_shellProcess.m_handleMaster, TIOCSWINSZ, &winp) != -1)
+ && (ioctl(m_shellProcess.m_handleSlave, TIOCSWINSZ, &winp) != -1));
+
+ if (res) {
+ m_size = QPair<qint16, qint16>(cols, rows);
+ }
+
+ return res;
+}
+
+bool UnixPtyProcess::kill()
+{
+ m_shellProcess.m_handleSlaveName = QString();
+ if (m_shellProcess.m_handleSlave >= 0) {
+ ::close(m_shellProcess.m_handleSlave);
+ m_shellProcess.m_handleSlave = -1;
+ }
+ if (m_shellProcess.m_handleMaster >= 0) {
+ ::close(m_shellProcess.m_handleMaster);
+ m_shellProcess.m_handleMaster = -1;
+ }
+
+ if (m_shellProcess.state() == QProcess::Running) {
+ m_readMasterNotify->disconnect();
+ m_readMasterNotify->deleteLater();
+
+ m_shellProcess.terminate();
+ m_shellProcess.waitForFinished(1000);
+
+ if (m_shellProcess.state() == QProcess::Running) {
+ QProcess::startDetached(QString("kill -9 %1").arg(pid()));
+ m_shellProcess.kill();
+ m_shellProcess.waitForFinished(1000);
+ }
+
+ return (m_shellProcess.state() == QProcess::NotRunning);
+ }
+ return false;
+}
+
+IPtyProcess::PtyType UnixPtyProcess::type()
+{
+ return IPtyProcess::UnixPty;
+}
+
+QString UnixPtyProcess::dumpDebugInfo()
+{
+#ifdef PTYQT_DEBUG
+ return QString("PID: %1, In: %2, Out: %3, Type: %4, Cols: %5, Rows: %6, IsRunning: %7, Shell: "
+ "%8, SlaveName: %9")
+ .arg(m_pid)
+ .arg(m_shellProcess.m_handleMaster)
+ .arg(m_shellProcess.m_handleSlave)
+ .arg(type())
+ .arg(m_size.first)
+ .arg(m_size.second)
+ .arg(m_shellProcess.state() == QProcess::Running)
+ .arg(m_shellPath)
+ .arg(m_shellProcess.m_handleSlaveName);
+#else
+ return QString("Nothing...");
+#endif
+}
+
+QIODevice *UnixPtyProcess::notifier()
+{
+ return &m_shellProcess;
+}
+
+QByteArray UnixPtyProcess::readAll()
+{
+ QByteArray tmpBuffer = m_shellReadBuffer;
+ m_shellReadBuffer.clear();
+ return tmpBuffer;
+}
+
+qint64 UnixPtyProcess::write(const QByteArray &byteArray)
+{
+ int result = ::write(m_shellProcess.m_handleMaster, byteArray.constData(), byteArray.size());
+ Q_UNUSED(result)
+
+ return byteArray.size();
+}
+
+bool UnixPtyProcess::isAvailable()
+{
+ //todo check something more if required
+ return true;
+}
+
+void UnixPtyProcess::moveToThread(QThread *targetThread)
+{
+ m_shellProcess.moveToThread(targetThread);
+}
+
+void ShellProcess::configChildProcess()
+{
+ dup2(m_handleSlave, STDIN_FILENO);
+ dup2(m_handleSlave, STDOUT_FILENO);
+ dup2(m_handleSlave, STDERR_FILENO);
+
+ pid_t sid = setsid();
+ ioctl(m_handleSlave, TIOCSCTTY, 0);
+ tcsetpgrp(m_handleSlave, sid);
+
+#if !defined(Q_OS_ANDROID) && !defined(Q_OS_FREEBSD)
+ // on Android imposible to put record to the 'utmp' file
+ struct utmpx utmpxInfo;
+ memset(&utmpxInfo, 0, sizeof(utmpxInfo));
+
+ strncpy(utmpxInfo.ut_user, qgetenv("USER"), sizeof(utmpxInfo.ut_user) - 1);
+
+ QString device(m_handleSlaveName);
+ if (device.startsWith("/dev/"))
+ device = device.mid(5);
+
+ const auto deviceAsLatin1 = device.toLatin1();
+ const char *d = deviceAsLatin1.constData();
+
+ strncpy(utmpxInfo.ut_line, d, sizeof(utmpxInfo.ut_line) - 1);
+
+ strncpy(utmpxInfo.ut_id, d + strlen(d) - sizeof(utmpxInfo.ut_id), sizeof(utmpxInfo.ut_id));
+
+ struct timeval tv;
+ gettimeofday(&tv, 0);
+ utmpxInfo.ut_tv.tv_sec = tv.tv_sec;
+ utmpxInfo.ut_tv.tv_usec = tv.tv_usec;
+
+ utmpxInfo.ut_type = USER_PROCESS;
+ utmpxInfo.ut_pid = getpid();
+
+ utmpxname(_PATH_UTMPX);
+ setutxent();
+ pututxline(&utmpxInfo);
+ endutxent();
+
+#if !defined(Q_OS_UNIX)
+ updwtmpx(_PATH_UTMPX, &loginInfo);
+#endif
+
+#endif
+}
diff --git a/src/libs/3rdparty/libptyqt/unixptyprocess.h b/src/libs/3rdparty/libptyqt/unixptyprocess.h
new file mode 100644
index 00000000000..e4df0d2f74d
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/unixptyprocess.h
@@ -0,0 +1,66 @@
+#ifndef UNIXPTYPROCESS_H
+#define UNIXPTYPROCESS_H
+
+#include "iptyprocess.h"
+#include <QProcess>
+#include <QSocketNotifier>
+
+// support for build with MUSL on Alpine Linux
+#ifndef _PATH_UTMPX
+#include <sys/time.h>
+#define _PATH_UTMPX "/var/log/utmp"
+#endif
+
+class ShellProcess : public QProcess
+{
+ friend class UnixPtyProcess;
+ Q_OBJECT
+public:
+ ShellProcess()
+ : QProcess()
+ , m_handleMaster(-1)
+ , m_handleSlave(-1)
+ {
+ setProcessChannelMode(QProcess::SeparateChannels);
+ setChildProcessModifier([this]() { configChildProcess(); });
+ }
+
+ void emitReadyRead() { emit readyRead(); }
+
+protected:
+ void configChildProcess();
+
+private:
+ int m_handleMaster, m_handleSlave;
+ QString m_handleSlaveName;
+};
+
+class UnixPtyProcess : public IPtyProcess
+{
+public:
+ UnixPtyProcess();
+ virtual ~UnixPtyProcess();
+
+ virtual bool startProcess(const QString &executable,
+ const QStringList &arguments,
+ const QString &workingDir,
+ QStringList environment,
+ qint16 cols,
+ qint16 rows);
+ virtual bool resize(qint16 cols, qint16 rows);
+ virtual bool kill();
+ virtual PtyType type();
+ virtual QString dumpDebugInfo();
+ virtual QIODevice *notifier();
+ virtual QByteArray readAll();
+ virtual qint64 write(const QByteArray &byteArray);
+ virtual bool isAvailable();
+ void moveToThread(QThread *targetThread);
+
+private:
+ ShellProcess m_shellProcess;
+ QSocketNotifier *m_readMasterNotify;
+ QByteArray m_shellReadBuffer;
+};
+
+#endif // UNIXPTYPROCESS_H
diff --git a/src/libs/3rdparty/libptyqt/winptyprocess.cpp b/src/libs/3rdparty/libptyqt/winptyprocess.cpp
new file mode 100644
index 00000000000..0509bb77c37
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/winptyprocess.cpp
@@ -0,0 +1,278 @@
+#include "winptyprocess.h"
+#include <QFile>
+#include <QFileInfo>
+#include <sstream>
+#include <QCoreApplication>
+#include <QLocalSocket>
+#include <QWinEventNotifier>
+
+#define DEBUG_VAR_LEGACY "WINPTYDBG"
+#define DEBUG_VAR_ACTUAL "WINPTY_DEBUG"
+#define SHOW_CONSOLE_VAR "WINPTY_SHOW_CONSOLE"
+#define WINPTY_AGENT_NAME "winpty-agent.exe"
+#define WINPTY_DLL_NAME "winpty.dll"
+
+QString castErrorToString(winpty_error_ptr_t error_ptr)
+{
+ return QString::fromStdWString(winpty_error_msg(error_ptr));
+}
+
+WinPtyProcess::WinPtyProcess()
+ : IPtyProcess()
+ , m_ptyHandler(nullptr)
+ , m_innerHandle(nullptr)
+ , m_inSocket(nullptr)
+ , m_outSocket(nullptr)
+{
+#ifdef PTYQT_DEBUG
+ m_trace = true;
+#endif
+}
+
+WinPtyProcess::~WinPtyProcess()
+{
+ kill();
+}
+
+bool WinPtyProcess::startProcess(const QString &executable,
+ const QStringList &arguments,
+ const QString &workingDir,
+ QStringList environment,
+ qint16 cols,
+ qint16 rows)
+{
+ if (!isAvailable())
+ {
+ m_lastError = QString("WinPty Error: winpty-agent.exe or winpty.dll not found!");
+ return false;
+ }
+
+ //already running
+ if (m_ptyHandler != nullptr)
+ return false;
+
+ QFileInfo fi(executable);
+ if (fi.isRelative() || !QFile::exists(executable))
+ {
+ //todo add auto-find executable in PATH env var
+ m_lastError = QString("WinPty Error: shell file path must be absolute");
+ return false;
+ }
+
+ m_shellPath = executable;
+ m_shellPath.replace('/', '\\');
+ m_size = QPair<qint16, qint16>(cols, rows);
+
+#ifdef PTYQT_DEBUG
+ if (m_trace)
+ {
+ environment.append(QString("%1=1").arg(DEBUG_VAR_LEGACY));
+ environment.append(QString("%1=trace").arg(DEBUG_VAR_ACTUAL));
+ environment.append(QString("%1=1").arg(SHOW_CONSOLE_VAR));
+ SetEnvironmentVariableA(DEBUG_VAR_LEGACY, "1");
+ SetEnvironmentVariableA(DEBUG_VAR_ACTUAL, "trace");
+ SetEnvironmentVariableA(SHOW_CONSOLE_VAR, "1");
+ }
+#endif
+
+ //env
+ std::wstringstream envBlock;
+ foreach (QString line, environment)
+ {
+ envBlock << line.toStdWString() << L'\0';
+ }
+ std::wstring env = envBlock.str();
+
+ //create start config
+ winpty_error_ptr_t errorPtr = nullptr;
+ winpty_config_t* startConfig = winpty_config_new(0, &errorPtr);
+ if (startConfig == nullptr)
+ {
+ m_lastError = QString("WinPty Error: create start config -> %1").arg(castErrorToString(errorPtr));
+ return false;
+ }
+ winpty_error_free(errorPtr);
+
+ //set params
+ winpty_config_set_initial_size(startConfig, cols, rows);
+ winpty_config_set_mouse_mode(startConfig, WINPTY_MOUSE_MODE_AUTO);
+ //winpty_config_set_agent_timeout();
+
+ //start agent
+ m_ptyHandler = winpty_open(startConfig, &errorPtr);
+ winpty_config_free(startConfig); //start config is local var, free it after use
+
+ if (m_ptyHandler == nullptr)
+ {
+ m_lastError = QString("WinPty Error: start agent -> %1").arg(castErrorToString(errorPtr));
+ return false;
+ }
+ winpty_error_free(errorPtr);
+
+ //create spawn config
+ winpty_spawn_config_t* spawnConfig = winpty_spawn_config_new(WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN, m_shellPath.toStdWString().c_str(),
+ //commandLine.toStdWString().c_str(), cwd.toStdWString().c_str(),
+ arguments.join(" ").toStdWString().c_str(), workingDir.toStdWString().c_str(),
+ env.c_str(),
+ &errorPtr);
+
+ if (spawnConfig == nullptr)
+ {
+ m_lastError = QString("WinPty Error: create spawn config -> %1").arg(castErrorToString(errorPtr));
+ return false;
+ }
+ winpty_error_free(errorPtr);
+
+ //spawn the new process
+ BOOL spawnSuccess = winpty_spawn(m_ptyHandler, spawnConfig, &m_innerHandle, nullptr, nullptr, &errorPtr);
+ winpty_spawn_config_free(spawnConfig); //spawn config is local var, free it after use
+ if (!spawnSuccess)
+ {
+ m_lastError = QString("WinPty Error: start terminal process -> %1").arg(castErrorToString(errorPtr));
+ return false;
+ }
+ winpty_error_free(errorPtr);
+
+ m_pid = (int)GetProcessId(m_innerHandle);
+
+ m_outSocket = new QLocalSocket();
+
+ // Notify when the shell process has been terminated
+ m_shellCloseWaitNotifier = new QWinEventNotifier(m_innerHandle, notifier());
+ QObject::connect(m_shellCloseWaitNotifier,
+ &QWinEventNotifier::activated,
+ notifier(),
+ [this](HANDLE hEvent) {
+ DWORD exitCode = 0;
+ GetExitCodeProcess(hEvent, &exitCode);
+ m_exitCode = exitCode;
+ // Do not respawn if the object is about to be destructed
+ if (!m_aboutToDestruct)
+ emit notifier()->aboutToClose();
+ m_shellCloseWaitNotifier->setEnabled(false);
+ });
+
+ //get pipe names
+ LPCWSTR conInPipeName = winpty_conin_name(m_ptyHandler);
+ m_conInName = QString::fromStdWString(std::wstring(conInPipeName));
+ m_inSocket = new QLocalSocket();
+ m_inSocket->connectToServer(m_conInName, QIODevice::WriteOnly);
+ m_inSocket->waitForConnected();
+
+ LPCWSTR conOutPipeName = winpty_conout_name(m_ptyHandler);
+ m_conOutName = QString::fromStdWString(std::wstring(conOutPipeName));
+ m_outSocket->connectToServer(m_conOutName, QIODevice::ReadOnly);
+ m_outSocket->waitForConnected();
+
+ if (m_inSocket->state() != QLocalSocket::ConnectedState && m_outSocket->state() != QLocalSocket::ConnectedState)
+ {
+ m_lastError = QString("WinPty Error: Unable to connect local sockets -> %1 / %2").arg(m_inSocket->errorString()).arg(m_outSocket->errorString());
+ m_inSocket->deleteLater();
+ m_outSocket->deleteLater();
+ m_inSocket = nullptr;
+ m_outSocket = nullptr;
+ return false;
+ }
+
+ return true;
+}
+
+bool WinPtyProcess::resize(qint16 cols, qint16 rows)
+{
+ if (m_ptyHandler == nullptr)
+ {
+ return false;
+ }
+
+ bool res = winpty_set_size(m_ptyHandler, cols, rows, nullptr);
+
+ if (res)
+ {
+ m_size = QPair<qint16, qint16>(cols, rows);
+ }
+
+ return res;
+}
+
+bool WinPtyProcess::kill()
+{
+ bool exitCode = false;
+ if (m_innerHandle != nullptr && m_ptyHandler != nullptr) {
+ m_aboutToDestruct = true;
+ //disconnect all signals (readyRead, ...)
+ m_inSocket->disconnect();
+ m_outSocket->disconnect();
+
+ //disconnect for server
+ m_inSocket->disconnectFromServer();
+ m_outSocket->disconnectFromServer();
+
+ m_inSocket->deleteLater();
+ m_outSocket->deleteLater();
+
+ m_inSocket = nullptr;
+ m_outSocket = nullptr;
+
+ winpty_free(m_ptyHandler);
+ exitCode = CloseHandle(m_innerHandle);
+
+ delete m_shellCloseWaitNotifier;
+ m_shellCloseWaitNotifier = nullptr;
+
+ m_ptyHandler = nullptr;
+ m_innerHandle = nullptr;
+ m_conInName = QString();
+ m_conOutName = QString();
+ m_pid = 0;
+ }
+ return exitCode;
+}
+
+IPtyProcess::PtyType WinPtyProcess::type()
+{
+ return PtyType::WinPty;
+}
+
+QString WinPtyProcess::dumpDebugInfo()
+{
+#ifdef PTYQT_DEBUG
+ return QString("PID: %1, ConIn: %2, ConOut: %3, Type: %4, Cols: %5, Rows: %6, IsRunning: %7, Shell: %8")
+ .arg(m_pid).arg(m_conInName).arg(m_conOutName).arg(type())
+ .arg(m_size.first).arg(m_size.second).arg(m_ptyHandler != nullptr)
+ .arg(m_shellPath);
+#else
+ return QString("Nothing...");
+#endif
+}
+
+QIODevice *WinPtyProcess::notifier()
+{
+ return m_outSocket;
+}
+
+QByteArray WinPtyProcess::readAll()
+{
+ return m_outSocket->readAll();
+}
+
+qint64 WinPtyProcess::write(const QByteArray &byteArray)
+{
+ return m_inSocket->write(byteArray);
+}
+
+bool WinPtyProcess::isAvailable()
+{
+#ifdef PTYQT_BUILD_STATIC
+ return QFile::exists(QCoreApplication::applicationDirPath() + "/" + WINPTY_AGENT_NAME);
+#elif PTYQT_BUILD_DYNAMIC
+ return QFile::exists(QCoreApplication::applicationDirPath() + "/" + WINPTY_AGENT_NAME)
+ && QFile::exists(QCoreApplication::applicationDirPath() + "/" + WINPTY_DLL_NAME);
+#endif
+ return true;
+}
+
+void WinPtyProcess::moveToThread(QThread *targetThread)
+{
+ m_inSocket->moveToThread(targetThread);
+ m_outSocket->moveToThread(targetThread);
+}
diff --git a/src/libs/3rdparty/libptyqt/winptyprocess.h b/src/libs/3rdparty/libptyqt/winptyprocess.h
new file mode 100644
index 00000000000..0bfb27c02c4
--- /dev/null
+++ b/src/libs/3rdparty/libptyqt/winptyprocess.h
@@ -0,0 +1,43 @@
+#ifndef WINPTYPROCESS_H
+#define WINPTYPROCESS_H
+
+#include "iptyprocess.h"
+#include "winpty.h"
+
+class QLocalSocket;
+class QWinEventNotifier;
+
+class WinPtyProcess : public IPtyProcess
+{
+public:
+ WinPtyProcess();
+ ~WinPtyProcess();
+
+ bool startProcess(const QString &executable,
+ const QStringList &arguments,
+ const QString &workingDir,
+ QStringList environment,
+ qint16 cols,
+ qint16 rows);
+ bool resize(qint16 cols, qint16 rows);
+ bool kill();
+ PtyType type();
+ QString dumpDebugInfo();
+ QIODevice *notifier();
+ QByteArray readAll();
+ qint64 write(const QByteArray &byteArray);
+ bool isAvailable();
+ void moveToThread(QThread *targetThread);
+
+private:
+ winpty_t *m_ptyHandler;
+ HANDLE m_innerHandle;
+ QString m_conInName;
+ QString m_conOutName;
+ QLocalSocket *m_inSocket;
+ QLocalSocket *m_outSocket;
+ bool m_aboutToDestruct{false};
+ QWinEventNotifier* m_shellCloseWaitNotifier;
+};
+
+#endif // WINPTYPROCESS_H
diff --git a/src/libs/3rdparty/libvterm/CMakeLists.txt b/src/libs/3rdparty/libvterm/CMakeLists.txt
new file mode 100644
index 00000000000..232217d9f58
--- /dev/null
+++ b/src/libs/3rdparty/libvterm/CMakeLists.txt
@@ -0,0 +1,18 @@
+add_qtc_library(libvterm STATIC
+ PUBLIC_INCLUDES ${CMAKE_CURRENT_SOURCE_DIR}/include
+ PROPERTIES QT_COMPILE_OPTIONS_DISABLE_WARNINGS ON
+ SOURCES
+ src/encoding.c
+ src/fullwidth.inc
+ src/keyboard.c
+ src/mouse.c
+ src/parser.c
+ src/pen.c
+ src/rect.h
+ src/screen.c
+ src/state.c
+ src/unicode.c
+ src/utf8.h
+ src/vterm.c
+ src/vterm_internal.h
+)
diff --git a/src/libs/3rdparty/libvterm/CONTRIBUTING b/src/libs/3rdparty/libvterm/CONTRIBUTING
new file mode 100644
index 00000000000..e9a8f0c3315
--- /dev/null
+++ b/src/libs/3rdparty/libvterm/CONTRIBUTING
@@ -0,0 +1,22 @@
+How to Contribute
+-----------------
+
+The main resources for this library are:
+
+ Launchpad
+ https://launchpad.net/libvterm
+
+ IRC:
+ ##tty or #tickit on irc.libera.chat
+
+ Email:
+ Paul "LeoNerd" Evans <[email protected]>
+
+
+Bug reports and feature requests can be sent to any of the above resources.
+
+New features, bug patches, etc.. should in the first instance be discussed via
+any of the resources listed above, before starting work on the actual code.
+There may be future plans or development already in-progress that could be
+affected so it is better to discuss the ideas first before starting work
+actually writing any code.
diff --git a/src/libs/3rdparty/libvterm/LICENSE b/src/libs/3rdparty/libvterm/LICENSE
new file mode 100644
index 00000000000..0d051634b28
--- /dev/null
+++ b/src/libs/3rdparty/libvterm/LICENSE
@@ -0,0 +1,23 @@
+
+
+The MIT License
+
+Copyright (c) 2008 Paul Evans <[email protected]>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/src/libs/3rdparty/libvterm/include/vterm.h b/src/libs/3rdparty/libvterm/include/vterm.h
new file mode 100644
index 00000000000..cb16ff2a044
--- /dev/null
+++ b/src/libs/3rdparty/libvterm/include/vterm.h
@@ -0,0 +1,637 @@
+#ifndef __VTERM_H__
+#define __VTERM_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+#include "vterm_keycodes.h"
+
+#define VTERM_VERSION_MAJOR 0
+#define VTERM_VERSION_MINOR 3
+
+#define VTERM_CHECK_VERSION \
+ vterm_check_version(VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR)
+
+/* Any cell can contain at most one basic printing character and 5 combining
+ * characters. This number could be changed but will be ABI-incompatible if
+ * you do */
+#define VTERM_MAX_CHARS_PER_CELL 6
+
+typedef struct VTerm VTerm;
+typedef struct VTermState VTermState;
+typedef struct VTermScreen VTermScreen;
+
+typedef struct {
+ int row;
+ int col;
+} VTermPos;
+
+/* some small utility functions; we can just keep these static here */
+
+/* order points by on-screen flow order */
+static inline int vterm_pos_cmp(VTermPos a, VTermPos b)
+{
+ return (a.row == b.row) ? a.col - b.col : a.row - b.row;
+}
+
+typedef struct {
+ int start_row;
+ int end_row;
+ int start_col;
+ int end_col;
+} VTermRect;
+
+/* true if the rect contains the point */
+static inline int vterm_rect_contains(VTermRect r, VTermPos p)
+{
+ return p.row >= r.start_row && p.row < r.end_row &&
+ p.col >= r.start_col && p.col < r.end_col;
+}
+
+/* move a rect */
+static inline void vterm_rect_move(VTermRect *rect, int row_delta, int col_delta)
+{
+ rect->start_row += row_delta; rect->end_row += row_delta;
+ rect->start_col += col_delta; rect->end_col += col_delta;
+}
+
+/**
+ * Bit-field describing the content of the tagged union `VTermColor`.
+ */
+typedef enum {
+ /**
+ * If the lower bit of `type` is not set, the colour is 24-bit RGB.
+ */
+ VTERM_COLOR_RGB = 0x00,
+
+ /**
+ * The colour is an index into a palette of 256 colours.
+ */
+ VTERM_COLOR_INDEXED = 0x01,
+
+ /**
+ * Mask that can be used to extract the RGB/Indexed bit.
+ */
+ VTERM_COLOR_TYPE_MASK = 0x01,
+
+ /**
+ * If set, indicates that this colour should be the default foreground
+ * color, i.e. there was no SGR request for another colour. When
+ * rendering this colour it is possible to ignore "idx" and just use a
+ * colour that is not in the palette.
+ */
+ VTERM_COLOR_DEFAULT_FG = 0x02,
+
+ /**
+ * If set, indicates that this colour should be the default background
+ * color, i.e. there was no SGR request for another colour. A common
+ * option when rendering this colour is to not render a background at
+ * all, for example by rendering the window transparently at this spot.
+ */
+ VTERM_COLOR_DEFAULT_BG = 0x04,
+
+ /**
+ * Mask that can be used to extract the default foreground/background bit.
+ */
+ VTERM_COLOR_DEFAULT_MASK = 0x06
+} VTermColorType;
+
+/**
+ * Returns true if the VTERM_COLOR_RGB `type` flag is set, indicating that the
+ * given VTermColor instance is an indexed colour.
+ */
+#define VTERM_COLOR_IS_INDEXED(col) \
+ (((col)->type & VTERM_COLOR_TYPE_MASK) == VTERM_COLOR_INDEXED)
+
+/**
+ * Returns true if the VTERM_COLOR_INDEXED `type` flag is set, indicating that
+ * the given VTermColor instance is an rgb colour.
+ */
+#define VTERM_COLOR_IS_RGB(col) \
+ (((col)->type & VTERM_COLOR_TYPE_MASK) == VTERM_COLOR_RGB)
+
+/**
+ * Returns true if the VTERM_COLOR_DEFAULT_FG `type` flag is set, indicating
+ * that the given VTermColor instance corresponds to the default foreground
+ * color.
+ */
+#define VTERM_COLOR_IS_DEFAULT_FG(col) \
+ (!!((col)->type & VTERM_COLOR_DEFAULT_FG))
+
+/**
+ * Returns true if the VTERM_COLOR_DEFAULT_BG `type` flag is set, indicating
+ * that the given VTermColor instance corresponds to the default background
+ * color.
+ */
+#define VTERM_COLOR_IS_DEFAULT_BG(col) \
+ (!!((col)->type & VTERM_COLOR_DEFAULT_BG))
+
+/**
+ * Tagged union storing either an RGB color or an index into a colour palette.
+ * In order to convert indexed colours to RGB, you may use the
+ * vterm_state_convert_color_to_rgb() or vterm_screen_convert_color_to_rgb()
+ * functions which lookup the RGB colour from the palette maintained by a
+ * VTermState or VTermScreen instance.
+ */
+typedef union {
+ /**
+ * Tag indicating which union member is actually valid. This variable
+ * coincides with the `type` member of the `rgb` and the `indexed` struct
+ * in memory. Please use the `VTERM_COLOR_IS_*` test macros to check whether
+ * a particular type flag is set.
+ */
+ uint8_t type;
+
+ /**
+ * Valid if `VTERM_COLOR_IS_RGB(type)` is true. Holds the RGB colour values.
+ */
+ struct {
+ /**
+ * Same as the top-level `type` member stored in VTermColor.
+ */
+ uint8_t type;
+
+ /**
+ * The actual 8-bit red, green, blue colour values.
+ */
+ uint8_t red, green, blue;
+ } rgb;
+
+ /**
+ * If `VTERM_COLOR_IS_INDEXED(type)` is true, this member holds the index into
+ * the colour palette.
+ */
+ struct {
+ /**
+ * Same as the top-level `type` member stored in VTermColor.
+ */
+ uint8_t type;
+
+ /**
+ * Index into the colour map.
+ */
+ uint8_t idx;
+ } indexed;
+} VTermColor;
+
+/**
+ * Constructs a new VTermColor instance representing the given RGB values.
+ */
+static inline void vterm_color_rgb(VTermColor *col, uint8_t red, uint8_t green,
+ uint8_t blue)
+{
+ col->type = VTERM_COLOR_RGB;
+ col->rgb.red = red;
+ col->rgb.green = green;
+ col->rgb.blue = blue;
+}
+
+/**
+ * Construct a new VTermColor instance representing an indexed color with the
+ * given index.
+ */
+static inline void vterm_color_indexed(VTermColor *col, uint8_t idx)
+{
+ col->type = VTERM_COLOR_INDEXED;
+ col->indexed.idx = idx;
+}
+
+/**
+ * Compares two colours. Returns true if the colors are equal, false otherwise.
+ */
+int vterm_color_is_equal(const VTermColor *a, const VTermColor *b);
+
+typedef enum {
+ /* VTERM_VALUETYPE_NONE = 0 */
+ VTERM_VALUETYPE_BOOL = 1,
+ VTERM_VALUETYPE_INT,
+ VTERM_VALUETYPE_STRING,
+ VTERM_VALUETYPE_COLOR,
+
+ VTERM_N_VALUETYPES
+} VTermValueType;
+
+typedef struct {
+ const char *str;
+ size_t len : 30;
+ bool initial : 1;
+ bool final : 1;
+} VTermStringFragment;
+
+typedef union {
+ int boolean;
+ int number;
+ VTermStringFragment string;
+ VTermColor color;
+} VTermValue;
+
+typedef enum {
+ /* VTERM_ATTR_NONE = 0 */
+ VTERM_ATTR_BOLD = 1, // bool: 1, 22
+ VTERM_ATTR_UNDERLINE, // number: 4, 21, 24
+ VTERM_ATTR_ITALIC, // bool: 3, 23
+ VTERM_ATTR_BLINK, // bool: 5, 25
+ VTERM_ATTR_REVERSE, // bool: 7, 27
+ VTERM_ATTR_CONCEAL, // bool: 8, 28
+ VTERM_ATTR_STRIKE, // bool: 9, 29
+ VTERM_ATTR_FONT, // number: 10-19
+ VTERM_ATTR_FOREGROUND, // color: 30-39 90-97
+ VTERM_ATTR_BACKGROUND, // color: 40-49 100-107
+ VTERM_ATTR_SMALL, // bool: 73, 74, 75
+ VTERM_ATTR_BASELINE, // number: 73, 74, 75
+
+ VTERM_N_ATTRS
+} VTermAttr;
+
+typedef enum {
+ /* VTERM_PROP_NONE = 0 */
+ VTERM_PROP_CURSORVISIBLE = 1, // bool
+ VTERM_PROP_CURSORBLINK, // bool
+ VTERM_PROP_ALTSCREEN, // bool
+ VTERM_PROP_TITLE, // string
+ VTERM_PROP_ICONNAME, // string
+ VTERM_PROP_REVERSE, // bool
+ VTERM_PROP_CURSORSHAPE, // number
+ VTERM_PROP_MOUSE, // number
+
+ VTERM_N_PROPS
+} VTermProp;
+
+enum {
+ VTERM_PROP_CURSORSHAPE_BLOCK = 1,
+ VTERM_PROP_CURSORSHAPE_UNDERLINE,
+ VTERM_PROP_CURSORSHAPE_BAR_LEFT,
+
+ VTERM_N_PROP_CURSORSHAPES
+};
+
+enum {
+ VTERM_PROP_MOUSE_NONE = 0,
+ VTERM_PROP_MOUSE_CLICK,
+ VTERM_PROP_MOUSE_DRAG,
+ VTERM_PROP_MOUSE_MOVE,
+
+ VTERM_N_PROP_MOUSES
+};
+
+typedef enum {
+ VTERM_SELECTION_CLIPBOARD = (1<<0),
+ VTERM_SELECTION_PRIMARY = (1<<1),
+ VTERM_SELECTION_SECONDARY = (1<<2),
+ VTERM_SELECTION_SELECT = (1<<3),
+ VTERM_SELECTION_CUT0 = (1<<4), /* also CUT1 .. CUT7 by bitshifting */
+} VTermSelectionMask;
+
+typedef struct {
+ const uint32_t *chars;
+ int width;
+ unsigned int protected_cell:1; /* DECSCA-protected against DECSEL/DECSED */
+ unsigned int dwl:1; /* DECDWL or DECDHL double-width line */
+ unsigned int dhl:2; /* DECDHL double-height line (1=top 2=bottom) */
+} VTermGlyphInfo;
+
+typedef struct {
+ unsigned int doublewidth:1; /* DECDWL or DECDHL line */
+ unsigned int doubleheight:2; /* DECDHL line (1=top 2=bottom) */
+ unsigned int continuation:1; /* Line is a flow continuation of the previous */
+} VTermLineInfo;
+
+/* Copies of VTermState fields that the 'resize' callback might have reason to
+ * edit. 'resize' callback gets total control of these fields and may
+ * free-and-reallocate them if required. They will be copied back from the
+ * struct after the callback has returned.
+ */
+typedef struct {
+ VTermPos pos; /* current cursor position */
+ VTermLineInfo *lineinfos[2]; /* [1] may be NULL */
+} VTermStateFields;
+
+typedef struct {
+ /* libvterm relies on this memory to be zeroed out before it is returned
+ * by the allocator. */
+ void *(*malloc)(size_t size, void *allocdata);
+ void (*free)(void *ptr, void *allocdata);
+} VTermAllocatorFunctions;
+
+void vterm_check_version(int major, int minor);
+
+struct VTermBuilder {
+ int ver; /* currently unused but reserved for some sort of ABI version flag */
+
+ int rows, cols;
+
+ const VTermAllocatorFunctions *allocator;
+ void *allocdata;
+
+ /* Override default sizes for various structures */
+ size_t outbuffer_len; /* default: 4096 */
+ size_t tmpbuffer_len; /* default: 4096 */
+};
+
+VTerm *vterm_build(const struct VTermBuilder *builder);
+
+/* A convenient shortcut for default cases */
+VTerm *vterm_new(int rows, int cols);
+/* This shortcuts are generally discouraged in favour of just using vterm_build() */
+VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata);
+
+void vterm_free(VTerm* vt);
+
+void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp);
+void vterm_set_size(VTerm *vt, int rows, int cols);
+
+int vterm_get_utf8(const VTerm *vt);
+void vterm_set_utf8(VTerm *vt, int is_utf8);
+
+size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len);
+
+/* Setting output callback will override the buffer logic */
+typedef void VTermOutputCallback(const char *s, size_t len, void *user);
+void vterm_output_set_callback(VTerm *vt, VTermOutputCallback *func, void *user);
+
+/* These buffer functions only work if output callback is NOT set
+ * These are deprecated and will be removed in a later version */
+size_t vterm_output_get_buffer_size(const VTerm *vt);
+size_t vterm_output_get_buffer_current(const VTerm *vt);
+size_t vterm_output_get_buffer_remaining(const VTerm *vt);
+
+/* This too */
+size_t vterm_output_read(VTerm *vt, char *buffer, size_t len);
+
+void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod);
+void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod);
+
+void vterm_keyboard_start_paste(VTerm *vt);
+void vterm_keyboard_end_paste(VTerm *vt);
+
+void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod);
+void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod);
+
+// ------------
+// Parser layer
+// ------------
+
+/* Flag to indicate non-final subparameters in a single CSI parameter.
+ * Consider
+ * CSI 1;2:3:4;5a
+ * 1 4 and 5 are final.
+ * 2 and 3 are non-final and will have this bit set
+ *
+ * Don't confuse this with the final byte of the CSI escape; 'a' in this case.
+ */
+#define CSI_ARG_FLAG_MORE (1U<<31)
+#define CSI_ARG_MASK (~(1U<<31))
+
+#define CSI_ARG_HAS_MORE(a) ((a) & CSI_ARG_FLAG_MORE)
+#define CSI_ARG(a) ((a) & CSI_ARG_MASK)
+
+/* Can't use -1 to indicate a missing argument; use this instead */
+#define CSI_ARG_MISSING ((1UL<<31)-1)
+
+#define CSI_ARG_IS_MISSING(a) (CSI_ARG(a) == CSI_ARG_MISSING)
+#define CSI_ARG_OR(a,def) (CSI_ARG(a) == CSI_ARG_MISSING ? (def) : CSI_ARG(a))
+#define CSI_ARG_COUNT(a) (CSI_ARG(a) == CSI_ARG_MISSING || CSI_ARG(a) == 0 ? 1 : CSI_ARG(a))
+
+typedef struct {
+ int (*text)(const char *bytes, size_t len, void *user);
+ int (*control)(unsigned char control, void *user);
+ int (*escape)(const char *bytes, size_t len, void *user);
+ int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user);
+ int (*osc)(int command, VTermStringFragment frag, void *user);
+ int (*dcs)(const char *command, size_t commandlen, VTermStringFragment frag, void *user);
+ int (*apc)(VTermStringFragment frag, void *user);
+ int (*pm)(VTermStringFragment frag, void *user);
+ int (*sos)(VTermStringFragment frag, void *user);
+ int (*resize)(int rows, int cols, void *user);
+} VTermParserCallbacks;
+
+void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user);
+void *vterm_parser_get_cbdata(VTerm *vt);
+
+/* Normally NUL, CAN, SUB and DEL are ignored. Setting this true causes them
+ * to be emitted by the 'control' callback
+ */
+void vterm_parser_set_emit_nul(VTerm *vt, bool emit);
+
+// -----------
+// State layer
+// -----------
+
+typedef struct {
+ int (*putglyph)(VTermGlyphInfo *info, VTermPos pos, void *user);
+ int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user);
+ int (*scrollrect)(VTermRect rect, int downward, int rightward, void *user);
+ int (*moverect)(VTermRect dest, VTermRect src, void *user);
+ int (*erase)(VTermRect rect, int selective, void *user);
+ int (*initpen)(void *user);
+ int (*setpenattr)(VTermAttr attr, VTermValue *val, void *user);
+ int (*settermprop)(VTermProp prop, VTermValue *val, void *user);
+ int (*bell)(void *user);
+ int (*resize)(int rows, int cols, VTermStateFields *fields, void *user);
+ int (*setlineinfo)(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user);
+ int (*sb_clear)(void *user);
+} VTermStateCallbacks;
+
+typedef struct {
+ int (*control)(unsigned char control, void *user);
+ int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user);
+ int (*osc)(int command, VTermStringFragment frag, void *user);
+ int (*dcs)(const char *command, size_t commandlen, VTermStringFragment frag, void *user);
+ int (*apc)(VTermStringFragment frag, void *user);
+ int (*pm)(VTermStringFragment frag, void *user);
+ int (*sos)(VTermStringFragment frag, void *user);
+} VTermStateFallbacks;
+
+typedef struct {
+ int (*set)(VTermSelectionMask mask, VTermStringFragment frag, void *user);
+ int (*query)(VTermSelectionMask mask, void *user);
+} VTermSelectionCallbacks;
+
+VTermState *vterm_obtain_state(VTerm *vt);
+
+void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user);
+void *vterm_state_get_cbdata(VTermState *state);
+
+void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermStateFallbacks *fallbacks, void *user);
+void *vterm_state_get_unrecognised_fbdata(VTermState *state);
+
+void vterm_state_reset(VTermState *state, int hard);
+void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos);
+void vterm_state_get_default_colors(const VTermState *state, VTermColor *default_fg, VTermColor *default_bg);
+void vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col);
+void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg);
+void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col);
+void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright);
+int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val);
+int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val);
+void vterm_state_focus_in(VTermState *state);
+void vterm_state_focus_out(VTermState *state);
+const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row);
+
+/**
+ * Makes sure that the given color `col` is indeed an RGB colour. After this
+ * function returns, VTERM_COLOR_IS_RGB(col) will return true, while all other
+ * flags stored in `col->type` will have been reset.
+ *
+ * @param state is the VTermState instance from which the colour palette should
+ * be extracted.
+ * @param col is a pointer at the VTermColor instance that should be converted
+ * to an RGB colour.
+ */
+void vterm_state_convert_color_to_rgb(const VTermState *state, VTermColor *col);
+
+void vterm_state_set_selection_callbacks(VTermState *state, const VTermSelectionCallbacks *callbacks, void *user,
+ char *buffer, size_t buflen);
+
+void vterm_state_send_selection(VTermState *state, VTermSelectionMask mask, VTermStringFragment frag);
+
+// ------------
+// Screen layer
+// ------------
+
+typedef struct {
+ unsigned int bold : 1;
+ unsigned int underline : 3;
+ unsigned int italic : 1;
+ unsigned int blink : 1;
+ unsigned int reverse : 1;
+ unsigned int conceal : 1;
+ unsigned int strike : 1;
+ unsigned int font : 4; /* 0 to 9 */
+ unsigned int dwl : 1; /* On a DECDWL or DECDHL line */
+ unsigned int dhl : 2; /* On a DECDHL line (1=top 2=bottom) */
+ unsigned int small : 1;
+ unsigned int baseline : 2;
+} VTermScreenCellAttrs;
+
+enum {
+ VTERM_UNDERLINE_OFF,
+ VTERM_UNDERLINE_SINGLE,
+ VTERM_UNDERLINE_DOUBLE,
+ VTERM_UNDERLINE_CURLY,
+ VTERM_UNDERLINE_DOTTED,
+ VTERM_UNDERLINE_DASHED
+};
+
+enum {
+ VTERM_BASELINE_NORMAL,
+ VTERM_BASELINE_RAISE,
+ VTERM_BASELINE_LOWER,
+};
+
+typedef struct {
+ uint32_t chars[VTERM_MAX_CHARS_PER_CELL];
+ char width;
+ VTermScreenCellAttrs attrs;
+ VTermColor fg, bg;
+} VTermScreenCell;
+
+typedef struct {
+ int (*damage)(VTermRect rect, void *user);
+ int (*moverect)(VTermRect dest, VTermRect src, void *user);
+ int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user);
+ int (*settermprop)(VTermProp prop, VTermValue *val, void *user);
+ int (*bell)(void *user);
+ int (*resize)(int rows, int cols, void *user);
+ int (*sb_pushline)(int cols, const VTermScreenCell *cells, void *user);
+ int (*sb_popline)(int cols, VTermScreenCell *cells, void *user);
+ int (*sb_clear)(void* user);
+} VTermScreenCallbacks;
+
+VTermScreen *vterm_obtain_screen(VTerm *vt);
+
+void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user);
+void *vterm_screen_get_cbdata(VTermScreen *screen);
+
+void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermStateFallbacks *fallbacks, void *user);
+void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen);
+
+void vterm_screen_enable_reflow(VTermScreen *screen, bool reflow);
+
+// Back-compat alias for the brief time it was in 0.3-RC1
+#define vterm_screen_set_reflow vterm_screen_enable_reflow
+
+void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen);
+
+typedef enum {
+ VTERM_DAMAGE_CELL, /* every cell */
+ VTERM_DAMAGE_ROW, /* entire rows */
+ VTERM_DAMAGE_SCREEN, /* entire screen */
+ VTERM_DAMAGE_SCROLL, /* entire screen + scrollrect */
+
+ VTERM_N_DAMAGES
+} VTermDamageSize;
+
+void vterm_screen_flush_damage(VTermScreen *screen);
+void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size);
+
+void vterm_screen_reset(VTermScreen *screen, int hard);
+
+/* Neither of these functions NUL-terminate the buffer */
+size_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect);
+size_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect);
+
+typedef enum {
+ VTERM_ATTR_BOLD_MASK = 1 << 0,
+ VTERM_ATTR_UNDERLINE_MASK = 1 << 1,
+ VTERM_ATTR_ITALIC_MASK = 1 << 2,
+ VTERM_ATTR_BLINK_MASK = 1 << 3,
+ VTERM_ATTR_REVERSE_MASK = 1 << 4,
+ VTERM_ATTR_STRIKE_MASK = 1 << 5,
+ VTERM_ATTR_FONT_MASK = 1 << 6,
+ VTERM_ATTR_FOREGROUND_MASK = 1 << 7,
+ VTERM_ATTR_BACKGROUND_MASK = 1 << 8,
+ VTERM_ATTR_CONCEAL_MASK = 1 << 9,
+ VTERM_ATTR_SMALL_MASK = 1 << 10,
+ VTERM_ATTR_BASELINE_MASK = 1 << 11,
+
+ VTERM_ALL_ATTRS_MASK = (1 << 12) - 1
+} VTermAttrMask;
+
+int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs);
+
+int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell);
+
+int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos);
+
+/**
+ * Same as vterm_state_convert_color_to_rgb(), but takes a `screen` instead of a `state`
+ * instance.
+ */
+void vterm_screen_convert_color_to_rgb(const VTermScreen *screen, VTermColor *col);
+
+/**
+ * Similar to vterm_state_set_default_colors(), but also resets colours in the
+ * screen buffer(s)
+ */
+void vterm_screen_set_default_colors(VTermScreen *screen, const VTermColor *default_fg, const VTermColor *default_bg);
+
+// ---------
+// Utilities
+// ---------
+
+VTermValueType vterm_get_attr_type(VTermAttr attr);
+VTermValueType vterm_get_prop_type(VTermProp prop);
+
+void vterm_scroll_rect(VTermRect rect,
+ int downward,
+ int rightward,
+ int (*moverect)(VTermRect src, VTermRect dest, void *user),
+ int (*eraserect)(VTermRect rect, int selective, void *user),
+ void *user);
+
+void vterm_copy_cells(VTermRect dest,
+ VTermRect src,
+ void (*copycell)(VTermPos dest, VTermPos src, void *user),
+ void *user);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/src/libs/3rdparty/libvterm/include/vterm_keycodes.h b/src/libs/3rdparty/libvterm/include/vterm_keycodes.h
new file mode 100644
index 00000000000..661759febd4
--- /dev/null
+++ b/src/libs/3rdparty/libvterm/include/vterm_keycodes.h
@@ -0,0 +1,61 @@
+#ifndef __VTERM_INPUT_H__
+#define __VTERM_INPUT_H__
+
+typedef enum {
+ VTERM_MOD_NONE = 0x00,
+ VTERM_MOD_SHIFT = 0x01,
+ VTERM_MOD_ALT = 0x02,
+ VTERM_MOD_CTRL = 0x04,
+
+ VTERM_ALL_MODS_MASK = 0x07
+} VTermModifier;
+
+typedef enum {
+ VTERM_KEY_NONE,
+
+ VTERM_KEY_ENTER,
+ VTERM_KEY_TAB,
+ VTERM_KEY_BACKSPACE,
+ VTERM_KEY_ESCAPE,
+
+ VTERM_KEY_UP,
+ VTERM_KEY_DOWN,
+ VTERM_KEY_LEFT,
+ VTERM_KEY_RIGHT,
+
+ VTERM_KEY_INS,
+ VTERM_KEY_DEL,
+ VTERM_KEY_HOME,
+ VTERM_KEY_END,
+ VTERM_KEY_PAGEUP,
+ VTERM_KEY_PAGEDOWN,
+
+ VTERM_KEY_FUNCTION_0 = 256,
+ VTERM_KEY_FUNCTION_MAX = VTERM_KEY_FUNCTION_0 + 255,
+
+ VTERM_KEY_KP_0,
+ VTERM_KEY_KP_1,
+ VTERM_KEY_KP_2,
+ VTERM_KEY_KP_3,
+ VTERM_KEY_KP_4,
+ VTERM_KEY_KP_5,
+ VTERM_KEY_KP_6,
+ VTERM_KEY_KP_7,
+ VTERM_KEY_KP_8,
+ VTERM_KEY_KP_9,
+ VTERM_KEY_KP_MULT,
+ VTERM_KEY_KP_PLUS,
+ VTERM_KEY_KP_COMMA,
+ VTERM_KEY_KP_MINUS,
+ VTERM_KEY_KP_PERIOD,
+ VTERM_KEY_KP_DIVIDE,
+ VTERM_KEY_KP_ENTER,
+ VTERM_KEY_KP_EQUAL,
+
+ VTERM_KEY_MAX, // Must be last
+ VTERM_N_KEYS = VTERM_KEY_MAX
+} VTermKey;
+
+#define VTERM_KEY_FUNCTION(n) (VTERM_KEY_FUNCTION_0+(n))
+
+#endif
diff --git a/src/libs/3rdparty/libvterm/src/encoding.c b/src/libs/3rdparty/libvterm/src/encoding.c
new file mode 100644
index 00000000000..434ac3f99b7
--- /dev/null
+++ b/src/libs/3rdparty/libvterm/src/encoding.c
@@ -0,0 +1,230 @@
+#include "vterm_internal.h"
+
+#define UNICODE_INVALID 0xFFFD
+
+#if defined(DEBUG) && DEBUG > 1
+# define DEBUG_PRINT_UTF8
+#endif
+
+struct UTF8DecoderData {
+ // number of bytes remaining in this codepoint
+ int bytes_remaining;
+
+ // number of bytes total in this codepoint once it's finished
+ // (for detecting overlongs)
+ int bytes_total;
+
+ int this_cp;
+};
+
+static void init_utf8(VTermEncoding *enc, void *data_)
+{
+ struct UTF8DecoderData *data = data_;
+
+ data->bytes_remaining = 0;
+ data->bytes_total = 0;
+}
+
+static void decode_utf8(VTermEncoding *enc, void *data_,
+ uint32_t cp[], int *cpi, int cplen,
+ const char bytes[], size_t *pos, size_t bytelen)
+{
+ struct UTF8DecoderData *data = data_;
+
+#ifdef DEBUG_PRINT_UTF8
+ printf("BEGIN UTF-8\n");
+#endif
+
+ for(; *pos < bytelen && *cpi < cplen; (*pos)++) {
+ unsigned char c = bytes[*pos];
+
+#ifdef DEBUG_PRINT_UTF8
+ printf(" pos=%zd c=%02x rem=%d\n", *pos, c, data->bytes_remaining);
+#endif
+
+ if(c < 0x20) // C0
+ return;
+
+ else if(c >= 0x20 && c < 0x7f) {
+ if(data->bytes_remaining)
+ cp[(*cpi)++] = UNICODE_INVALID;
+
+ cp[(*cpi)++] = c;
+#ifdef DEBUG_PRINT_UTF8
+ printf(" UTF-8 char: U+%04x\n", c);
+#endif
+ data->bytes_remaining = 0;
+ }
+
+ else if(c == 0x7f) // DEL
+ return;
+
+ else if(c >= 0x80 && c < 0xc0) {
+ if(!data->bytes_remaining) {
+ cp[(*cpi)++] = UNICODE_INVALID;
+ continue;
+ }
+
+ data->this_cp <<= 6;
+ data->this_cp |= c & 0x3f;
+ data->bytes_remaining--;
+
+ if(!data->bytes_remaining) {
+#ifdef DEBUG_PRINT_UTF8
+ printf(" UTF-8 raw char U+%04x bytelen=%d ", data->this_cp, data->bytes_total);
+#endif
+ // Check for overlong sequences
+ switch(data->bytes_total) {
+ case 2:
+ if(data->this_cp < 0x0080) data->this_cp = UNICODE_INVALID;
+ break;
+ case 3:
+ if(data->this_cp < 0x0800) data->this_cp = UNICODE_INVALID;
+ break;
+ case 4:
+ if(data->this_cp < 0x10000) data->this_cp = UNICODE_INVALID;
+ break;
+ case 5:
+ if(data->this_cp < 0x200000) data->this_cp = UNICODE_INVALID;
+ break;
+ case 6:
+ if(data->this_cp < 0x4000000) data->this_cp = UNICODE_INVALID;
+ break;
+ }
+ // Now look for plain invalid ones
+ if((data->this_cp >= 0xD800 && data->this_cp <= 0xDFFF) ||
+ data->this_cp == 0xFFFE ||
+ data->this_cp == 0xFFFF)
+ data->this_cp = UNICODE_INVALID;
+#ifdef DEBUG_PRINT_UTF8
+ printf(" char: U+%04x\n", data->this_cp);
+#endif
+ cp[(*cpi)++] = data->this_cp;
+ }
+ }
+
+ else if(c >= 0xc0 && c < 0xe0) {
+ if(data->bytes_remaining)
+ cp[(*cpi)++] = UNICODE_INVALID;
+
+ data->this_cp = c & 0x1f;
+ data->bytes_total = 2;
+ data->bytes_remaining = 1;
+ }
+
+ else if(c >= 0xe0 && c < 0xf0) {
+ if(data->bytes_remaining)
+ cp[(*cpi)++] = UNICODE_INVALID;
+
+ data->this_cp = c & 0x0f;
+ data->bytes_total = 3;
+ data->bytes_remaining = 2;
+ }
+
+ else if(c >= 0xf0 && c < 0xf8) {
+ if(data->bytes_remaining)
+ cp[(*cpi)++] = UNICODE_INVALID;
+
+ data->this_cp = c & 0x07;
+ data->bytes_total = 4;
+ data->bytes_remaining = 3;
+ }
+
+ else if(c >= 0xf8 && c < 0xfc) {
+ if(data->bytes_remaining)
+ cp[(*cpi)++] = UNICODE_INVALID;
+
+ data->this_cp = c & 0x03;
+ data->bytes_total = 5;
+ data->bytes_remaining = 4;
+ }
+
+ else if(c >= 0xfc && c < 0xfe) {
+ if(data->bytes_remaining)
+ cp[(*cpi)++] = UNICODE_INVALID;
+
+ data->this_cp = c & 0x01;
+ data->bytes_total = 6;
+ data->bytes_remaining = 5;
+ }
+
+ else {
+ cp[(*cpi)++] = UNICODE_INVALID;
+ }
+ }
+}
+
+static VTermEncoding encoding_utf8 = {
+ .init = &init_utf8,
+ .decode = &decode_utf8,
+};
+
+static void decode_usascii(VTermEncoding *enc, void *data,
+ uint32_t cp[], int *cpi, int cplen,
+ const char bytes[], size_t *pos, size_t bytelen)
+{
+ int is_gr = bytes[*pos] & 0x80;
+
+ for(; *pos < bytelen && *cpi < cplen; (*pos)++) {
+ unsigned char c = bytes[*pos] ^ is_gr;
+
+ if(c < 0x20 || c == 0x7f || c >= 0x80)
+ return;
+
+ cp[(*cpi)++] = c;
+ }
+}
+
+static VTermEncoding encoding_usascii = {
+ .decode = &decode_usascii,
+};
+
+struct StaticTableEncoding {
+ const VTermEncoding enc;
+ const uint32_t chars[128];
+};
+
+static void decode_table(VTermEncoding *enc, void *data,
+ uint32_t cp[], int *cpi, int cplen,
+ const char bytes[], size_t *pos, size_t bytelen)
+{
+ struct StaticTableEncoding *table = (struct StaticTableEncoding *)enc;
+ int is_gr = bytes[*pos] & 0x80;
+
+ for(; *pos < bytelen && *cpi < cplen; (*pos)++) {
+ unsigned char c = bytes[*pos] ^ is_gr;
+
+ if(c < 0x20 || c == 0x7f || c >= 0x80)
+ return;
+
+ if(table->chars[c])
+ cp[(*cpi)++] = table->chars[c];
+ else
+ cp[(*cpi)++] = c;
+ }
+}
+
+#include "encoding/DECdrawing.inc"
+#include "encoding/uk.inc"
+
+static struct {
+ VTermEncodingType type;
+ char designation;
+ VTermEncoding *enc;
+}
+encodings[] = {
+ { ENC_UTF8, 'u', &encoding_utf8 },
+ { ENC_SINGLE_94, '0', (VTermEncoding*)&encoding_DECdrawing },
+ { ENC_SINGLE_94, 'A', (VTermEncoding*)&encoding_uk },
+ { ENC_SINGLE_94, 'B', &encoding_usascii },
+ { 0 },
+};
+
+/* This ought to be INTERNAL but isn't because it's used by unit testing */
+VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation)
+{
+ for(int i = 0; encodings[i].designation; i++)
+ if(encodings[i].type == type && encodings[i].designation == designation)
+ return encodings[i].enc;
+ return NULL;
+}
diff --git a/src/libs/3rdparty/libvterm/src/encoding/DECdrawing.inc b/src/libs/3rdparty/libvterm/src/encoding/DECdrawing.inc
new file mode 100644
index 00000000000..47093ed0a81
--- /dev/null
+++ b/src/libs/3rdparty/libvterm/src/encoding/DECdrawing.inc
@@ -0,0 +1,36 @@
+static const struct StaticTableEncoding encoding_DECdrawing = {
+ { .decode = &decode_table },
+ {
+ [0x60] = 0x25C6,
+ [0x61] = 0x2592,
+ [0x62] = 0x2409,
+ [0x63] = 0x240C,
+ [0x64] = 0x240D,
+ [0x65] = 0x240A,
+ [0x66] = 0x00B0,
+ [0x67] = 0x00B1,
+ [0x68] = 0x2424,
+ [0x69] = 0x240B,
+ [0x6a] = 0x2518,
+ [0x6b] = 0x2510,
+ [0x6c] = 0x250C,
+ [0x6d] = 0x2514,
+ [0x6e] = 0x253C,
+ [0x6f] = 0x23BA,
+ [0x70] = 0x23BB,
+ [0x71] = 0x2500,
+ [0x72] = 0x23BC,
+ [0x73] = 0x23BD,
+ [0x74] = 0x251C,
+ [0x75] = 0x2524,
+ [0x76] = 0x2534,
+ [0x77] = 0x252C,
+ [0x78] = 0x2502,
+ [0x79] = 0x2A7D,
+ [0x7a] = 0x2A7E,
+ [0x7b] = 0x03C0,
+ [0x7c] = 0x2260,
+ [0x7d] = 0x00A3,
+ [0x7e] = 0x00B7,
+ }
+};
diff --git a/src/libs/3rdparty/libvterm/src/encoding/uk.inc b/src/libs/3rdparty/libvterm/src/encoding/uk.inc
new file mode 100644
index 00000000000..da1445decaf
--- /dev/null
+++ b/src/libs/3rdparty/libvterm/src/encoding/uk.inc
@@ -0,0 +1,6 @@
+static const struct StaticTableEncoding encoding_uk = {
+ { .decode = &decode_table },
+ {
+ [0x23] = 0x00a3,
+ }
+};
diff --git a/src/libs/3rdparty/libvterm/src/fullwidth.inc b/src/libs/3rdparty/libvterm/src/fullwidth.inc
new file mode 100644
index 00000000000..a703529a76d
--- /dev/null
+++ b/src/libs/3rdparty/libvterm/src/fullwidth.inc
@@ -0,0 +1,111 @@
+ { 0x1100, 0x115f },
+ { 0x231a, 0x231b },
+ { 0x2329, 0x232a },
+ { 0x23e9, 0x23ec },
+ { 0x23f0, 0x23f0 },
+ { 0x23f3, 0x23f3 },
+ { 0x25fd, 0x25fe },
+ { 0x2614, 0x2615 },
+ { 0x2648, 0x2653 },
+ { 0x267f, 0x267f },
+ { 0x2693, 0x2693 },
+ { 0x26a1, 0x26a1 },
+ { 0x26aa, 0x26ab },
+ { 0x26bd, 0x26be },
+ { 0x26c4, 0x26c5 },
+ { 0x26ce, 0x26ce },
+ { 0x26d4, 0x26d4 },
+ { 0x26ea, 0x26ea },
+ { 0x26f2, 0x26f3 },
+ { 0x26f5, 0x26f5 },
+ { 0x26fa, 0x26fa },
+ { 0x26fd, 0x26fd },
+ { 0x2705, 0x2705 },
+ { 0x270a, 0x270b },
+ { 0x2728, 0x2728 },
+ { 0x274c, 0x274c },
+ { 0x274e, 0x274e },
+ { 0x2753, 0x2755 },
+ { 0x2757, 0x2757 },
+ { 0x2795, 0x2797 },
+ { 0x27b0, 0x27b0 },
+ { 0x27bf, 0x27bf },
+ { 0x2b1b, 0x2b1c },
+ { 0x2b50, 0x2b50 },
+ { 0x2b55, 0x2b55 },
+ { 0x2e80, 0x2e99 },
+ { 0x2e9b, 0x2ef3 },
+ { 0x2f00, 0x2fd5 },
+ { 0x2ff0, 0x2ffb },
+ { 0x3000, 0x303e },
+ { 0x3041, 0x3096 },
+ { 0x3099, 0x30ff },
+ { 0x3105, 0x312f },
+ { 0x3131, 0x318e },
+ { 0x3190, 0x31ba },
+ { 0x31c0, 0x31e3 },
+ { 0x31f0, 0x321e },
+ { 0x3220, 0x3247 },
+ { 0x3250, 0x4dbf },
+ { 0x4e00, 0xa48c },
+ { 0xa490, 0xa4c6 },
+ { 0xa960, 0xa97c },
+ { 0xac00, 0xd7a3 },
+ { 0xf900, 0xfaff },
+ { 0xfe10, 0xfe19 },
+ { 0xfe30, 0xfe52 },
+ { 0xfe54, 0xfe66 },
+ { 0xfe68, 0xfe6b },
+ { 0xff01, 0xff60 },
+ { 0xffe0, 0xffe6 },
+ { 0x16fe0, 0x16fe3 },
+ { 0x17000, 0x187f7 },
+ { 0x18800, 0x18af2 },
+ { 0x1b000, 0x1b11e },
+ { 0x1b150, 0x1b152 },
+ { 0x1b164, 0x1b167 },
+ { 0x1b170, 0x1b2fb },
+ { 0x1f004, 0x1f004 },
+ { 0x1f0cf, 0x1f0cf },
+ { 0x1f18e, 0x1f18e },
+ { 0x1f191, 0x1f19a },
+ { 0x1f200, 0x1f202 },
+ { 0x1f210, 0x1f23b },
+ { 0x1f240, 0x1f248 },
+ { 0x1f250, 0x1f251 },
+ { 0x1f260, 0x1f265 },
+ { 0x1f300, 0x1f320 },
+ { 0x1f32d, 0x1f335 },
+ { 0x1f337, 0x1f37c },
+ { 0x1f37e, 0x1f393 },
+ { 0x1f3a0, 0x1f3ca },
+ { 0x1f3cf, 0x1f3d3 },
+ { 0x1f3e0, 0x1f3f0 },
+ { 0x1f3f4, 0x1f3f4 },
+ { 0x1f3f8, 0x1f43e },
+ { 0x1f440, 0x1f440 },
+ { 0x1f442, 0x1f4fc },
+ { 0x1f4ff, 0x1f53d },
+ { 0x1f54b, 0x1f54e },
+ { 0x1f550, 0x1f567 },
+ { 0x1f57a, 0x1f57a },
+ { 0x1f595, 0x1f596 },
+ { 0x1f5a4, 0x1f5a4 },
+ { 0x1f5fb, 0x1f64f },
+ { 0x1f680, 0x1f6c5 },
+ { 0x1f6cc, 0x1f6cc },
+ { 0x1f6d0, 0x1f6d2 },
+ { 0x1f6d5, 0x1f6d5 },
+ { 0x1f6eb, 0x1f6ec },
+ { 0x1f6f4, 0x1f6fa },
+ { 0x1f7e0, 0x1f7eb },
+ { 0x1f90d, 0x1f971 },
+ { 0x1f973, 0x1f976 },
+ { 0x1f97a, 0x1f9a2 },
+ { 0x1f9a5, 0x1f9aa },
+ { 0x1f9ae, 0x1f9ca },
+ { 0x1f9cd, 0x1f9ff },
+ { 0x1fa70, 0x1fa73 },
+ { 0x1fa78, 0x1fa7a },
+ { 0x1fa80, 0x1fa82 },
+ { 0x1fa90, 0x1fa95 },
diff --git a/src/libs/3rdparty/libvterm/src/keyboard.c b/src/libs/3rdparty/libvterm/src/keyboard.c
new file mode 100644
index 00000000000..d31c8be12d3
--- /dev/null
+++ b/src/libs/3rdparty/libvterm/src/keyboard.c
@@ -0,0 +1,226 @@
+#include "vterm_internal.h"
+
+#include <stdio.h>
+
+#include "utf8.h"
+
+void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod)
+{
+ /* The shift modifier is never important for Unicode characters
+ * apart from Space
+ */
+ if(c != ' ')
+ mod &= ~VTERM_MOD_SHIFT;
+
+ if(mod == 0) {
+ // Normal text - ignore just shift
+ char str[6];
+ int seqlen = fill_utf8(c, str);
+ vterm_push_output_bytes(vt, str, seqlen);
+ return;
+ }
+
+ int needs_CSIu;
+ switch(c) {
+ /* Special Ctrl- letters that can't be represented elsewise */
+ case 'i': case 'j': case 'm': case '[':
+ needs_CSIu = 1;
+ break;
+ /* Ctrl-\ ] ^ _ don't need CSUu */
+ case '\\': case ']': case '^': case '_':
+ needs_CSIu = 0;
+ break;
+ /* Shift-space needs CSIu */
+ case ' ':
+ needs_CSIu = !!(mod & VTERM_MOD_SHIFT);
+ break;
+ /* All other characters needs CSIu except for letters a-z */
+ default:
+ needs_CSIu = (c < 'a' || c > 'z');
+ }
+
+ /* ALT we can just prefix with ESC; anything else requires CSI u */
+ if(needs_CSIu && (mod & ~VTERM_MOD_ALT)) {
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", c, mod+1);
+ return;
+ }
+
+ if(mod & VTERM_MOD_CTRL)
+ c &= 0x1f;
+
+ vterm_push_output_sprintf(vt, "%s%c", mod & VTERM_MOD_ALT ? ESC_S : "", c);
+}
+
+typedef struct {
+ enum {
+ KEYCODE_NONE,
+ KEYCODE_LITERAL,
+ KEYCODE_TAB,
+ KEYCODE_ENTER,
+ KEYCODE_SS3,
+ KEYCODE_CSI,
+ KEYCODE_CSI_CURSOR,
+ KEYCODE_CSINUM,
+ KEYCODE_KEYPAD,
+ } type;
+ char literal;
+ int csinum;
+} keycodes_s;
+
+static keycodes_s keycodes[] = {
+ { KEYCODE_NONE }, // NONE
+
+ { KEYCODE_ENTER, '\r' }, // ENTER
+ { KEYCODE_TAB, '\t' }, // TAB
+ { KEYCODE_LITERAL, '\x7f' }, // BACKSPACE == ASCII DEL
+ { KEYCODE_LITERAL, '\x1b' }, // ESCAPE
+
+ { KEYCODE_CSI_CURSOR, 'A' }, // UP
+ { KEYCODE_CSI_CURSOR, 'B' }, // DOWN
+ { KEYCODE_CSI_CURSOR, 'D' }, // LEFT
+ { KEYCODE_CSI_CURSOR, 'C' }, // RIGHT
+
+ { KEYCODE_CSINUM, '~', 2 }, // INS
+ { KEYCODE_CSINUM, '~', 3 }, // DEL
+ { KEYCODE_CSI_CURSOR, 'H' }, // HOME
+ { KEYCODE_CSI_CURSOR, 'F' }, // END
+ { KEYCODE_CSINUM, '~', 5 }, // PAGEUP
+ { KEYCODE_CSINUM, '~', 6 }, // PAGEDOWN
+};
+
+static keycodes_s keycodes_fn[] = {
+ { KEYCODE_NONE }, // F0 - shouldn't happen
+ { KEYCODE_SS3, 'P' }, // F1
+ { KEYCODE_SS3, 'Q' }, // F2
+ { KEYCODE_SS3, 'R' }, // F3
+ { KEYCODE_SS3, 'S' }, // F4
+ { KEYCODE_CSINUM, '~', 15 }, // F5
+ { KEYCODE_CSINUM, '~', 17 }, // F6
+ { KEYCODE_CSINUM, '~', 18 }, // F7
+ { KEYCODE_CSINUM, '~', 19 }, // F8
+ { KEYCODE_CSINUM, '~', 20 }, // F9
+ { KEYCODE_CSINUM, '~', 21 }, // F10
+ { KEYCODE_CSINUM, '~', 23 }, // F11
+ { KEYCODE_CSINUM, '~', 24 }, // F12
+};
+
+static keycodes_s keycodes_kp[] = {
+ { KEYCODE_KEYPAD, '0', 'p' }, // KP_0
+ { KEYCODE_KEYPAD, '1', 'q' }, // KP_1
+ { KEYCODE_KEYPAD, '2', 'r' }, // KP_2
+ { KEYCODE_KEYPAD, '3', 's' }, // KP_3
+ { KEYCODE_KEYPAD, '4', 't' }, // KP_4
+ { KEYCODE_KEYPAD, '5', 'u' }, // KP_5
+ { KEYCODE_KEYPAD, '6', 'v' }, // KP_6
+ { KEYCODE_KEYPAD, '7', 'w' }, // KP_7
+ { KEYCODE_KEYPAD, '8', 'x' }, // KP_8
+ { KEYCODE_KEYPAD, '9', 'y' }, // KP_9
+ { KEYCODE_KEYPAD, '*', 'j' }, // KP_MULT
+ { KEYCODE_KEYPAD, '+', 'k' }, // KP_PLUS
+ { KEYCODE_KEYPAD, ',', 'l' }, // KP_COMMA
+ { KEYCODE_KEYPAD, '-', 'm' }, // KP_MINUS
+ { KEYCODE_KEYPAD, '.', 'n' }, // KP_PERIOD
+ { KEYCODE_KEYPAD, '/', 'o' }, // KP_DIVIDE
+ { KEYCODE_KEYPAD, '\n', 'M' }, // KP_ENTER
+ { KEYCODE_KEYPAD, '=', 'X' }, // KP_EQUAL
+};
+
+void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod)
+{
+ if(key == VTERM_KEY_NONE)
+ return;
+
+ keycodes_s k;
+ if(key < VTERM_KEY_FUNCTION_0) {
+ if(key >= sizeof(keycodes)/sizeof(keycodes[0]))
+ return;
+ k = keycodes[key];
+ }
+ else if(key >= VTERM_KEY_FUNCTION_0 && key <= VTERM_KEY_FUNCTION_MAX) {
+ if((key - VTERM_KEY_FUNCTION_0) >= sizeof(keycodes_fn)/sizeof(keycodes_fn[0]))
+ return;
+ k = keycodes_fn[key - VTERM_KEY_FUNCTION_0];
+ }
+ else if(key >= VTERM_KEY_KP_0) {
+ if((key - VTERM_KEY_KP_0) >= sizeof(keycodes_kp)/sizeof(keycodes_kp[0]))
+ return;
+ k = keycodes_kp[key - VTERM_KEY_KP_0];
+ }
+
+ switch(k.type) {
+ case KEYCODE_NONE:
+ break;
+
+ case KEYCODE_TAB:
+ /* Shift-Tab is CSI Z but plain Tab is 0x09 */
+ if(mod == VTERM_MOD_SHIFT)
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "Z");
+ else if(mod & VTERM_MOD_SHIFT)
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%dZ", mod+1);
+ else
+ goto case_LITERAL;
+ break;
+
+ case KEYCODE_ENTER:
+ /* Enter is CRLF in newline mode, but just LF in linefeed */
+ if(vt->state->mode.newline)
+ vterm_push_output_sprintf(vt, "\r\n");
+ else
+ goto case_LITERAL;
+ break;
+
+ case KEYCODE_LITERAL: case_LITERAL:
+ if(mod & (VTERM_MOD_SHIFT|VTERM_MOD_CTRL))
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", k.literal, mod+1);
+ else
+ vterm_push_output_sprintf(vt, mod & VTERM_MOD_ALT ? ESC_S "%c" : "%c", k.literal);
+ break;
+
+ case KEYCODE_SS3: case_SS3:
+ if(mod == 0)
+ vterm_push_output_sprintf_ctrl(vt, C1_SS3, "%c", k.literal);
+ else
+ goto case_CSI;
+ break;
+
+ case KEYCODE_CSI: case_CSI:
+ if(mod == 0)
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%c", k.literal);
+ else
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%d%c", mod + 1, k.literal);
+ break;
+
+ case KEYCODE_CSINUM:
+ if(mod == 0)
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d%c", k.csinum, k.literal);
+ else
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%d%c", k.csinum, mod + 1, k.literal);
+ break;
+
+ case KEYCODE_CSI_CURSOR:
+ if(vt->state->mode.cursor)
+ goto case_SS3;
+ else
+ goto case_CSI;
+
+ case KEYCODE_KEYPAD:
+ if(vt->state->mode.keypad) {
+ k.literal = k.csinum;
+ goto case_SS3;
+ }
+ else
+ goto case_LITERAL;
+ }
+}
+
+void vterm_keyboard_start_paste(VTerm *vt)
+{
+ if(vt->state->mode.bracketpaste)
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "200~");
+}
+
+void vterm_keyboard_end_paste(VTerm *vt)
+{
+ if(vt->state->mode.bracketpaste)
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "201~");
+}
diff --git a/src/libs/3rdparty/libvterm/src/mouse.c b/src/libs/3rdparty/libvterm/src/mouse.c
new file mode 100644
index 00000000000..bd713f8106a
--- /dev/null
+++ b/src/libs/3rdparty/libvterm/src/mouse.c
@@ -0,0 +1,99 @@
+#include "vterm_internal.h"
+
+#include "utf8.h"
+
+static void output_mouse(VTermState *state, int code, int pressed, int modifiers, int col, int row)
+{
+ modifiers <<= 2;
+
+ switch(state->mouse_protocol) {
+ case MOUSE_X10:
+ if(col + 0x21 > 0xff)
+ col = 0xff - 0x21;
+ if(row + 0x21 > 0xff)
+ row = 0xff - 0x21;
+
+ if(!pressed)
+ code = 3;
+
+ vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%c%c%c",
+ (code | modifiers) + 0x20, col + 0x21, row + 0x21);
+ break;
+
+ case MOUSE_UTF8:
+ {
+ char utf8[18]; size_t len = 0;
+
+ if(!pressed)
+ code = 3;
+
+ len += fill_utf8((code | modifiers) + 0x20, utf8 + len);
+ len += fill_utf8(col + 0x21, utf8 + len);
+ len += fill_utf8(row + 0x21, utf8 + len);
+ utf8[len] = 0;
+
+ vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%s", utf8);
+ }
+ break;
+
+ case MOUSE_SGR:
+ vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "<%d;%d;%d%c",
+ code | modifiers, col + 1, row + 1, pressed ? 'M' : 'm');
+ break;
+
+ case MOUSE_RXVT:
+ if(!pressed)
+ code = 3;
+
+ vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%d;%d;%dM",
+ code | modifiers, col + 1, row + 1);
+ break;
+ }
+}
+
+void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod)
+{
+ VTermState *state = vt->state;
+
+ if(col == state->mouse_col && row == state->mouse_row)
+ return;
+
+ state->mouse_col = col;
+ state->mouse_row = row;
+
+ if((state->mouse_flags & MOUSE_WANT_DRAG && state->mouse_buttons) ||
+ (state->mouse_flags & MOUSE_WANT_MOVE)) {
+ int button = state->mouse_buttons & 0x01 ? 1 :
+ state->mouse_buttons & 0x02 ? 2 :
+ state->mouse_buttons & 0x04 ? 3 : 4;
+ output_mouse(state, button-1 + 0x20, 1, mod, col, row);
+ }
+}
+
+void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod)
+{
+ VTermState *state = vt->state;
+
+ int old_buttons = state->mouse_buttons;
+
+ if(button > 0 && button <= 3) {
+ if(pressed)
+ state->mouse_buttons |= (1 << (button-1));
+ else
+ state->mouse_buttons &= ~(1 << (button-1));
+ }
+
+ /* Most of the time we don't get button releases from 4/5 */
+ if(state->mouse_buttons == old_buttons && button < 4)
+ return;
+
+ if(!state->mouse_flags)
+ return;
+
+ if(button < 4) {
+ output_mouse(state, button-1, pressed, mod, state->mouse_col, state->mouse_row);
+ }
+ else if(button < 6) {
+ output_mouse(state, button-4 + 0x40, pressed, mod, state->mouse_col, state->mouse_row);
+ }
+}
diff --git a/src/libs/3rdparty/libvterm/src/parser.c b/src/libs/3rdparty/libvterm/src/parser.c
new file mode 100644
index 00000000000..b43a549cefc
--- /dev/null
+++ b/src/libs/3rdparty/libvterm/src/parser.c
@@ -0,0 +1,402 @@
+#include "vterm_internal.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#undef DEBUG_PARSER
+
+static bool is_intermed(unsigned char c)
+{
+ return c >= 0x20 && c <= 0x2f;
+}
+
+static void do_control(VTerm *vt, unsigned char control)
+{
+ if(vt->parser.callbacks && vt->parser.callbacks->control)
+ if((*vt->parser.callbacks->control)(control, vt->parser.cbdata))
+ return;
+
+ DEBUG_LOG("libvterm: Unhandled control 0x%02x\n", control);
+}
+
+static void do_csi(VTerm *vt, char command)
+{
+#ifdef DEBUG_PARSER
+ printf("Parsed CSI args as:\n", arglen, args);
+ printf(" leader: %s\n", vt->parser.v.csi.leader);
+ for(int argi = 0; argi < vt->parser.v.csi.argi; argi++) {
+ printf(" %lu", CSI_ARG(vt->parser.v.csi.args[argi]));
+ if(!CSI_ARG_HAS_MORE(vt->parser.v.csi.args[argi]))
+ printf("\n");
+ printf(" intermed: %s\n", vt->parser.intermed);
+ }
+#endif
+
+ if(vt->parser.callbacks && vt->parser.callbacks->csi)
+ if((*vt->parser.callbacks->csi)(
+ vt->parser.v.csi.leaderlen ? vt->parser.v.csi.leader : NULL,
+ vt->parser.v.csi.args,
+ vt->parser.v.csi.argi,
+ vt->parser.intermedlen ? vt->parser.intermed : NULL,
+ command,
+ vt->parser.cbdata))
+ return;
+
+ DEBUG_LOG("libvterm: Unhandled CSI %c\n", command);
+}
+
+static void do_escape(VTerm *vt, char command)
+{
+ char seq[INTERMED_MAX+1];
+
+ size_t len = vt->parser.intermedlen;
+ strncpy(seq, vt->parser.intermed, len);
+ seq[len++] = command;
+ seq[len] = 0;
+
+ if(vt->parser.callbacks && vt->parser.callbacks->escape)
+ if((*vt->parser.callbacks->escape)(seq, len, vt->parser.cbdata))
+ return;
+
+ DEBUG_LOG("libvterm: Unhandled escape ESC 0x%02x\n", command);
+}
+
+static void string_fragment(VTerm *vt, const char *str, size_t len, bool final)
+{
+ VTermStringFragment frag = {
+ .str = str,
+ .len = len,
+ .initial = vt->parser.string_initial,
+ .final = final,
+ };
+
+ switch(vt->parser.state) {
+ case OSC:
+ if(vt->parser.callbacks && vt->parser.callbacks->osc)
+ (*vt->parser.callbacks->osc)(vt->parser.v.osc.command, frag, vt->parser.cbdata);
+ break;
+
+ case DCS:
+ if(vt->parser.callbacks && vt->parser.callbacks->dcs)
+ (*vt->parser.callbacks->dcs)(vt->parser.v.dcs.command, vt->parser.v.dcs.commandlen, frag, vt->parser.cbdata);
+ break;
+
+ case APC:
+ if(vt->parser.callbacks && vt->parser.callbacks->apc)
+ (*vt->parser.callbacks->apc)(frag, vt->parser.cbdata);
+ break;
+
+ case PM:
+ if(vt->parser.callbacks && vt->parser.callbacks->pm)
+ (*vt->parser.callbacks->pm)(frag, vt->parser.cbdata);
+ break;
+
+ case SOS:
+ if(vt->parser.callbacks && vt->parser.callbacks->sos)
+ (*vt->parser.callbacks->sos)(frag, vt->parser.cbdata);
+ break;
+
+ case NORMAL:
+ case CSI_LEADER:
+ case CSI_ARGS:
+ case CSI_INTERMED:
+ case OSC_COMMAND:
+ case DCS_COMMAND:
+ break;
+ }
+
+ vt->parser.string_initial = false;
+}
+
+size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len)
+{
+ size_t pos = 0;
+ const char *string_start;
+
+ switch(vt->parser.state) {
+ case NORMAL:
+ case CSI_LEADER:
+ case CSI_ARGS:
+ case CSI_INTERMED:
+ case OSC_COMMAND:
+ case DCS_COMMAND:
+ string_start = NULL;
+ break;
+ case OSC:
+ case DCS:
+ case APC:
+ case PM:
+ case SOS:
+ string_start = bytes;
+ break;
+ }
+
+#define ENTER_STATE(st) do { vt->parser.state = st; string_start = NULL; } while(0)
+#define ENTER_NORMAL_STATE() ENTER_STATE(NORMAL)
+
+#define IS_STRING_STATE() (vt->parser.state >= OSC_COMMAND)
+
+ for( ; pos < len; pos++) {
+ unsigned char c = bytes[pos];
+ bool c1_allowed = !vt->mode.utf8;
+
+ if(c == 0x00 || c == 0x7f) { // NUL, DEL
+ if(IS_STRING_STATE()) {
+ string_fragment(vt, string_start, bytes + pos - string_start, false);
+ string_start = bytes + pos + 1;
+ }
+ if(vt->parser.emit_nul)
+ do_control(vt, c);
+ continue;
+ }
+ if(c == 0x18 || c == 0x1a) { // CAN, SUB
+ vt->parser.in_esc = false;
+ ENTER_NORMAL_STATE();
+ if(vt->parser.emit_nul)
+ do_control(vt, c);
+ continue;
+ }
+ else if(c == 0x1b) { // ESC
+ vt->parser.intermedlen = 0;
+ if(!IS_STRING_STATE())
+ vt->parser.state = NORMAL;
+ vt->parser.in_esc = true;
+ continue;
+ }
+ else if(c == 0x07 && // BEL, can stand for ST in OSC or DCS state
+ IS_STRING_STATE()) {
+ // fallthrough
+ }
+ else if(c < 0x20) { // other C0
+ if(vt->parser.state == SOS)
+ continue; // All other C0s permitted in SOS
+
+ if(IS_STRING_STATE())
+ string_fragment(vt, string_start, bytes + pos - string_start, false);
+ do_control(vt, c);
+ if(IS_STRING_STATE())
+ string_start = bytes + pos + 1;
+ continue;
+ }
+ // else fallthrough
+
+ size_t string_len = bytes + pos - string_start;
+
+ if(vt->parser.in_esc) {
+ // Hoist an ESC letter into a C1 if we're not in a string mode
+ // Always accept ESC \ == ST even in string mode
+ if(!vt->parser.intermedlen &&
+ c >= 0x40 && c < 0x60 &&
+ ((!IS_STRING_STATE() || c == 0x5c))) {
+ c += 0x40;
+ c1_allowed = true;
+ if(string_len)
+ string_len -= 1;
+ vt->parser.in_esc = false;
+ }
+ else {
+ string_start = NULL;
+ vt->parser.state = NORMAL;
+ }
+ }
+
+ switch(vt->parser.state) {
+ case CSI_LEADER:
+ /* Extract leader bytes 0x3c to 0x3f */
+ if(c >= 0x3c && c <= 0x3f) {
+ if(vt->parser.v.csi.leaderlen < CSI_LEADER_MAX-1)
+ vt->parser.v.csi.leader[vt->parser.v.csi.leaderlen++] = c;
+ break;
+ }
+
+ /* else fallthrough */
+ vt->parser.v.csi.leader[vt->parser.v.csi.leaderlen] = 0;
+
+ vt->parser.v.csi.argi = 0;
+ vt->parser.v.csi.args[0] = CSI_ARG_MISSING;
+ vt->parser.state = CSI_ARGS;
+
+ /* fallthrough */
+ case CSI_ARGS:
+ /* Numerical value of argument */
+ if(c >= '0' && c <= '9') {
+ if(vt->parser.v.csi.args[vt->parser.v.csi.argi] == CSI_ARG_MISSING)
+ vt->parser.v.csi.args[vt->parser.v.csi.argi] = 0;
+ vt->parser.v.csi.args[vt->parser.v.csi.argi] *= 10;
+ vt->parser.v.csi.args[vt->parser.v.csi.argi] += c - '0';
+ break;
+ }
+ if(c == ':') {
+ vt->parser.v.csi.args[vt->parser.v.csi.argi] |= CSI_ARG_FLAG_MORE;
+ c = ';';
+ }
+ if(c == ';') {
+ vt->parser.v.csi.argi++;
+ vt->parser.v.csi.args[vt->parser.v.csi.argi] = CSI_ARG_MISSING;
+ break;
+ }
+
+ /* else fallthrough */
+ vt->parser.v.csi.argi++;
+ vt->parser.intermedlen = 0;
+ vt->parser.state = CSI_INTERMED;
+ case CSI_INTERMED:
+ if(is_intermed(c)) {
+ if(vt->parser.intermedlen < INTERMED_MAX-1)
+ vt->parser.intermed[vt->parser.intermedlen++] = c;
+ break;
+ }
+ else if(c == 0x1b) {
+ /* ESC in CSI cancels */
+ }
+ else if(c >= 0x40 && c <= 0x7e) {
+ vt->parser.intermed[vt->parser.intermedlen] = 0;
+ do_csi(vt, c);
+ }
+ /* else was invalid CSI */
+
+ ENTER_NORMAL_STATE();
+ break;
+
+ case OSC_COMMAND:
+ /* Numerical value of command */
+ if(c >= '0' && c <= '9') {
+ if(vt->parser.v.osc.command == -1)
+ vt->parser.v.osc.command = 0;
+ else
+ vt->parser.v.osc.command *= 10;
+ vt->parser.v.osc.command += c - '0';
+ break;
+ }
+ if(c == ';') {
+ vt->parser.state = OSC;
+ string_start = bytes + pos + 1;
+ break;
+ }
+
+ /* else fallthrough */
+ string_start = bytes + pos;
+ string_len = 0;
+ vt->parser.state = OSC;
+ goto string_state;
+
+ case DCS_COMMAND:
+ if(vt->parser.v.dcs.commandlen < CSI_LEADER_MAX)
+ vt->parser.v.dcs.command[vt->parser.v.dcs.commandlen++] = c;
+
+ if(c >= 0x40 && c<= 0x7e) {
+ string_start = bytes + pos + 1;
+ vt->parser.state = DCS;
+ }
+ break;
+
+string_state:
+ case OSC:
+ case DCS:
+ case APC:
+ case PM:
+ case SOS:
+ if(c == 0x07 || (c1_allowed && c == 0x9c)) {
+ string_fragment(vt, string_start, string_len, true);
+ ENTER_NORMAL_STATE();
+ }
+ break;
+
+ case NORMAL:
+ if(vt->parser.in_esc) {
+ if(is_intermed(c)) {
+ if(vt->parser.intermedlen < INTERMED_MAX-1)
+ vt->parser.intermed[vt->parser.intermedlen++] = c;
+ }
+ else if(c >= 0x30 && c < 0x7f) {
+ do_escape(vt, c);
+ vt->parser.in_esc = 0;
+ ENTER_NORMAL_STATE();
+ }
+ else {
+ DEBUG_LOG("TODO: Unhandled byte %02x in Escape\n", c);
+ }
+ break;
+ }
+ if(c1_allowed && c >= 0x80 && c < 0xa0) {
+ switch(c) {
+ case 0x90: // DCS
+ vt->parser.string_initial = true;
+ vt->parser.v.dcs.commandlen = 0;
+ ENTER_STATE(DCS_COMMAND);
+ break;
+ case 0x98: // SOS
+ vt->parser.string_initial = true;
+ ENTER_STATE(SOS);
+ string_start = bytes + pos + 1;
+ string_len = 0;
+ break;
+ case 0x9b: // CSI
+ vt->parser.v.csi.leaderlen = 0;
+ ENTER_STATE(CSI_LEADER);
+ break;
+ case 0x9d: // OSC
+ vt->parser.v.osc.command = -1;
+ vt->parser.string_initial = true;
+ string_start = bytes + pos + 1;
+ ENTER_STATE(OSC_COMMAND);
+ break;
+ case 0x9e: // PM
+ vt->parser.string_initial = true;
+ ENTER_STATE(PM);
+ string_start = bytes + pos + 1;
+ string_len = 0;
+ break;
+ case 0x9f: // APC
+ vt->parser.string_initial = true;
+ ENTER_STATE(APC);
+ string_start = bytes + pos + 1;
+ string_len = 0;
+ break;
+ default:
+ do_control(vt, c);
+ break;
+ }
+ }
+ else {
+ size_t eaten = 0;
+ if(vt->parser.callbacks && vt->parser.callbacks->text)
+ eaten = (*vt->parser.callbacks->text)(bytes + pos, len - pos, vt->parser.cbdata);
+
+ if(!eaten) {
+ DEBUG_LOG("libvterm: Text callback did not consume any input\n");
+ /* force it to make progress */
+ eaten = 1;
+ }
+
+ pos += (eaten - 1); // we'll ++ it again in a moment
+ }
+ break;
+ }
+ }
+
+ if(string_start) {
+ size_t string_len = bytes + pos - string_start;
+ if(vt->parser.in_esc)
+ string_len -= 1;
+ string_fragment(vt, string_start, string_len, false);
+ }
+
+ return len;
+}
+
+void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user)
+{
+ vt->parser.callbacks = callbacks;
+ vt->parser.cbdata = user;
+}
+
+void *vterm_parser_get_cbdata(VTerm *vt)
+{
+ return vt->parser.cbdata;
+}
+
+void vterm_parser_set_emit_nul(VTerm *vt, bool emit)
+{
+ vt->parser.emit_nul = emit;
+}
diff --git a/src/libs/3rdparty/libvterm/src/pen.c b/src/libs/3rdparty/libvterm/src/pen.c
new file mode 100644
index 00000000000..891a45cec78
--- /dev/null
+++ b/src/libs/3rdparty/libvterm/src/pen.c
@@ -0,0 +1,613 @@
+#include "vterm_internal.h"
+
+#include <stdio.h>
+
+/**
+ * Structure used to store RGB triples without the additional metadata stored in
+ * VTermColor.
+ */
+typedef struct {
+ uint8_t red, green, blue;
+} VTermRGB;
+
+static const VTermRGB ansi_colors[] = {
+ /* R G B */
+ { 0, 0, 0 }, // black
+ { 224, 0, 0 }, // red
+ { 0, 224, 0 }, // green
+ { 224, 224, 0 }, // yellow
+ { 0, 0, 224 }, // blue
+ { 224, 0, 224 }, // magenta
+ { 0, 224, 224 }, // cyan
+ { 224, 224, 224 }, // white == light grey
+
+ // high intensity
+ { 128, 128, 128 }, // black
+ { 255, 64, 64 }, // red
+ { 64, 255, 64 }, // green
+ { 255, 255, 64 }, // yellow
+ { 64, 64, 255 }, // blue
+ { 255, 64, 255 }, // magenta
+ { 64, 255, 255 }, // cyan
+ { 255, 255, 255 }, // white for real
+};
+
+static int ramp6[] = {
+ 0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF,
+};
+
+static int ramp24[] = {
+ 0x00, 0x0B, 0x16, 0x21, 0x2C, 0x37, 0x42, 0x4D, 0x58, 0x63, 0x6E, 0x79,
+ 0x85, 0x90, 0x9B, 0xA6, 0xB1, 0xBC, 0xC7, 0xD2, 0xDD, 0xE8, 0xF3, 0xFF,
+};
+
+static void lookup_default_colour_ansi(long idx, VTermColor *col)
+{
+ if (idx >= 0 && idx < 16) {
+ vterm_color_rgb(
+ col,
+ ansi_colors[idx].red, ansi_colors[idx].green, ansi_colors[idx].blue);
+ }
+}
+
+static bool lookup_colour_ansi(const VTermState *state, long index, VTermColor *col)
+{
+ if(index >= 0 && index < 16) {
+ *col = state->colors[index];
+ return true;
+ }
+
+ return false;
+}
+
+static bool lookup_colour_palette(const VTermState *state, long index, VTermColor *col)
+{
+ if(index >= 0 && index < 16) {
+ // Normal 8 colours or high intensity - parse as palette 0
+ return lookup_colour_ansi(state, index, col);
+ }
+ else if(index >= 16 && index < 232) {
+ // 216-colour cube
+ index -= 16;
+
+ vterm_color_rgb(col, ramp6[index/6/6 % 6],
+ ramp6[index/6 % 6],
+ ramp6[index % 6]);
+
+ return true;
+ }
+ else if(index >= 232 && index < 256) {
+ // 24 greyscales
+ index -= 232;
+
+ vterm_color_rgb(col, ramp24[index], ramp24[index], ramp24[index]);
+
+ return true;
+ }
+
+ return false;
+}
+
+static int lookup_colour(const VTermState *state, int palette, const long args[], int argcount, VTermColor *col)
+{
+ switch(palette) {
+ case 2: // RGB mode - 3 args contain colour values directly
+ if(argcount < 3)
+ return argcount;
+
+ vterm_color_rgb(col, CSI_ARG(args[0]), CSI_ARG(args[1]), CSI_ARG(args[2]));
+
+ return 3;
+
+ case 5: // XTerm 256-colour mode
+ if (!argcount || CSI_ARG_IS_MISSING(args[0])) {
+ return argcount ? 1 : 0;
+ }
+
+ vterm_color_indexed(col, args[0]);
+
+ return argcount ? 1 : 0;
+
+ default:
+ DEBUG_LOG("Unrecognised colour palette %d\n", palette);
+ return 0;
+ }
+}
+
+// Some conveniences
+
+static void setpenattr(VTermState *state, VTermAttr attr, VTermValueType type, VTermValue *val)
+{
+#ifdef DEBUG
+ if(type != vterm_get_attr_type(attr)) {
+ DEBUG_LOG("Cannot set attr %d as it has type %d, not type %d\n",
+ attr, vterm_get_attr_type(attr), type);
+ return;
+ }
+#endif
+ if(state->callbacks && state->callbacks->setpenattr)
+ (*state->callbacks->setpenattr)(attr, val, state->cbdata);
+}
+
+static void setpenattr_bool(VTermState *state, VTermAttr attr, int boolean)
+{
+ VTermValue val = { .boolean = boolean };
+ setpenattr(state, attr, VTERM_VALUETYPE_BOOL, &val);
+}
+
+static void setpenattr_int(VTermState *state, VTermAttr attr, int number)
+{
+ VTermValue val = { .number = number };
+ setpenattr(state, attr, VTERM_VALUETYPE_INT, &val);
+}
+
+static void setpenattr_col(VTermState *state, VTermAttr attr, VTermColor color)
+{
+ VTermValue val = { .color = color };
+ setpenattr(state, attr, VTERM_VALUETYPE_COLOR, &val);
+}
+
+static void set_pen_col_ansi(VTermState *state, VTermAttr attr, long col)
+{
+ VTermColor *colp = (attr == VTERM_ATTR_BACKGROUND) ? &state->pen.bg : &state->pen.fg;
+
+ vterm_color_indexed(colp, col);
+
+ setpenattr_col(state, attr, *colp);
+}
+
+INTERNAL void vterm_state_newpen(VTermState *state)
+{
+ // 90% grey so that pure white is brighter
+ vterm_color_rgb(&state->default_fg, 240, 240, 240);
+ vterm_color_rgb(&state->default_bg, 0, 0, 0);
+ vterm_state_set_default_colors(state, &state->default_fg, &state->default_bg);
+
+ for(int col = 0; col < 16; col++)
+ lookup_default_colour_ansi(col, &state->colors[col]);
+}
+
+INTERNAL void vterm_state_resetpen(VTermState *state)
+{
+ state->pen.bold = 0; setpenattr_bool(state, VTERM_ATTR_BOLD, 0);
+ state->pen.underline = 0; setpenattr_int (state, VTERM_ATTR_UNDERLINE, 0);
+ state->pen.italic = 0; setpenattr_bool(state, VTERM_ATTR_ITALIC, 0);
+ state->pen.blink = 0; setpenattr_bool(state, VTERM_ATTR_BLINK, 0);
+ state->pen.reverse = 0; setpenattr_bool(state, VTERM_ATTR_REVERSE, 0);
+ state->pen.conceal = 0; setpenattr_bool(state, VTERM_ATTR_CONCEAL, 0);
+ state->pen.strike = 0; setpenattr_bool(state, VTERM_ATTR_STRIKE, 0);
+ state->pen.font = 0; setpenattr_int (state, VTERM_ATTR_FONT, 0);
+ state->pen.small = 0; setpenattr_bool(state, VTERM_ATTR_SMALL, 0);
+ state->pen.baseline = 0; setpenattr_int (state, VTERM_ATTR_BASELINE, 0);
+
+ state->pen.fg = state->default_fg; setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->default_fg);
+ state->pen.bg = state->default_bg; setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->default_bg);
+}
+
+INTERNAL void vterm_state_savepen(VTermState *state, int save)
+{
+ if(save) {
+ state->saved.pen = state->pen;
+ }
+ else {
+ state->pen = state->saved.pen;
+
+ setpenattr_bool(state, VTERM_ATTR_BOLD, state->pen.bold);
+ setpenattr_int (state, VTERM_ATTR_UNDERLINE, state->pen.underline);
+ setpenattr_bool(state, VTERM_ATTR_ITALIC, state->pen.italic);
+ setpenattr_bool(state, VTERM_ATTR_BLINK, state->pen.blink);
+ setpenattr_bool(state, VTERM_ATTR_REVERSE, state->pen.reverse);
+ setpenattr_bool(state, VTERM_ATTR_CONCEAL, state->pen.conceal);
+ setpenattr_bool(state, VTERM_ATTR_STRIKE, state->pen.strike);
+ setpenattr_int (state, VTERM_ATTR_FONT, state->pen.font);
+ setpenattr_bool(state, VTERM_ATTR_SMALL, state->pen.small);
+ setpenattr_int (state, VTERM_ATTR_BASELINE, state->pen.baseline);
+
+ setpenattr_col( state, VTERM_ATTR_FOREGROUND, state->pen.fg);
+ setpenattr_col( state, VTERM_ATTR_BACKGROUND, state->pen.bg);
+ }
+}
+
+int vterm_color_is_equal(const VTermColor *a, const VTermColor *b)
+{
+ /* First make sure that the two colours are of the same type (RGB/Indexed) */
+ if (a->type != b->type) {
+ return false;
+ }
+
+ /* Depending on the type inspect the corresponding members */
+ if (VTERM_COLOR_IS_INDEXED(a)) {
+ return a->indexed.idx == b->indexed.idx;
+ }
+ else if (VTERM_COLOR_IS_RGB(a)) {
+ return (a->rgb.red == b->rgb.red)
+ && (a->rgb.green == b->rgb.green)
+ && (a->rgb.blue == b->rgb.blue);
+ }
+
+ return 0;
+}
+
+void vterm_state_get_default_colors(const VTermState *state, VTermColor *default_fg, VTermColor *default_bg)
+{
+ *default_fg = state->default_fg;
+ *default_bg = state->default_bg;
+}
+
+void vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col)
+{
+ lookup_colour_palette(state, index, col);
+}
+
+void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg)
+{
+ if(default_fg) {
+ state->default_fg = *default_fg;
+ state->default_fg.type = (state->default_fg.type & ~VTERM_COLOR_DEFAULT_MASK)
+ | VTERM_COLOR_DEFAULT_FG;
+ }
+
+ if(default_bg) {
+ state->default_bg = *default_bg;
+ state->default_bg.type = (state->default_bg.type & ~VTERM_COLOR_DEFAULT_MASK)
+ | VTERM_COLOR_DEFAULT_BG;
+ }
+}
+
+void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col)
+{
+ if(index >= 0 && index < 16)
+ state->colors[index] = *col;
+}
+
+void vterm_state_convert_color_to_rgb(const VTermState *state, VTermColor *col)
+{
+ if (VTERM_COLOR_IS_INDEXED(col)) { /* Convert indexed colors to RGB */
+ lookup_colour_palette(state, col->indexed.idx, col);
+ }
+ col->type &= VTERM_COLOR_TYPE_MASK; /* Reset any metadata but the type */
+}
+
+void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright)
+{
+ state->bold_is_highbright = bold_is_highbright;
+}
+
+INTERNAL void vterm_state_setpen(VTermState *state, const long args[], int argcount)
+{
+ // SGR - ECMA-48 8.3.117
+
+ int argi = 0;
+ int value;
+
+ while(argi < argcount) {
+ // This logic is easier to do 'done' backwards; set it true, and make it
+ // false again in the 'default' case
+ int done = 1;
+
+ long arg;
+ switch(arg = CSI_ARG(args[argi])) {
+ case CSI_ARG_MISSING:
+ case 0: // Reset
+ vterm_state_resetpen(state);
+ break;
+
+ case 1: { // Bold on
+ const VTermColor *fg = &state->pen.fg;
+ state->pen.bold = 1;
+ setpenattr_bool(state, VTERM_ATTR_BOLD, 1);
+ if(!VTERM_COLOR_IS_DEFAULT_FG(fg) && VTERM_COLOR_IS_INDEXED(fg) && fg->indexed.idx < 8 && state->bold_is_highbright)
+ set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, fg->indexed.idx + (state->pen.bold ? 8 : 0));
+ break;
+ }
+
+ case 3: // Italic on
+ state->pen.italic = 1;
+ setpenattr_bool(state, VTERM_ATTR_ITALIC, 1);
+ break;
+
+ case 4: // Underline
+ state->pen.underline = VTERM_UNDERLINE_SINGLE;
+ if(CSI_ARG_HAS_MORE(args[argi])) {
+ argi++;
+ switch(CSI_ARG(args[argi])) {
+ case 0:
+ state->pen.underline = 0;
+ break;
+ case 1:
+ state->pen.underline = VTERM_UNDERLINE_SINGLE;
+ break;
+ case 2:
+ state->pen.underline = VTERM_UNDERLINE_DOUBLE;
+ break;
+ case 3:
+ state->pen.underline = VTERM_UNDERLINE_CURLY;
+ break;
+ case 4:
+ state->pen.underline = VTERM_UNDERLINE_DOTTED;
+ break;
+ case 5:
+ state->pen.underline = VTERM_UNDERLINE_DASHED;
+ break;
+ }
+ }
+ setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline);
+ break;
+
+ case 5: // Blink
+ state->pen.blink = 1;
+ setpenattr_bool(state, VTERM_ATTR_BLINK, 1);
+ break;
+
+ case 7: // Reverse on
+ state->pen.reverse = 1;
+ setpenattr_bool(state, VTERM_ATTR_REVERSE, 1);
+ break;
+
+ case 8: // Conceal on
+ state->pen.conceal = 1;
+ setpenattr_bool(state, VTERM_ATTR_CONCEAL, 1);
+ break;
+
+ case 9: // Strikethrough on
+ state->pen.strike = 1;
+ setpenattr_bool(state, VTERM_ATTR_STRIKE, 1);
+ break;
+
+ case 10: case 11: case 12: case 13: case 14:
+ case 15: case 16: case 17: case 18: case 19: // Select font
+ state->pen.font = CSI_ARG(args[argi]) - 10;
+ setpenattr_int(state, VTERM_ATTR_FONT, state->pen.font);
+ break;
+
+ case 21: // Underline double
+ state->pen.underline = VTERM_UNDERLINE_DOUBLE;
+ setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline);
+ break;
+
+ case 22: // Bold off
+ state->pen.bold = 0;
+ setpenattr_bool(state, VTERM_ATTR_BOLD, 0);
+ break;
+
+ case 23: // Italic and Gothic (currently unsupported) off
+ state->pen.italic = 0;
+ setpenattr_bool(state, VTERM_ATTR_ITALIC, 0);
+ break;
+
+ case 24: // Underline off
+ state->pen.underline = 0;
+ setpenattr_int(state, VTERM_ATTR_UNDERLINE, 0);
+ break;
+
+ case 25: // Blink off
+ state->pen.blink = 0;
+ setpenattr_bool(state, VTERM_ATTR_BLINK, 0);
+ break;
+
+ case 27: // Reverse off
+ state->pen.reverse = 0;
+ setpenattr_bool(state, VTERM_ATTR_REVERSE, 0);
+ break;
+
+ case 28: // Conceal off (Reveal)
+ state->pen.conceal = 0;
+ setpenattr_bool(state, VTERM_ATTR_CONCEAL, 0);
+ break;
+
+ case 29: // Strikethrough off
+ state->pen.strike = 0;
+ setpenattr_bool(state, VTERM_ATTR_STRIKE, 0);
+ break;
+
+ case 30: case 31: case 32: case 33:
+ case 34: case 35: case 36: case 37: // Foreground colour palette
+ value = CSI_ARG(args[argi]) - 30;
+ if(state->pen.bold && state->bold_is_highbright)
+ value += 8;
+ set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value);
+ break;
+
+ case 38: // Foreground colour alternative palette
+ if(argcount - argi < 1)
+ return;
+ argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.fg);
+ setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg);
+ break;
+
+ case 39: // Foreground colour default
+ state->pen.fg = state->default_fg;
+ setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg);
+ break;
+
+ case 40: case 41: case 42: case 43:
+ case 44: case 45: case 46: case 47: // Background colour palette
+ value = CSI_ARG(args[argi]) - 40;
+ set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value);
+ break;
+
+ case 48: // Background colour alternative palette
+ if(argcount - argi < 1)
+ return;
+ argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.bg);
+ setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg);
+ break;
+
+ case 49: // Default background
+ state->pen.bg = state->default_bg;
+ setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg);
+ break;
+
+ case 73: // Superscript
+ case 74: // Subscript
+ case 75: // Superscript/subscript off
+ state->pen.small = (arg != 75);
+ state->pen.baseline =
+ (arg == 73) ? VTERM_BASELINE_RAISE :
+ (arg == 74) ? VTERM_BASELINE_LOWER :
+ VTERM_BASELINE_NORMAL;
+ setpenattr_bool(state, VTERM_ATTR_SMALL, state->pen.small);
+ setpenattr_int (state, VTERM_ATTR_BASELINE, state->pen.baseline);
+ break;
+
+ case 90: case 91: case 92: case 93:
+ case 94: case 95: case 96: case 97: // Foreground colour high-intensity palette
+ value = CSI_ARG(args[argi]) - 90 + 8;
+ set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value);
+ break;
+
+ case 100: case 101: case 102: case 103:
+ case 104: case 105: case 106: case 107: // Background colour high-intensity palette
+ value = CSI_ARG(args[argi]) - 100 + 8;
+ set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value);
+ break;
+
+ default:
+ done = 0;
+ break;
+ }
+
+ if(!done)
+ DEBUG_LOG("libvterm: Unhandled CSI SGR %ld\n", arg);
+
+ while(CSI_ARG_HAS_MORE(args[argi++]));
+ }
+}
+
+static int vterm_state_getpen_color(const VTermColor *col, int argi, long args[], int fg)
+{
+ /* Do nothing if the given color is the default color */
+ if (( fg && VTERM_COLOR_IS_DEFAULT_FG(col)) ||
+ (!fg && VTERM_COLOR_IS_DEFAULT_BG(col))) {
+ return argi;
+ }
+
+ /* Decide whether to send an indexed color or an RGB color */
+ if (VTERM_COLOR_IS_INDEXED(col)) {
+ const uint8_t idx = col->indexed.idx;
+ if (idx < 8) {
+ args[argi++] = (idx + (fg ? 30 : 40));
+ }
+ else if (idx < 16) {
+ args[argi++] = (idx - 8 + (fg ? 90 : 100));
+ }
+ else {
+ args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48);
+ args[argi++] = CSI_ARG_FLAG_MORE | 5;
+ args[argi++] = idx;
+ }
+ }
+ else if (VTERM_COLOR_IS_RGB(col)) {
+ args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48);
+ args[argi++] = CSI_ARG_FLAG_MORE | 2;
+ args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.red;
+ args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.green;
+ args[argi++] = col->rgb.blue;
+ }
+ return argi;
+}
+
+INTERNAL int vterm_state_getpen(VTermState *state, long args[], int argcount)
+{
+ int argi = 0;
+
+ if(state->pen.bold)
+ args[argi++] = 1;
+
+ if(state->pen.italic)
+ args[argi++] = 3;
+
+ if(state->pen.underline == VTERM_UNDERLINE_SINGLE)
+ args[argi++] = 4;
+ if(state->pen.underline == VTERM_UNDERLINE_CURLY)
+ args[argi++] = 4 | CSI_ARG_FLAG_MORE, args[argi++] = 3;
+
+ if(state->pen.blink)
+ args[argi++] = 5;
+
+ if(state->pen.reverse)
+ args[argi++] = 7;
+
+ if(state->pen.conceal)
+ args[argi++] = 8;
+
+ if(state->pen.strike)
+ args[argi++] = 9;
+
+ if(state->pen.font)
+ args[argi++] = 10 + state->pen.font;
+
+ if(state->pen.underline == VTERM_UNDERLINE_DOUBLE)
+ args[argi++] = 21;
+
+ argi = vterm_state_getpen_color(&state->pen.fg, argi, args, true);
+
+ argi = vterm_state_getpen_color(&state->pen.bg, argi, args, false);
+
+ if(state->pen.small) {
+ if(state->pen.baseline == VTERM_BASELINE_RAISE)
+ args[argi++] = 73;
+ else if(state->pen.baseline == VTERM_BASELINE_LOWER)
+ args[argi++] = 74;
+ }
+
+ return argi;
+}
+
+int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val)
+{
+ switch(attr) {
+ case VTERM_ATTR_BOLD:
+ val->boolean = state->pen.bold;
+ return 1;
+
+ case VTERM_ATTR_UNDERLINE:
+ val->number = state->pen.underline;
+ return 1;
+
+ case VTERM_ATTR_ITALIC:
+ val->boolean = state->pen.italic;
+ return 1;
+
+ case VTERM_ATTR_BLINK:
+ val->boolean = state->pen.blink;
+ return 1;
+
+ case VTERM_ATTR_REVERSE:
+ val->boolean = state->pen.reverse;
+ return 1;
+
+ case VTERM_ATTR_CONCEAL:
+ val->boolean = state->pen.conceal;
+ return 1;
+
+ case VTERM_ATTR_STRIKE:
+ val->boolean = state->pen.strike;
+ return 1;
+
+ case VTERM_ATTR_FONT:
+ val->number = state->pen.font;
+ return 1;
+
+ case VTERM_ATTR_FOREGROUND:
+ val->color = state->pen.fg;
+ return 1;
+
+ case VTERM_ATTR_BACKGROUND:
+ val->color = state->pen.bg;
+ return 1;
+
+ case VTERM_ATTR_SMALL:
+ val->boolean = state->pen.small;
+ return 1;
+
+ case VTERM_ATTR_BASELINE:
+ val->number = state->pen.baseline;
+ return 1;
+
+ case VTERM_N_ATTRS:
+ return 0;
+ }
+
+ return 0;
+}
diff --git a/src/libs/3rdparty/libvterm/src/rect.h b/src/libs/3rdparty/libvterm/src/rect.h
new file mode 100644
index 00000000000..2114f24c1be
--- /dev/null
+++ b/src/libs/3rdparty/libvterm/src/rect.h
@@ -0,0 +1,56 @@
+/*
+ * Some utility functions on VTermRect structures
+ */
+
+#define STRFrect "(%d,%d-%d,%d)"
+#define ARGSrect(r) (r).start_row, (r).start_col, (r).end_row, (r).end_col
+
+/* Expand dst to contain src as well */
+static void rect_expand(VTermRect *dst, VTermRect *src)
+{
+ if(dst->start_row > src->start_row) dst->start_row = src->start_row;
+ if(dst->start_col > src->start_col) dst->start_col = src->start_col;
+ if(dst->end_row < src->end_row) dst->end_row = src->end_row;
+ if(dst->end_col < src->end_col) dst->end_col = src->end_col;
+}
+
+/* Clip the dst to ensure it does not step outside of bounds */
+static void rect_clip(VTermRect *dst, VTermRect *bounds)
+{
+ if(dst->start_row < bounds->start_row) dst->start_row = bounds->start_row;
+ if(dst->start_col < bounds->start_col) dst->start_col = bounds->start_col;
+ if(dst->end_row > bounds->end_row) dst->end_row = bounds->end_row;
+ if(dst->end_col > bounds->end_col) dst->end_col = bounds->end_col;
+ /* Ensure it doesn't end up negatively-sized */
+ if(dst->end_row < dst->start_row) dst->end_row = dst->start_row;
+ if(dst->end_col < dst->start_col) dst->end_col = dst->start_col;
+}
+
+/* True if the two rectangles are equal */
+static int rect_equal(VTermRect *a, VTermRect *b)
+{
+ return (a->start_row == b->start_row) &&
+ (a->start_col == b->start_col) &&
+ (a->end_row == b->end_row) &&
+ (a->end_col == b->end_col);
+}
+
+/* True if small is contained entirely within big */
+static int rect_contains(VTermRect *big, VTermRect *small)
+{
+ if(small->start_row < big->start_row) return 0;
+ if(small->start_col < big->start_col) return 0;
+ if(small->end_row > big->end_row) return 0;
+ if(small->end_col > big->end_col) return 0;
+ return 1;
+}
+
+/* True if the rectangles overlap at all */
+static int rect_intersects(VTermRect *a, VTermRect *b)
+{
+ if(a->start_row > b->end_row || b->start_row > a->end_row)
+ return 0;
+ if(a->start_col > b->end_col || b->start_col > a->end_col)
+ return 0;
+ return 1;
+}
diff --git a/src/libs/3rdparty/libvterm/src/screen.c b/src/libs/3rdparty/libvterm/src/screen.c
new file mode 100644
index 00000000000..9d1028e67ae
--- /dev/null
+++ b/src/libs/3rdparty/libvterm/src/screen.c
@@ -0,0 +1,1183 @@
+#include "vterm_internal.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "rect.h"
+#include "utf8.h"
+
+#define UNICODE_SPACE 0x20
+#define UNICODE_LINEFEED 0x0a
+
+#undef DEBUG_REFLOW
+
+/* State of the pen at some moment in time, also used in a cell */
+typedef struct
+{
+ /* After the bitfield */
+ VTermColor fg, bg;
+
+ unsigned int bold : 1;
+ unsigned int underline : 3;
+ unsigned int italic : 1;
+ unsigned int blink : 1;
+ unsigned int reverse : 1;
+ unsigned int conceal : 1;
+ unsigned int strike : 1;
+ unsigned int font : 4; /* 0 to 9 */
+ unsigned int small : 1;
+ unsigned int baseline : 2;
+
+ /* Extra state storage that isn't strictly pen-related */
+ unsigned int protected_cell : 1;
+ unsigned int dwl : 1; /* on a DECDWL or DECDHL line */
+ unsigned int dhl : 2; /* on a DECDHL line (1=top 2=bottom) */
+} ScreenPen;
+
+/* Internal representation of a screen cell */
+typedef struct
+{
+ uint32_t chars[VTERM_MAX_CHARS_PER_CELL];
+ ScreenPen pen;
+} ScreenCell;
+
+struct VTermScreen
+{
+ VTerm *vt;
+ VTermState *state;
+
+ const VTermScreenCallbacks *callbacks;
+ void *cbdata;
+
+ VTermDamageSize damage_merge;
+ /* start_row == -1 => no damage */
+ VTermRect damaged;
+ VTermRect pending_scrollrect;
+ int pending_scroll_downward, pending_scroll_rightward;
+
+ int rows;
+ int cols;
+
+ unsigned int global_reverse : 1;
+ unsigned int reflow : 1;
+
+ /* Primary and Altscreen. buffers[1] is lazily allocated as needed */
+ ScreenCell *buffers[2];
+
+ /* buffer will == buffers[0] or buffers[1], depending on altscreen */
+ ScreenCell *buffer;
+
+ /* buffer for a single screen row used in scrollback storage callbacks */
+ VTermScreenCell *sb_buffer;
+
+ ScreenPen pen;
+};
+
+static inline void clearcell(const VTermScreen *screen, ScreenCell *cell)
+{
+ cell->chars[0] = 0;
+ cell->pen = screen->pen;
+}
+
+static inline ScreenCell *getcell(const VTermScreen *screen, int row, int col)
+{
+ if(row < 0 || row >= screen->rows)
+ return NULL;
+ if(col < 0 || col >= screen->cols)
+ return NULL;
+ return screen->buffer + (screen->cols * row) + col;
+}
+
+static ScreenCell *alloc_buffer(VTermScreen *screen, int rows, int cols)
+{
+ ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, sizeof(ScreenCell) * rows * cols);
+
+ for(int row = 0; row < rows; row++) {
+ for(int col = 0; col < cols; col++) {
+ clearcell(screen, &new_buffer[row * cols + col]);
+ }
+ }
+
+ return new_buffer;
+}
+
+static void damagerect(VTermScreen *screen, VTermRect rect)
+{
+ VTermRect emit;
+
+ switch(screen->damage_merge) {
+ case VTERM_DAMAGE_CELL:
+ /* Always emit damage event */
+ emit = rect;
+ break;
+
+ case VTERM_DAMAGE_ROW:
+ /* Emit damage longer than one row. Try to merge with existing damage in
+ * the same row */
+ if(rect.end_row > rect.start_row + 1) {
+ // Bigger than 1 line - flush existing, emit this
+ vterm_screen_flush_damage(screen);
+ emit = rect;
+ }
+ else if(screen->damaged.start_row == -1) {
+ // None stored yet
+ screen->damaged = rect;
+ return;
+ }
+ else if(rect.start_row == screen->damaged.start_row) {
+ // Merge with the stored line
+ if(screen->damaged.start_col > rect.start_col)
+ screen->damaged.start_col = rect.start_col;
+ if(screen->damaged.end_col < rect.end_col)
+ screen->damaged.end_col = rect.end_col;
+ return;
+ }
+ else {
+ // Emit the currently stored line, store a new one
+ emit = screen->damaged;
+ screen->damaged = rect;
+ }
+ break;
+
+ case VTERM_DAMAGE_SCREEN:
+ case VTERM_DAMAGE_SCROLL:
+ /* Never emit damage event */
+ if(screen->damaged.start_row == -1)
+ screen->damaged = rect;
+ else {
+ rect_expand(&screen->damaged, &rect);
+ }
+ return;
+
+ default:
+ DEBUG_LOG("TODO: Maybe merge damage for level %d\n", screen->damage_merge);
+ return;
+ }
+
+ if(screen->callbacks && screen->callbacks->damage)
+ (*screen->callbacks->damage)(emit, screen->cbdata);
+}
+
+static void damagescreen(VTermScreen *screen)
+{
+ VTermRect rect = {
+ .start_row = 0,
+ .end_row = screen->rows,
+ .start_col = 0,
+ .end_col = screen->cols,
+ };
+
+ damagerect(screen, rect);
+}
+
+static int putglyph(VTermGlyphInfo *info, VTermPos pos, void *user)
+{
+ VTermScreen *screen = user;
+ ScreenCell *cell = getcell(screen, pos.row, pos.col);
+
+ if(!cell)
+ return 0;
+
+ int i;
+ for(i = 0; i < VTERM_MAX_CHARS_PER_CELL && info->chars[i]; i++) {
+ cell->chars[i] = info->chars[i];
+ cell->pen = screen->pen;
+ }
+ if(i < VTERM_MAX_CHARS_PER_CELL)
+ cell->chars[i] = 0;
+
+ for(int col = 1; col < info->width; col++)
+ getcell(screen, pos.row, pos.col + col)->chars[0] = (uint32_t)-1;
+
+ VTermRect rect = {
+ .start_row = pos.row,
+ .end_row = pos.row+1,
+ .start_col = pos.col,
+ .end_col = pos.col+info->width,
+ };
+
+ cell->pen.protected_cell = info->protected_cell;
+ cell->pen.dwl = info->dwl;
+ cell->pen.dhl = info->dhl;
+
+ damagerect(screen, rect);
+
+ return 1;
+}
+
+static void sb_pushline_from_row(VTermScreen *screen, int row)
+{
+ VTermPos pos = { .row = row };
+ for(pos.col = 0; pos.col < screen->cols; pos.col++)
+ vterm_screen_get_cell(screen, pos, screen->sb_buffer + pos.col);
+
+ (screen->callbacks->sb_pushline)(screen->cols, screen->sb_buffer, screen->cbdata);
+}
+
+static int moverect_internal(VTermRect dest, VTermRect src, void *user)
+{
+ VTermScreen *screen = user;
+
+ if(screen->callbacks && screen->callbacks->sb_pushline &&
+ dest.start_row == 0 && dest.start_col == 0 && // starts top-left corner
+ dest.end_col == screen->cols && // full width
+ screen->buffer == screen->buffers[BUFIDX_PRIMARY]) { // not altscreen
+ for(int row = 0; row < src.start_row; row++)
+ sb_pushline_from_row(screen, row);
+ }
+
+ int cols = src.end_col - src.start_col;
+ int downward = src.start_row - dest.start_row;
+
+ int init_row, test_row, inc_row;
+ if(downward < 0) {
+ init_row = dest.end_row - 1;
+ test_row = dest.start_row - 1;
+ inc_row = -1;
+ }
+ else {
+ init_row = dest.start_row;
+ test_row = dest.end_row;
+ inc_row = +1;
+ }
+
+ for(int row = init_row; row != test_row; row += inc_row)
+ memmove(getcell(screen, row, dest.start_col),
+ getcell(screen, row + downward, src.start_col),
+ cols * sizeof(ScreenCell));
+
+ return 1;
+}
+
+static int moverect_user(VTermRect dest, VTermRect src, void *user)
+{
+ VTermScreen *screen = user;
+
+ if(screen->callbacks && screen->callbacks->moverect) {
+ if(screen->damage_merge != VTERM_DAMAGE_SCROLL)
+ // Avoid an infinite loop
+ vterm_screen_flush_damage(screen);
+
+ if((*screen->callbacks->moverect)(dest, src, screen->cbdata))
+ return 1;
+ }
+
+ damagerect(screen, dest);
+
+ return 1;
+}
+
+static int erase_internal(VTermRect rect, int selective, void *user)
+{
+ VTermScreen *screen = user;
+
+ for(int row = rect.start_row; row < screen->state->rows && row < rect.end_row; row++) {
+ const VTermLineInfo *info = vterm_state_get_lineinfo(screen->state, row);
+
+ for(int col = rect.start_col; col < rect.end_col; col++) {
+ ScreenCell *cell = getcell(screen, row, col);
+
+ if(selective && cell->pen.protected_cell)
+ continue;
+
+ cell->chars[0] = 0;
+ cell->pen = (ScreenPen){
+ /* Only copy .fg and .bg; leave things like rv in reset state */
+ .fg = screen->pen.fg,
+ .bg = screen->pen.bg,
+ };
+ cell->pen.dwl = info->doublewidth;
+ cell->pen.dhl = info->doubleheight;
+ }
+ }
+
+ return 1;
+}
+
+static int erase_user(VTermRect rect, int selective, void *user)
+{
+ VTermScreen *screen = user;
+
+ damagerect(screen, rect);
+
+ return 1;
+}
+
+static int erase(VTermRect rect, int selective, void *user)
+{
+ erase_internal(rect, selective, user);
+ return erase_user(rect, 0, user);
+}
+
+static int scrollrect(VTermRect rect, int downward, int rightward, void *user)
+{
+ VTermScreen *screen = user;
+
+ if(screen->damage_merge != VTERM_DAMAGE_SCROLL) {
+ vterm_scroll_rect(rect, downward, rightward,
+ moverect_internal, erase_internal, screen);
+
+ vterm_screen_flush_damage(screen);
+
+ vterm_scroll_rect(rect, downward, rightward,
+ moverect_user, erase_user, screen);
+
+ return 1;
+ }
+
+ if(screen->damaged.start_row != -1 &&
+ !rect_intersects(&rect, &screen->damaged)) {
+ vterm_screen_flush_damage(screen);
+ }
+
+ if(screen->pending_scrollrect.start_row == -1) {
+ screen->pending_scrollrect = rect;
+ screen->pending_scroll_downward = downward;
+ screen->pending_scroll_rightward = rightward;
+ }
+ else if(rect_equal(&screen->pending_scrollrect, &rect) &&
+ ((screen->pending_scroll_downward == 0 && downward == 0) ||
+ (screen->pending_scroll_rightward == 0 && rightward == 0))) {
+ screen->pending_scroll_downward += downward;
+ screen->pending_scroll_rightward += rightward;
+ }
+ else {
+ vterm_screen_flush_damage(screen);
+
+ screen->pending_scrollrect = rect;
+ screen->pending_scroll_downward = downward;
+ screen->pending_scroll_rightward = rightward;
+ }
+
+ vterm_scroll_rect(rect, downward, rightward,
+ moverect_internal, erase_internal, screen);
+
+ if(screen->damaged.start_row == -1)
+ return 1;
+
+ if(rect_contains(&rect, &screen->damaged)) {
+ /* Scroll region entirely contains the damage; just move it */
+ vterm_rect_move(&screen->damaged, -downward, -rightward);
+ rect_clip(&screen->damaged, &rect);
+ }
+ /* There are a number of possible cases here, but lets restrict this to only
+ * the common case where we might actually gain some performance by
+ * optimising it. Namely, a vertical scroll that neatly cuts the damage
+ * region in half.
+ */
+ else if(rect.start_col <= screen->damaged.start_col &&
+ rect.end_col >= screen->damaged.end_col &&
+ rightward == 0) {
+ if(screen->damaged.start_row >= rect.start_row &&
+ screen->damaged.start_row < rect.end_row) {
+ screen->damaged.start_row -= downward;
+ if(screen->damaged.start_row < rect.start_row)
+ screen->damaged.start_row = rect.start_row;
+ if(screen->damaged.start_row > rect.end_row)
+ screen->damaged.start_row = rect.end_row;
+ }
+ if(screen->damaged.end_row >= rect.start_row &&
+ screen->damaged.end_row < rect.end_row) {
+ screen->damaged.end_row -= downward;
+ if(screen->damaged.end_row < rect.start_row)
+ screen->damaged.end_row = rect.start_row;
+ if(screen->damaged.end_row > rect.end_row)
+ screen->damaged.end_row = rect.end_row;
+ }
+ }
+ else {
+ DEBUG_LOG("TODO: Just flush and redo damaged=" STRFrect " rect=" STRFrect "\n",
+ ARGSrect(screen->damaged), ARGSrect(rect));
+ }
+
+ return 1;
+}
+
+static int movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user)
+{
+ VTermScreen *screen = user;
+
+ if(screen->callbacks && screen->callbacks->movecursor)
+ return (*screen->callbacks->movecursor)(pos, oldpos, visible, screen->cbdata);
+
+ return 0;
+}
+
+static int setpenattr(VTermAttr attr, VTermValue *val, void *user)
+{
+ VTermScreen *screen = user;
+
+ switch(attr) {
+ case VTERM_ATTR_BOLD:
+ screen->pen.bold = val->boolean;
+ return 1;
+ case VTERM_ATTR_UNDERLINE:
+ screen->pen.underline = val->number;
+ return 1;
+ case VTERM_ATTR_ITALIC:
+ screen->pen.italic = val->boolean;
+ return 1;
+ case VTERM_ATTR_BLINK:
+ screen->pen.blink = val->boolean;
+ return 1;
+ case VTERM_ATTR_REVERSE:
+ screen->pen.reverse = val->boolean;
+ return 1;
+ case VTERM_ATTR_CONCEAL:
+ screen->pen.conceal = val->boolean;
+ return 1;
+ case VTERM_ATTR_STRIKE:
+ screen->pen.strike = val->boolean;
+ return 1;
+ case VTERM_ATTR_FONT:
+ screen->pen.font = val->number;
+ return 1;
+ case VTERM_ATTR_FOREGROUND:
+ screen->pen.fg = val->color;
+ return 1;
+ case VTERM_ATTR_BACKGROUND:
+ screen->pen.bg = val->color;
+ return 1;
+ case VTERM_ATTR_SMALL:
+ screen->pen.small = val->boolean;
+ return 1;
+ case VTERM_ATTR_BASELINE:
+ screen->pen.baseline = val->number;
+ return 1;
+
+ case VTERM_N_ATTRS:
+ return 0;
+ }
+
+ return 0;
+}
+
+static int settermprop(VTermProp prop, VTermValue *val, void *user)
+{
+ VTermScreen *screen = user;
+
+ switch(prop) {
+ case VTERM_PROP_ALTSCREEN:
+ if(val->boolean && !screen->buffers[BUFIDX_ALTSCREEN])
+ return 0;
+
+ screen->buffer = val->boolean ? screen->buffers[BUFIDX_ALTSCREEN] : screen->buffers[BUFIDX_PRIMARY];
+ /* only send a damage event on disable; because during enable there's an
+ * erase that sends a damage anyway
+ */
+ if(!val->boolean)
+ damagescreen(screen);
+ break;
+ case VTERM_PROP_REVERSE:
+ screen->global_reverse = val->boolean;
+ damagescreen(screen);
+ break;
+ default:
+ ; /* ignore */
+ }
+
+ if(screen->callbacks && screen->callbacks->settermprop)
+ return (*screen->callbacks->settermprop)(prop, val, screen->cbdata);
+
+ return 1;
+}
+
+static int bell(void *user)
+{
+ VTermScreen *screen = user;
+
+ if(screen->callbacks && screen->callbacks->bell)
+ return (*screen->callbacks->bell)(screen->cbdata);
+
+ return 0;
+}
+
+/* How many cells are non-blank
+ * Returns the position of the first blank cell in the trailing blank end */
+static int line_popcount(ScreenCell *buffer, int row, int rows, int cols)
+{
+ int col = cols - 1;
+ while(col >= 0 && buffer[row * cols + col].chars[0] == 0)
+ col--;
+ return col + 1;
+}
+
+#define REFLOW (screen->reflow)
+
+static void resize_buffer(VTermScreen *screen, int bufidx, int new_rows, int new_cols, bool active, VTermStateFields *statefields)
+{
+ int old_rows = screen->rows;
+ int old_cols = screen->cols;
+
+ ScreenCell *old_buffer = screen->buffers[bufidx];
+ VTermLineInfo *old_lineinfo = statefields->lineinfos[bufidx];
+
+ ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, sizeof(ScreenCell) * new_rows * new_cols);
+ VTermLineInfo *new_lineinfo = vterm_allocator_malloc(screen->vt, sizeof(new_lineinfo[0]) * new_rows);
+
+ int old_row = old_rows - 1;
+ int new_row = new_rows - 1;
+
+ VTermPos old_cursor = statefields->pos;
+ VTermPos new_cursor = { -1, -1 };
+
+#ifdef DEBUG_REFLOW
+ fprintf(stderr, "Resizing from %dx%d to %dx%d; cursor was at (%d,%d)\n",
+ old_cols, old_rows, new_cols, new_rows, old_cursor.col, old_cursor.row);
+#endif
+
+ /* Keep track of the final row that is knonw to be blank, so we know what
+ * spare space we have for scrolling into
+ */
+ int final_blank_row = new_rows;
+
+ while(old_row >= 0) {
+ int old_row_end = old_row;
+ /* TODO: Stop if dwl or dhl */
+ while(REFLOW && old_lineinfo && old_row >= 0 && old_lineinfo[old_row].continuation)
+ old_row--;
+ int old_row_start = old_row;
+
+ int width = 0;
+ for(int row = old_row_start; row <= old_row_end; row++) {
+ if(REFLOW && row < (old_rows - 1) && old_lineinfo[row + 1].continuation)
+ width += old_cols;
+ else
+ width += line_popcount(old_buffer, row, old_rows, old_cols);
+ }
+
+ if(final_blank_row == (new_row + 1) && width == 0)
+ final_blank_row = new_row;
+
+ int new_height = REFLOW
+ ? width ? (width + new_cols - 1) / new_cols : 1
+ : 1;
+
+ int new_row_end = new_row;
+ int new_row_start = new_row - new_height + 1;
+
+ old_row = old_row_start;
+ int old_col = 0;
+
+ int spare_rows = new_rows - final_blank_row;
+
+ if(new_row_start < 0 && /* we'd fall off the top */
+ spare_rows >= 0 && /* we actually have spare rows */
+ (!active || new_cursor.row == -1 || (new_cursor.row - new_row_start) < new_rows))
+ {
+ /* Attempt to scroll content down into the blank rows at the bottom to
+ * make it fit
+ */
+ int downwards = -new_row_start;
+ if(downwards > spare_rows)
+ downwards = spare_rows;
+ int rowcount = new_rows - downwards;
+
+#ifdef DEBUG_REFLOW
+ fprintf(stderr, " scroll %d rows +%d downwards\n", rowcount, downwards);
+#endif
+
+ memmove(&new_buffer[downwards * new_cols], &new_buffer[0], rowcount * new_cols * sizeof(ScreenCell));
+ memmove(&new_lineinfo[downwards], &new_lineinfo[0], rowcount * sizeof(new_lineinfo[0]));
+
+ new_row += downwards;
+ new_row_start += downwards;
+ new_row_end += downwards;
+
+ if(new_cursor.row >= 0)
+ new_cursor.row += downwards;
+
+ final_blank_row += downwards;
+ }
+
+#ifdef DEBUG_REFLOW
+ fprintf(stderr, " rows [%d..%d] <- [%d..%d] width=%d\n",
+ new_row_start, new_row_end, old_row_start, old_row_end, width);
+#endif
+
+ if(new_row_start < 0)
+ break;
+
+ for(new_row = new_row_start, old_row = old_row_start; new_row <= new_row_end; new_row++) {
+ int count = width >= new_cols ? new_cols : width;
+ width -= count;
+
+ int new_col = 0;
+
+ while(count) {
+ /* TODO: This could surely be done a lot faster by memcpy()'ing the entire range */
+ new_buffer[new_row * new_cols + new_col] = old_buffer[old_row * old_cols + old_col];
+
+ if(old_cursor.row == old_row && old_cursor.col == old_col)
+ new_cursor.row = new_row, new_cursor.col = new_col;
+
+ old_col++;
+ if(old_col == old_cols) {
+ old_row++;
+
+ if(!REFLOW) {
+ new_col++;
+ break;
+ }
+ old_col = 0;
+ }
+
+ new_col++;
+ count--;
+ }
+
+ if(old_cursor.row == old_row && old_cursor.col >= old_col) {
+ new_cursor.row = new_row, new_cursor.col = (old_cursor.col - old_col + new_col);
+ if(new_cursor.col >= new_cols)
+ new_cursor.col = new_cols-1;
+ }
+
+ while(new_col < new_cols) {
+ clearcell(screen, &new_buffer[new_row * new_cols + new_col]);
+ new_col++;
+ }
+
+ new_lineinfo[new_row].continuation = (new_row > new_row_start);
+ }
+
+ old_row = old_row_start - 1;
+ new_row = new_row_start - 1;
+ }
+
+ if(old_cursor.row <= old_row) {
+ /* cursor would have moved entirely off the top of the screen; lets just
+ * bring it within range */
+ new_cursor.row = 0, new_cursor.col = old_cursor.col;
+ if(new_cursor.col >= new_cols)
+ new_cursor.col = new_cols-1;
+ }
+
+ /* We really expect the cursor position to be set by now */
+ if(active && (new_cursor.row == -1 || new_cursor.col == -1)) {
+ fprintf(stderr, "screen_resize failed to update cursor position\n");
+ abort();
+ }
+
+ if(old_row >= 0 && bufidx == BUFIDX_PRIMARY) {
+ /* Push spare lines to scrollback buffer */
+ for(int row = 0; row <= old_row; row++)
+ sb_pushline_from_row(screen, row);
+ if(active)
+ statefields->pos.row -= (old_row + 1);
+ }
+ if(new_row >= 0 && bufidx == BUFIDX_PRIMARY &&
+ screen->callbacks && screen->callbacks->sb_popline) {
+ /* Try to backfill rows by popping scrollback buffer */
+ while(new_row >= 0) {
+ if(!(screen->callbacks->sb_popline(old_cols, screen->sb_buffer, screen->cbdata)))
+ break;
+
+ VTermPos pos = { .row = new_row };
+ for(pos.col = 0; pos.col < old_cols && pos.col < new_cols; pos.col += screen->sb_buffer[pos.col].width) {
+ VTermScreenCell *src = &screen->sb_buffer[pos.col];
+ ScreenCell *dst = &new_buffer[pos.row * new_cols + pos.col];
+
+ for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL; i++) {
+ dst->chars[i] = src->chars[i];
+ if(!src->chars[i])
+ break;
+ }
+
+ dst->pen.bold = src->attrs.bold;
+ dst->pen.underline = src->attrs.underline;
+ dst->pen.italic = src->attrs.italic;
+ dst->pen.blink = src->attrs.blink;
+ dst->pen.reverse = src->attrs.reverse ^ screen->global_reverse;
+ dst->pen.conceal = src->attrs.conceal;
+ dst->pen.strike = src->attrs.strike;
+ dst->pen.font = src->attrs.font;
+ dst->pen.small = src->attrs.small;
+ dst->pen.baseline = src->attrs.baseline;
+
+ dst->pen.fg = src->fg;
+ dst->pen.bg = src->bg;
+
+ if(src->width == 2 && pos.col < (new_cols-1))
+ (dst + 1)->chars[0] = (uint32_t) -1;
+ }
+ for( ; pos.col < new_cols; pos.col++)
+ clearcell(screen, &new_buffer[pos.row * new_cols + pos.col]);
+ new_row--;
+
+ if(active)
+ statefields->pos.row++;
+ }
+ }
+ if(new_row >= 0) {
+ /* Scroll new rows back up to the top and fill in blanks at the bottom */
+ int moverows = new_rows - new_row - 1;
+ memmove(&new_buffer[0], &new_buffer[(new_row + 1) * new_cols], moverows * new_cols * sizeof(ScreenCell));
+ memmove(&new_lineinfo[0], &new_lineinfo[new_row + 1], moverows * sizeof(new_lineinfo[0]));
+
+ new_cursor.row -= (new_row + 1);
+
+ for(new_row = moverows; new_row < new_rows; new_row++) {
+ for(int col = 0; col < new_cols; col++)
+ clearcell(screen, &new_buffer[new_row * new_cols + col]);
+ new_lineinfo[new_row] = (VTermLineInfo){ 0 };
+ }
+ }
+
+ vterm_allocator_free(screen->vt, old_buffer);
+ screen->buffers[bufidx] = new_buffer;
+
+ vterm_allocator_free(screen->vt, old_lineinfo);
+ statefields->lineinfos[bufidx] = new_lineinfo;
+
+ if(active)
+ statefields->pos = new_cursor;
+
+ return;
+}
+
+static int resize(int new_rows, int new_cols, VTermStateFields *fields, void *user)
+{
+ VTermScreen *screen = user;
+
+ int altscreen_active = (screen->buffers[BUFIDX_ALTSCREEN] && screen->buffer == screen->buffers[BUFIDX_ALTSCREEN]);
+
+ int old_rows = screen->rows;
+ int old_cols = screen->cols;
+
+ if(new_cols > old_cols) {
+ /* Ensure that ->sb_buffer is large enough for a new or and old row */
+ if(screen->sb_buffer)
+ vterm_allocator_free(screen->vt, screen->sb_buffer);
+
+ screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * new_cols);
+ }
+
+ resize_buffer(screen, 0, new_rows, new_cols, !altscreen_active, fields);
+ if(screen->buffers[BUFIDX_ALTSCREEN])
+ resize_buffer(screen, 1, new_rows, new_cols, altscreen_active, fields);
+ else if(new_rows != old_rows) {
+ /* We don't need a full resize of the altscreen because it isn't enabled
+ * but we should at least keep the lineinfo the right size */
+ vterm_allocator_free(screen->vt, fields->lineinfos[BUFIDX_ALTSCREEN]);
+
+ VTermLineInfo *new_lineinfo = vterm_allocator_malloc(screen->vt, sizeof(new_lineinfo[0]) * new_rows);
+ for(int row = 0; row < new_rows; row++)
+ new_lineinfo[row] = (VTermLineInfo){ 0 };
+
+ fields->lineinfos[BUFIDX_ALTSCREEN] = new_lineinfo;
+ }
+
+ screen->buffer = altscreen_active ? screen->buffers[BUFIDX_ALTSCREEN] : screen->buffers[BUFIDX_PRIMARY];
+
+ screen->rows = new_rows;
+ screen->cols = new_cols;
+
+ if(new_cols <= old_cols) {
+ if(screen->sb_buffer)
+ vterm_allocator_free(screen->vt, screen->sb_buffer);
+
+ screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * new_cols);
+ }
+
+ /* TODO: Maaaaybe we can optimise this if there's no reflow happening */
+ damagescreen(screen);
+
+ if(screen->callbacks && screen->callbacks->resize)
+ return (*screen->callbacks->resize)(new_rows, new_cols, screen->cbdata);
+
+ return 1;
+}
+
+static int setlineinfo(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user)
+{
+ VTermScreen *screen = user;
+
+ if(newinfo->doublewidth != oldinfo->doublewidth ||
+ newinfo->doubleheight != oldinfo->doubleheight) {
+ for(int col = 0; col < screen->cols; col++) {
+ ScreenCell *cell = getcell(screen, row, col);
+ cell->pen.dwl = newinfo->doublewidth;
+ cell->pen.dhl = newinfo->doubleheight;
+ }
+
+ VTermRect rect = {
+ .start_row = row,
+ .end_row = row + 1,
+ .start_col = 0,
+ .end_col = newinfo->doublewidth ? screen->cols / 2 : screen->cols,
+ };
+ damagerect(screen, rect);
+
+ if(newinfo->doublewidth) {
+ rect.start_col = screen->cols / 2;
+ rect.end_col = screen->cols;
+
+ erase_internal(rect, 0, user);
+ }
+ }
+
+ return 1;
+}
+
+static int sb_clear(void *user) {
+ VTermScreen *screen = user;
+
+ if(screen->callbacks && screen->callbacks->sb_clear)
+ if((*screen->callbacks->sb_clear)(screen->cbdata))
+ return 1;
+
+ return 0;
+}
+
+static VTermStateCallbacks state_cbs = {
+ .putglyph = &putglyph,
+ .movecursor = &movecursor,
+ .scrollrect = &scrollrect,
+ .erase = &erase,
+ .setpenattr = &setpenattr,
+ .settermprop = &settermprop,
+ .bell = &bell,
+ .resize = &resize,
+ .setlineinfo = &setlineinfo,
+ .sb_clear = &sb_clear,
+};
+
+static VTermScreen *screen_new(VTerm *vt)
+{
+ VTermState *state = vterm_obtain_state(vt);
+ if(!state)
+ return NULL;
+
+ VTermScreen *screen = vterm_allocator_malloc(vt, sizeof(VTermScreen));
+ int rows, cols;
+
+ vterm_get_size(vt, &rows, &cols);
+
+ screen->vt = vt;
+ screen->state = state;
+
+ screen->damage_merge = VTERM_DAMAGE_CELL;
+ screen->damaged.start_row = -1;
+ screen->pending_scrollrect.start_row = -1;
+
+ screen->rows = rows;
+ screen->cols = cols;
+
+ screen->global_reverse = false;
+ screen->reflow = false;
+
+ screen->callbacks = NULL;
+ screen->cbdata = NULL;
+
+ screen->buffers[BUFIDX_PRIMARY] = alloc_buffer(screen, rows, cols);
+
+ screen->buffer = screen->buffers[BUFIDX_PRIMARY];
+
+ screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * cols);
+
+ vterm_state_set_callbacks(screen->state, &state_cbs, screen);
+
+ return screen;
+}
+
+INTERNAL void vterm_screen_free(VTermScreen *screen)
+{
+ vterm_allocator_free(screen->vt, screen->buffers[BUFIDX_PRIMARY]);
+ if(screen->buffers[BUFIDX_ALTSCREEN])
+ vterm_allocator_free(screen->vt, screen->buffers[BUFIDX_ALTSCREEN]);
+
+ vterm_allocator_free(screen->vt, screen->sb_buffer);
+
+ vterm_allocator_free(screen->vt, screen);
+}
+
+void vterm_screen_reset(VTermScreen *screen, int hard)
+{
+ screen->damaged.start_row = -1;
+ screen->pending_scrollrect.start_row = -1;
+ vterm_state_reset(screen->state, hard);
+ vterm_screen_flush_damage(screen);
+}
+
+static size_t _get_chars(const VTermScreen *screen, const int utf8, void *buffer, size_t len, const VTermRect rect)
+{
+ size_t outpos = 0;
+ int padding = 0;
+
+#define PUT(c) \
+ if(utf8) { \
+ size_t thislen = utf8_seqlen(c); \
+ if(buffer && outpos + thislen <= len) \
+ outpos += fill_utf8((c), (char *)buffer + outpos); \
+ else \
+ outpos += thislen; \
+ } \
+ else { \
+ if(buffer && outpos + 1 <= len) \
+ ((uint32_t*)buffer)[outpos++] = (c); \
+ else \
+ outpos++; \
+ }
+
+ for(int row = rect.start_row; row < rect.end_row; row++) {
+ for(int col = rect.start_col; col < rect.end_col; col++) {
+ ScreenCell *cell = getcell(screen, row, col);
+
+ if(cell->chars[0] == 0)
+ // Erased cell, might need a space
+ padding++;
+ else if(cell->chars[0] == (uint32_t)-1)
+ // Gap behind a double-width char, do nothing
+ ;
+ else {
+ while(padding) {
+ PUT(UNICODE_SPACE);
+ padding--;
+ }
+ for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL && cell->chars[i]; i++) {
+ PUT(cell->chars[i]);
+ }
+ }
+ }
+
+ if(row < rect.end_row - 1) {
+ PUT(UNICODE_LINEFEED);
+ padding = 0;
+ }
+ }
+
+ return outpos;
+}
+
+size_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect)
+{
+ return _get_chars(screen, 0, chars, len, rect);
+}
+
+size_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect)
+{
+ return _get_chars(screen, 1, str, len, rect);
+}
+
+/* Copy internal to external representation of a screen cell */
+int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell)
+{
+ ScreenCell *intcell = getcell(screen, pos.row, pos.col);
+ if(!intcell)
+ return 0;
+
+ for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL; i++) {
+ cell->chars[i] = intcell->chars[i];
+ if(!intcell->chars[i])
+ break;
+ }
+
+ cell->attrs.bold = intcell->pen.bold;
+ cell->attrs.underline = intcell->pen.underline;
+ cell->attrs.italic = intcell->pen.italic;
+ cell->attrs.blink = intcell->pen.blink;
+ cell->attrs.reverse = intcell->pen.reverse ^ screen->global_reverse;
+ cell->attrs.conceal = intcell->pen.conceal;
+ cell->attrs.strike = intcell->pen.strike;
+ cell->attrs.font = intcell->pen.font;
+ cell->attrs.small = intcell->pen.small;
+ cell->attrs.baseline = intcell->pen.baseline;
+
+ cell->attrs.dwl = intcell->pen.dwl;
+ cell->attrs.dhl = intcell->pen.dhl;
+
+ cell->fg = intcell->pen.fg;
+ cell->bg = intcell->pen.bg;
+
+ if(pos.col < (screen->cols - 1) &&
+ getcell(screen, pos.row, pos.col + 1)->chars[0] == (uint32_t)-1)
+ cell->width = 2;
+ else
+ cell->width = 1;
+
+ return 1;
+}
+
+int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos)
+{
+ /* This cell is EOL if this and every cell to the right is black */
+ for(; pos.col < screen->cols; pos.col++) {
+ ScreenCell *cell = getcell(screen, pos.row, pos.col);
+ if(cell->chars[0] != 0)
+ return 0;
+ }
+
+ return 1;
+}
+
+VTermScreen *vterm_obtain_screen(VTerm *vt)
+{
+ if(vt->screen)
+ return vt->screen;
+
+ VTermScreen *screen = screen_new(vt);
+ vt->screen = screen;
+
+ return screen;
+}
+
+void vterm_screen_enable_reflow(VTermScreen *screen, bool reflow)
+{
+ screen->reflow = reflow;
+}
+
+#undef vterm_screen_set_reflow
+void vterm_screen_set_reflow(VTermScreen *screen, bool reflow)
+{
+ vterm_screen_enable_reflow(screen, reflow);
+}
+
+void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen)
+{
+ if(!screen->buffers[BUFIDX_ALTSCREEN] && altscreen) {
+ int rows, cols;
+ vterm_get_size(screen->vt, &rows, &cols);
+
+ screen->buffers[BUFIDX_ALTSCREEN] = alloc_buffer(screen, rows, cols);
+ }
+}
+
+void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user)
+{
+ screen->callbacks = callbacks;
+ screen->cbdata = user;
+}
+
+void *vterm_screen_get_cbdata(VTermScreen *screen)
+{
+ return screen->cbdata;
+}
+
+void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermStateFallbacks *fallbacks, void *user)
+{
+ vterm_state_set_unrecognised_fallbacks(screen->state, fallbacks, user);
+}
+
+void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen)
+{
+ return vterm_state_get_unrecognised_fbdata(screen->state);
+}
+
+void vterm_screen_flush_damage(VTermScreen *screen)
+{
+ if(screen->pending_scrollrect.start_row != -1) {
+ vterm_scroll_rect(screen->pending_scrollrect, screen->pending_scroll_downward, screen->pending_scroll_rightward,
+ moverect_user, erase_user, screen);
+
+ screen->pending_scrollrect.start_row = -1;
+ }
+
+ if(screen->damaged.start_row != -1) {
+ if(screen->callbacks && screen->callbacks->damage)
+ (*screen->callbacks->damage)(screen->damaged, screen->cbdata);
+
+ screen->damaged.start_row = -1;
+ }
+}
+
+void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size)
+{
+ vterm_screen_flush_damage(screen);
+ screen->damage_merge = size;
+}
+
+static int attrs_differ(VTermAttrMask attrs, ScreenCell *a, ScreenCell *b)
+{
+ if((attrs & VTERM_ATTR_BOLD_MASK) && (a->pen.bold != b->pen.bold))
+ return 1;
+ if((attrs & VTERM_ATTR_UNDERLINE_MASK) && (a->pen.underline != b->pen.underline))
+ return 1;
+ if((attrs & VTERM_ATTR_ITALIC_MASK) && (a->pen.italic != b->pen.italic))
+ return 1;
+ if((attrs & VTERM_ATTR_BLINK_MASK) && (a->pen.blink != b->pen.blink))
+ return 1;
+ if((attrs & VTERM_ATTR_REVERSE_MASK) && (a->pen.reverse != b->pen.reverse))
+ return 1;
+ if((attrs & VTERM_ATTR_CONCEAL_MASK) && (a->pen.conceal != b->pen.conceal))
+ return 1;
+ if((attrs & VTERM_ATTR_STRIKE_MASK) && (a->pen.strike != b->pen.strike))
+ return 1;
+ if((attrs & VTERM_ATTR_FONT_MASK) && (a->pen.font != b->pen.font))
+ return 1;
+ if((attrs & VTERM_ATTR_FOREGROUND_MASK) && !vterm_color_is_equal(&a->pen.fg, &b->pen.fg))
+ return 1;
+ if((attrs & VTERM_ATTR_BACKGROUND_MASK) && !vterm_color_is_equal(&a->pen.bg, &b->pen.bg))
+ return 1;
+ if((attrs & VTERM_ATTR_SMALL_MASK) && (a->pen.small != b->pen.small))
+ return 1;
+ if((attrs & VTERM_ATTR_BASELINE_MASK) && (a->pen.baseline != b->pen.baseline))
+ return 1;
+
+ return 0;
+}
+
+int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs)
+{
+ ScreenCell *target = getcell(screen, pos.row, pos.col);
+
+ // TODO: bounds check
+ extent->start_row = pos.row;
+ extent->end_row = pos.row + 1;
+
+ if(extent->start_col < 0)
+ extent->start_col = 0;
+ if(extent->end_col < 0)
+ extent->end_col = screen->cols;
+
+ int col;
+
+ for(col = pos.col - 1; col >= extent->start_col; col--)
+ if(attrs_differ(attrs, target, getcell(screen, pos.row, col)))
+ break;
+ extent->start_col = col + 1;
+
+ for(col = pos.col + 1; col < extent->end_col; col++)
+ if(attrs_differ(attrs, target, getcell(screen, pos.row, col)))
+ break;
+ extent->end_col = col - 1;
+
+ return 1;
+}
+
+void vterm_screen_convert_color_to_rgb(const VTermScreen *screen, VTermColor *col)
+{
+ vterm_state_convert_color_to_rgb(screen->state, col);
+}
+
+static void reset_default_colours(VTermScreen *screen, ScreenCell *buffer)
+{
+ for(int row = 0; row <= screen->rows - 1; row++)
+ for(int col = 0; col <= screen->cols - 1; col++) {
+ ScreenCell *cell = &buffer[row * screen->cols + col];
+ if(VTERM_COLOR_IS_DEFAULT_FG(&cell->pen.fg))
+ cell->pen.fg = screen->pen.fg;
+ if(VTERM_COLOR_IS_DEFAULT_BG(&cell->pen.bg))
+ cell->pen.bg = screen->pen.bg;
+ }
+}
+
+void vterm_screen_set_default_colors(VTermScreen *screen, const VTermColor *default_fg, const VTermColor *default_bg)
+{
+ vterm_state_set_default_colors(screen->state, default_fg, default_bg);
+
+ if(default_fg && VTERM_COLOR_IS_DEFAULT_FG(&screen->pen.fg)) {
+ screen->pen.fg = *default_fg;
+ screen->pen.fg.type = (screen->pen.fg.type & ~VTERM_COLOR_DEFAULT_MASK)
+ | VTERM_COLOR_DEFAULT_FG;
+ }
+
+ if(default_bg && VTERM_COLOR_IS_DEFAULT_BG(&screen->pen.bg)) {
+ screen->pen.bg = *default_bg;
+ screen->pen.bg.type = (screen->pen.bg.type & ~VTERM_COLOR_DEFAULT_MASK)
+ | VTERM_COLOR_DEFAULT_BG;
+ }
+
+ reset_default_colours(screen, screen->buffers[0]);
+ if(screen->buffers[1])
+ reset_default_colours(screen, screen->buffers[1]);
+}
diff --git a/src/libs/3rdparty/libvterm/src/state.c b/src/libs/3rdparty/libvterm/src/state.c
new file mode 100644
index 00000000000..313e746e77c
--- /dev/null
+++ b/src/libs/3rdparty/libvterm/src/state.c
@@ -0,0 +1,2315 @@
+#include "vterm_internal.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#define strneq(a,b,n) (strncmp(a,b,n)==0)
+
+#if defined(DEBUG) && DEBUG > 1
+# define DEBUG_GLYPH_COMBINE
+#endif
+
+/* Some convenient wrappers to make callback functions easier */
+
+static void putglyph(VTermState *state, const uint32_t chars[], int width, VTermPos pos)
+{
+ VTermGlyphInfo info = {
+ .chars = chars,
+ .width = width,
+ .protected_cell = state->protected_cell,
+ .dwl = state->lineinfo[pos.row].doublewidth,
+ .dhl = state->lineinfo[pos.row].doubleheight,
+ };
+
+ if(state->callbacks && state->callbacks->putglyph)
+ if((*state->callbacks->putglyph)(&info, pos, state->cbdata))
+ return;
+
+ DEBUG_LOG("libvterm: Unhandled putglyph U+%04x at (%d,%d)\n", chars[0], pos.col, pos.row);
+}
+
+static void updatecursor(VTermState *state, VTermPos *oldpos, int cancel_phantom)
+{
+ if(state->pos.col == oldpos->col && state->pos.row == oldpos->row)
+ return;
+
+ if(cancel_phantom)
+ state->at_phantom = 0;
+
+ if(state->callbacks && state->callbacks->movecursor)
+ if((*state->callbacks->movecursor)(state->pos, *oldpos, state->mode.cursor_visible, state->cbdata))
+ return;
+}
+
+static void erase(VTermState *state, VTermRect rect, int selective)
+{
+ if(rect.end_col == state->cols) {
+ /* If we're erasing the final cells of any lines, cancel the continuation
+ * marker on the subsequent line
+ */
+ for(int row = rect.start_row + 1; row < rect.end_row + 1 && row < state->rows; row++)
+ state->lineinfo[row].continuation = 0;
+ }
+
+ if(state->callbacks && state->callbacks->erase)
+ if((*state->callbacks->erase)(rect, selective, state->cbdata))
+ return;
+}
+
+static VTermState *vterm_state_new(VTerm *vt)
+{
+ VTermState *state = vterm_allocator_malloc(vt, sizeof(VTermState));
+
+ state->vt = vt;
+
+ state->rows = vt->rows;
+ state->cols = vt->cols;
+
+ state->mouse_col = 0;
+ state->mouse_row = 0;
+ state->mouse_buttons = 0;
+
+ state->mouse_protocol = MOUSE_X10;
+
+ state->callbacks = NULL;
+ state->cbdata = NULL;
+
+ state->selection.callbacks = NULL;
+ state->selection.user = NULL;
+ state->selection.buffer = NULL;
+
+ vterm_state_newpen(state);
+
+ state->bold_is_highbright = 0;
+
+ state->combine_chars_size = 16;
+ state->combine_chars = vterm_allocator_malloc(state->vt, state->combine_chars_size * sizeof(state->combine_chars[0]));
+
+ state->tabstops = vterm_allocator_malloc(state->vt, (state->cols + 7) / 8);
+
+ state->lineinfos[BUFIDX_PRIMARY] = vterm_allocator_malloc(state->vt, state->rows * sizeof(VTermLineInfo));
+ /* TODO: Make an 'enable' function */
+ state->lineinfos[BUFIDX_ALTSCREEN] = vterm_allocator_malloc(state->vt, state->rows * sizeof(VTermLineInfo));
+ state->lineinfo = state->lineinfos[BUFIDX_PRIMARY];
+
+ state->encoding_utf8.enc = vterm_lookup_encoding(ENC_UTF8, 'u');
+ if(*state->encoding_utf8.enc->init)
+ (*state->encoding_utf8.enc->init)(state->encoding_utf8.enc, state->encoding_utf8.data);
+
+ return state;
+}
+
+INTERNAL void vterm_state_free(VTermState *state)
+{
+ vterm_allocator_free(state->vt, state->tabstops);
+ vterm_allocator_free(state->vt, state->lineinfos[BUFIDX_PRIMARY]);
+ if(state->lineinfos[BUFIDX_ALTSCREEN])
+ vterm_allocator_free(state->vt, state->lineinfos[BUFIDX_ALTSCREEN]);
+ vterm_allocator_free(state->vt, state->combine_chars);
+ vterm_allocator_free(state->vt, state);
+}
+
+static void scroll(VTermState *state, VTermRect rect, int downward, int rightward)
+{
+ if(!downward && !rightward)
+ return;
+
+ int rows = rect.end_row - rect.start_row;
+ if(downward > rows)
+ downward = rows;
+ else if(downward < -rows)
+ downward = -rows;
+
+ int cols = rect.end_col - rect.start_col;
+ if(rightward > cols)
+ rightward = cols;
+ else if(rightward < -cols)
+ rightward = -cols;
+
+ // Update lineinfo if full line
+ if(rect.start_col == 0 && rect.end_col == state->cols && rightward == 0) {
+ int height = rect.end_row - rect.start_row - abs(downward);
+
+ if(downward > 0) {
+ memmove(state->lineinfo + rect.start_row,
+ state->lineinfo + rect.start_row + downward,
+ height * sizeof(state->lineinfo[0]));
+ for(int row = rect.end_row - downward; row < rect.end_row; row++)
+ state->lineinfo[row] = (VTermLineInfo){ 0 };
+ }
+ else {
+ memmove(state->lineinfo + rect.start_row - downward,
+ state->lineinfo + rect.start_row,
+ height * sizeof(state->lineinfo[0]));
+ for(int row = rect.start_row; row < rect.start_row - downward; row++)
+ state->lineinfo[row] = (VTermLineInfo){ 0 };
+ }
+ }
+
+ if(state->callbacks && state->callbacks->scrollrect)
+ if((*state->callbacks->scrollrect)(rect, downward, rightward, state->cbdata))
+ return;
+
+ if(state->callbacks)
+ vterm_scroll_rect(rect, downward, rightward,
+ state->callbacks->moverect, state->callbacks->erase, state->cbdata);
+}
+
+static void linefeed(VTermState *state)
+{
+ if(state->pos.row == SCROLLREGION_BOTTOM(state) - 1) {
+ VTermRect rect = {
+ .start_row = state->scrollregion_top,
+ .end_row = SCROLLREGION_BOTTOM(state),
+ .start_col = SCROLLREGION_LEFT(state),
+ .end_col = SCROLLREGION_RIGHT(state),
+ };
+
+ scroll(state, rect, 1, 0);
+ }
+ else if(state->pos.row < state->rows-1)
+ state->pos.row++;
+}
+
+static void grow_combine_buffer(VTermState *state)
+{
+ size_t new_size = state->combine_chars_size * 2;
+ uint32_t *new_chars = vterm_allocator_malloc(state->vt, new_size * sizeof(new_chars[0]));
+
+ memcpy(new_chars, state->combine_chars, state->combine_chars_size * sizeof(new_chars[0]));
+
+ vterm_allocator_free(state->vt, state->combine_chars);
+
+ state->combine_chars = new_chars;
+ state->combine_chars_size = new_size;
+}
+
+static void set_col_tabstop(VTermState *state, int col)
+{
+ unsigned char mask = 1 << (col & 7);
+ state->tabstops[col >> 3] |= mask;
+}
+
+static void clear_col_tabstop(VTermState *state, int col)
+{
+ unsigned char mask = 1 << (col & 7);
+ state->tabstops[col >> 3] &= ~mask;
+}
+
+static int is_col_tabstop(VTermState *state, int col)
+{
+ unsigned char mask = 1 << (col & 7);
+ return state->tabstops[col >> 3] & mask;
+}
+
+static int is_cursor_in_scrollregion(const VTermState *state)
+{
+ if(state->pos.row < state->scrollregion_top ||
+ state->pos.row >= SCROLLREGION_BOTTOM(state))
+ return 0;
+ if(state->pos.col < SCROLLREGION_LEFT(state) ||
+ state->pos.col >= SCROLLREGION_RIGHT(state))
+ return 0;
+
+ return 1;
+}
+
+static void tab(VTermState *state, int count, int direction)
+{
+ while(count > 0) {
+ if(direction > 0) {
+ if(state->pos.col >= THISROWWIDTH(state)-1)
+ return;
+
+ state->pos.col++;
+ }
+ else if(direction < 0) {
+ if(state->pos.col < 1)
+ return;
+
+ state->pos.col--;
+ }
+
+ if(is_col_tabstop(state, state->pos.col))
+ count--;
+ }
+}
+
+#define NO_FORCE 0
+#define FORCE 1
+
+#define DWL_OFF 0
+#define DWL_ON 1
+
+#define DHL_OFF 0
+#define DHL_TOP 1
+#define DHL_BOTTOM 2
+
+static void set_lineinfo(VTermState *state, int row, int force, int dwl, int dhl)
+{
+ VTermLineInfo info = state->lineinfo[row];
+
+ if(dwl == DWL_OFF)
+ info.doublewidth = DWL_OFF;
+ else if(dwl == DWL_ON)
+ info.doublewidth = DWL_ON;
+ // else -1 to ignore
+
+ if(dhl == DHL_OFF)
+ info.doubleheight = DHL_OFF;
+ else if(dhl == DHL_TOP)
+ info.doubleheight = DHL_TOP;
+ else if(dhl == DHL_BOTTOM)
+ info.doubleheight = DHL_BOTTOM;
+
+ if((state->callbacks &&
+ state->callbacks->setlineinfo &&
+ (*state->callbacks->setlineinfo)(row, &info, state->lineinfo + row, state->cbdata))
+ || force)
+ state->lineinfo[row] = info;
+}
+
+static int on_text(const char bytes[], size_t len, void *user)
+{
+ VTermState *state = user;
+
+ VTermPos oldpos = state->pos;
+
+ uint32_t *codepoints = (uint32_t *)(state->vt->tmpbuffer);
+ size_t maxpoints = (state->vt->tmpbuffer_len) / sizeof(uint32_t);
+
+ int npoints = 0;
+ size_t eaten = 0;
+
+ VTermEncodingInstance *encoding =
+ state->gsingle_set ? &state->encoding[state->gsingle_set] :
+ !(bytes[eaten] & 0x80) ? &state->encoding[state->gl_set] :
+ state->vt->mode.utf8 ? &state->encoding_utf8 :
+ &state->encoding[state->gr_set];
+
+ (*encoding->enc->decode)(encoding->enc, encoding->data,
+ codepoints, &npoints, state->gsingle_set ? 1 : maxpoints,
+ bytes, &eaten, len);
+
+ /* There's a chance an encoding (e.g. UTF-8) hasn't found enough bytes yet
+ * for even a single codepoint
+ */
+ if(!npoints)
+ return eaten;
+
+ if(state->gsingle_set && npoints)
+ state->gsingle_set = 0;
+
+ int i = 0;
+
+ /* This is a combining char. that needs to be merged with the previous
+ * glyph output */
+ if(vterm_unicode_is_combining(codepoints[i])) {
+ /* See if the cursor has moved since */
+ if(state->pos.row == state->combine_pos.row && state->pos.col == state->combine_pos.col + state->combine_width) {
+#ifdef DEBUG_GLYPH_COMBINE
+ int printpos;
+ printf("DEBUG: COMBINING SPLIT GLYPH of chars {");
+ for(printpos = 0; state->combine_chars[printpos]; printpos++)
+ printf("U+%04x ", state->combine_chars[printpos]);
+ printf("} + {");
+#endif
+
+ /* Find where we need to append these combining chars */
+ int saved_i = 0;
+ while(state->combine_chars[saved_i])
+ saved_i++;
+
+ /* Add extra ones */
+ while(i < npoints && vterm_unicode_is_combining(codepoints[i])) {
+ if(saved_i >= state->combine_chars_size)
+ grow_combine_buffer(state);
+ state->combine_chars[saved_i++] = codepoints[i++];
+ }
+ if(saved_i >= state->combine_chars_size)
+ grow_combine_buffer(state);
+ state->combine_chars[saved_i] = 0;
+
+#ifdef DEBUG_GLYPH_COMBINE
+ for(; state->combine_chars[printpos]; printpos++)
+ printf("U+%04x ", state->combine_chars[printpos]);
+ printf("}\n");
+#endif
+
+ /* Now render it */
+ putglyph(state, state->combine_chars, state->combine_width, state->combine_pos);
+ }
+ else {
+ DEBUG_LOG("libvterm: TODO: Skip over split char+combining\n");
+ }
+ }
+
+ for(; i < npoints; i++) {
+ // Try to find combining characters following this
+ int glyph_starts = i;
+ int glyph_ends;
+ for(glyph_ends = i + 1;
+ (glyph_ends < npoints) && (glyph_ends < glyph_starts + VTERM_MAX_CHARS_PER_CELL);
+ glyph_ends++)
+ if(!vterm_unicode_is_combining(codepoints[glyph_ends]))
+ break;
+
+ int width = 0;
+
+ uint32_t chars[VTERM_MAX_CHARS_PER_CELL + 1];
+
+ for( ; i < glyph_ends; i++) {
+ chars[i - glyph_starts] = codepoints[i];
+ int this_width = vterm_unicode_width(codepoints[i]);
+#ifdef DEBUG
+ if(this_width < 0) {
+ fprintf(stderr, "Text with negative-width codepoint U+%04x\n", codepoints[i]);
+ abort();
+ }
+#endif
+ width += this_width;
+ }
+
+ while(i < npoints && vterm_unicode_is_combining(codepoints[i]))
+ i++;
+
+ chars[glyph_ends - glyph_starts] = 0;
+ i--;
+
+#ifdef DEBUG_GLYPH_COMBINE
+ int printpos;
+ printf("DEBUG: COMBINED GLYPH of %d chars {", glyph_ends - glyph_starts);
+ for(printpos = 0; printpos < glyph_ends - glyph_starts; printpos++)
+ printf("U+%04x ", chars[printpos]);
+ printf("}, onscreen width %d\n", width);
+#endif
+
+ if(state->at_phantom || state->pos.col + width > THISROWWIDTH(state)) {
+ linefeed(state);
+ state->pos.col = 0;
+ state->at_phantom = 0;
+ state->lineinfo[state->pos.row].continuation = 1;
+ }
+
+ if(state->mode.insert) {
+ /* TODO: This will be a little inefficient for large bodies of text, as
+ * it'll have to 'ICH' effectively before every glyph. We should scan
+ * ahead and ICH as many times as required
+ */
+ VTermRect rect = {
+ .start_row = state->pos.row,
+ .end_row = state->pos.row + 1,
+ .start_col = state->pos.col,
+ .end_col = THISROWWIDTH(state),
+ };
+ scroll(state, rect, 0, -1);
+ }
+
+ putglyph(state, chars, width, state->pos);
+
+ if(i == npoints - 1) {
+ /* End of the buffer. Save the chars in case we have to combine with
+ * more on the next call */
+ int save_i;
+ for(save_i = 0; chars[save_i]; save_i++) {
+ if(save_i >= state->combine_chars_size)
+ grow_combine_buffer(state);
+ state->combine_chars[save_i] = chars[save_i];
+ }
+ if(save_i >= state->combine_chars_size)
+ grow_combine_buffer(state);
+ state->combine_chars[save_i] = 0;
+ state->combine_width = width;
+ state->combine_pos = state->pos;
+ }
+
+ if(state->pos.col + width >= THISROWWIDTH(state)) {
+ if(state->mode.autowrap)
+ state->at_phantom = 1;
+ }
+ else {
+ state->pos.col += width;
+ }
+ }
+
+ updatecursor(state, &oldpos, 0);
+
+#ifdef DEBUG
+ if(state->pos.row < 0 || state->pos.row >= state->rows ||
+ state->pos.col < 0 || state->pos.col >= state->cols) {
+ fprintf(stderr, "Position out of bounds after text: (%d,%d)\n",
+ state->pos.row, state->pos.col);
+ abort();
+ }
+#endif
+
+ return eaten;
+}
+
+static int on_control(unsigned char control, void *user)
+{
+ VTermState *state = user;
+
+ VTermPos oldpos = state->pos;
+
+ switch(control) {
+ case 0x07: // BEL - ECMA-48 8.3.3
+ if(state->callbacks && state->callbacks->bell)
+ (*state->callbacks->bell)(state->cbdata);
+ break;
+
+ case 0x08: // BS - ECMA-48 8.3.5
+ if(state->pos.col > 0)
+ state->pos.col--;
+ break;
+
+ case 0x09: // HT - ECMA-48 8.3.60
+ tab(state, 1, +1);
+ break;
+
+ case 0x0a: // LF - ECMA-48 8.3.74
+ case 0x0b: // VT
+ case 0x0c: // FF
+ linefeed(state);
+ if(state->mode.newline)
+ state->pos.col = 0;
+ break;
+
+ case 0x0d: // CR - ECMA-48 8.3.15
+ state->pos.col = 0;
+ break;
+
+ case 0x0e: // LS1 - ECMA-48 8.3.76
+ state->gl_set = 1;
+ break;
+
+ case 0x0f: // LS0 - ECMA-48 8.3.75
+ state->gl_set = 0;
+ break;
+
+ case 0x84: // IND - DEPRECATED but implemented for completeness
+ linefeed(state);
+ break;
+
+ case 0x85: // NEL - ECMA-48 8.3.86
+ linefeed(state);
+ state->pos.col = 0;
+ break;
+
+ case 0x88: // HTS - ECMA-48 8.3.62
+ set_col_tabstop(state, state->pos.col);
+ break;
+
+ case 0x8d: // RI - ECMA-48 8.3.104
+ if(state->pos.row == state->scrollregion_top) {
+ VTermRect rect = {
+ .start_row = state->scrollregion_top,
+ .end_row = SCROLLREGION_BOTTOM(state),
+ .start_col = SCROLLREGION_LEFT(state),
+ .end_col = SCROLLREGION_RIGHT(state),
+ };
+
+ scroll(state, rect, -1, 0);
+ }
+ else if(state->pos.row > 0)
+ state->pos.row--;
+ break;
+
+ case 0x8e: // SS2 - ECMA-48 8.3.141
+ state->gsingle_set = 2;
+ break;
+
+ case 0x8f: // SS3 - ECMA-48 8.3.142
+ state->gsingle_set = 3;
+ break;
+
+ default:
+ if(state->fallbacks && state->fallbacks->control)
+ if((*state->fallbacks->control)(control, state->fbdata))
+ return 1;
+
+ return 0;
+ }
+
+ updatecursor(state, &oldpos, 1);
+
+#ifdef DEBUG
+ if(state->pos.row < 0 || state->pos.row >= state->rows ||
+ state->pos.col < 0 || state->pos.col >= state->cols) {
+ fprintf(stderr, "Position out of bounds after Ctrl %02x: (%d,%d)\n",
+ control, state->pos.row, state->pos.col);
+ abort();
+ }
+#endif
+
+ return 1;
+}
+
+static int settermprop_bool(VTermState *state, VTermProp prop, int v)
+{
+ VTermValue val = { .boolean = v };
+ return vterm_state_set_termprop(state, prop, &val);
+}
+
+static int settermprop_int(VTermState *state, VTermProp prop, int v)
+{
+ VTermValue val = { .number = v };
+ return vterm_state_set_termprop(state, prop, &val);
+}
+
+static int settermprop_string(VTermState *state, VTermProp prop, VTermStringFragment frag)
+{
+ VTermValue val = { .string = frag };
+ return vterm_state_set_termprop(state, prop, &val);
+}
+
+static void savecursor(VTermState *state, int save)
+{
+ if(save) {
+ state->saved.pos = state->pos;
+ state->saved.mode.cursor_visible = state->mode.cursor_visible;
+ state->saved.mode.cursor_blink = state->mode.cursor_blink;
+ state->saved.mode.cursor_shape = state->mode.cursor_shape;
+
+ vterm_state_savepen(state, 1);
+ }
+ else {
+ VTermPos oldpos = state->pos;
+
+ state->pos = state->saved.pos;
+
+ settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, state->saved.mode.cursor_visible);
+ settermprop_bool(state, VTERM_PROP_CURSORBLINK, state->saved.mode.cursor_blink);
+ settermprop_int (state, VTERM_PROP_CURSORSHAPE, state->saved.mode.cursor_shape);
+
+ vterm_state_savepen(state, 0);
+
+ updatecursor(state, &oldpos, 1);
+ }
+}
+
+static int on_escape(const char *bytes, size_t len, void *user)
+{
+ VTermState *state = user;
+
+ /* Easier to decode this from the first byte, even though the final
+ * byte terminates it
+ */
+ switch(bytes[0]) {
+ case ' ':
+ if(len != 2)
+ return 0;
+
+ switch(bytes[1]) {
+ case 'F': // S7C1T
+ state->vt->mode.ctrl8bit = 0;
+ break;
+
+ case 'G': // S8C1T
+ state->vt->mode.ctrl8bit = 1;
+ break;
+
+ default:
+ return 0;
+ }
+ return 2;
+
+ case '#':
+ if(len != 2)
+ return 0;
+
+ switch(bytes[1]) {
+ case '3': // DECDHL top
+ if(state->mode.leftrightmargin)
+ break;
+ set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_TOP);
+ break;
+
+ case '4': // DECDHL bottom
+ if(state->mode.leftrightmargin)
+ break;
+ set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_BOTTOM);
+ break;
+
+ case '5': // DECSWL
+ if(state->mode.leftrightmargin)
+ break;
+ set_lineinfo(state, state->pos.row, NO_FORCE, DWL_OFF, DHL_OFF);
+ break;
+
+ case '6': // DECDWL
+ if(state->mode.leftrightmargin)
+ break;
+ set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_OFF);
+ break;
+
+ case '8': // DECALN
+ {
+ VTermPos pos;
+ uint32_t E[] = { 'E', 0 };
+ for(pos.row = 0; pos.row < state->rows; pos.row++)
+ for(pos.col = 0; pos.col < ROWWIDTH(state, pos.row); pos.col++)
+ putglyph(state, E, 1, pos);
+ break;
+ }
+
+ default:
+ return 0;
+ }
+ return 2;
+
+ case '(': case ')': case '*': case '+': // SCS
+ if(len != 2)
+ return 0;
+
+ {
+ int setnum = bytes[0] - 0x28;
+ VTermEncoding *newenc = vterm_lookup_encoding(ENC_SINGLE_94, bytes[1]);
+
+ if(newenc) {
+ state->encoding[setnum].enc = newenc;
+
+ if(newenc->init)
+ (*newenc->init)(newenc, state->encoding[setnum].data);
+ }
+ }
+
+ return 2;
+
+ case '7': // DECSC
+ savecursor(state, 1);
+ return 1;
+
+ case '8': // DECRC
+ savecursor(state, 0);
+ return 1;
+
+ case '<': // Ignored by VT100. Used in VT52 mode to switch up to VT100
+ return 1;
+
+ case '=': // DECKPAM
+ state->mode.keypad = 1;
+ return 1;
+
+ case '>': // DECKPNM
+ state->mode.keypad = 0;
+ return 1;
+
+ case 'c': // RIS - ECMA-48 8.3.105
+ {
+ VTermPos oldpos = state->pos;
+ vterm_state_reset(state, 1);
+ if(state->callbacks && state->callbacks->movecursor)
+ (*state->callbacks->movecursor)(state->pos, oldpos, state->mode.cursor_visible, state->cbdata);
+ return 1;
+ }
+
+ case 'n': // LS2 - ECMA-48 8.3.78
+ state->gl_set = 2;
+ return 1;
+
+ case 'o': // LS3 - ECMA-48 8.3.80
+ state->gl_set = 3;
+ return 1;
+
+ case '~': // LS1R - ECMA-48 8.3.77
+ state->gr_set = 1;
+ return 1;
+
+ case '}': // LS2R - ECMA-48 8.3.79
+ state->gr_set = 2;
+ return 1;
+
+ case '|': // LS3R - ECMA-48 8.3.81
+ state->gr_set = 3;
+ return 1;
+
+ default:
+ return 0;
+ }
+}
+
+static void set_mode(VTermState *state, int num, int val)
+{
+ switch(num) {
+ case 4: // IRM - ECMA-48 7.2.10
+ state->mode.insert = val;
+ break;
+
+ case 20: // LNM - ANSI X3.4-1977
+ state->mode.newline = val;
+ break;
+
+ default:
+ DEBUG_LOG("libvterm: Unknown mode %d\n", num);
+ return;
+ }
+}
+
+static void set_dec_mode(VTermState *state, int num, int val)
+{
+ switch(num) {
+ case 1:
+ state->mode.cursor = val;
+ break;
+
+ case 5: // DECSCNM - screen mode
+ settermprop_bool(state, VTERM_PROP_REVERSE, val);
+ break;
+
+ case 6: // DECOM - origin mode
+ {
+ VTermPos oldpos = state->pos;
+ state->mode.origin = val;
+ state->pos.row = state->mode.origin ? state->scrollregion_top : 0;
+ state->pos.col = state->mode.origin ? SCROLLREGION_LEFT(state) : 0;
+ updatecursor(state, &oldpos, 1);
+ }
+ break;
+
+ case 7:
+ state->mode.autowrap = val;
+ break;
+
+ case 12:
+ settermprop_bool(state, VTERM_PROP_CURSORBLINK, val);
+ break;
+
+ case 25:
+ settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, val);
+ break;
+
+ case 69: // DECVSSM - vertical split screen mode
+ // DECLRMM - left/right margin mode
+ state->mode.leftrightmargin = val;
+ if(val) {
+ // Setting DECVSSM must clear doublewidth/doubleheight state of every line
+ for(int row = 0; row < state->rows; row++)
+ set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
+ }
+
+ break;
+
+ case 1000:
+ case 1002:
+ case 1003:
+ settermprop_int(state, VTERM_PROP_MOUSE,
+ !val ? VTERM_PROP_MOUSE_NONE :
+ (num == 1000) ? VTERM_PROP_MOUSE_CLICK :
+ (num == 1002) ? VTERM_PROP_MOUSE_DRAG :
+ VTERM_PROP_MOUSE_MOVE);
+ break;
+
+ case 1004:
+ state->mode.report_focus = val;
+ break;
+
+ case 1005:
+ state->mouse_protocol = val ? MOUSE_UTF8 : MOUSE_X10;
+ break;
+
+ case 1006:
+ state->mouse_protocol = val ? MOUSE_SGR : MOUSE_X10;
+ break;
+
+ case 1015:
+ state->mouse_protocol = val ? MOUSE_RXVT : MOUSE_X10;
+ break;
+
+ case 1047:
+ settermprop_bool(state, VTERM_PROP_ALTSCREEN, val);
+ break;
+
+ case 1048:
+ savecursor(state, val);
+ break;
+
+ case 1049:
+ settermprop_bool(state, VTERM_PROP_ALTSCREEN, val);
+ savecursor(state, val);
+ break;
+
+ case 2004:
+ state->mode.bracketpaste = val;
+ break;
+
+ default:
+ DEBUG_LOG("libvterm: Unknown DEC mode %d\n", num);
+ return;
+ }
+}
+
+static void request_dec_mode(VTermState *state, int num)
+{
+ int reply;
+
+ switch(num) {
+ case 1:
+ reply = state->mode.cursor;
+ break;
+
+ case 5:
+ reply = state->mode.screen;
+ break;
+
+ case 6:
+ reply = state->mode.origin;
+ break;
+
+ case 7:
+ reply = state->mode.autowrap;
+ break;
+
+ case 12:
+ reply = state->mode.cursor_blink;
+ break;
+
+ case 25:
+ reply = state->mode.cursor_visible;
+ break;
+
+ case 69:
+ reply = state->mode.leftrightmargin;
+ break;
+
+ case 1000:
+ reply = state->mouse_flags == MOUSE_WANT_CLICK;
+ break;
+
+ case 1002:
+ reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_DRAG);
+ break;
+
+ case 1003:
+ reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_MOVE);
+ break;
+
+ case 1004:
+ reply = state->mode.report_focus;
+ break;
+
+ case 1005:
+ reply = state->mouse_protocol == MOUSE_UTF8;
+ break;
+
+ case 1006:
+ reply = state->mouse_protocol == MOUSE_SGR;
+ break;
+
+ case 1015:
+ reply = state->mouse_protocol == MOUSE_RXVT;
+ break;
+
+ case 1047:
+ reply = state->mode.alt_screen;
+ break;
+
+ case 2004:
+ reply = state->mode.bracketpaste;
+ break;
+
+ default:
+ vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, 0);
+ return;
+ }
+
+ vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, reply ? 1 : 2);
+}
+
+static void request_version_string(VTermState *state)
+{
+ vterm_push_output_sprintf_str(state->vt, C1_DCS, true, ">|libvterm(%d.%d)",
+ VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR);
+}
+
+static int on_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user)
+{
+ VTermState *state = user;
+ int leader_byte = 0;
+ int intermed_byte = 0;
+ int cancel_phantom = 1;
+
+ if(leader && leader[0]) {
+ if(leader[1]) // longer than 1 char
+ return 0;
+
+ switch(leader[0]) {
+ case '?':
+ case '>':
+ leader_byte = leader[0];
+ break;
+ default:
+ return 0;
+ }
+ }
+
+ if(intermed && intermed[0]) {
+ if(intermed[1]) // longer than 1 char
+ return 0;
+
+ switch(intermed[0]) {
+ case ' ':
+ case '"':
+ case '$':
+ case '\'':
+ intermed_byte = intermed[0];
+ break;
+ default:
+ return 0;
+ }
+ }
+
+ VTermPos oldpos = state->pos;
+
+ // Some temporaries for later code
+ int count, val;
+ int row, col;
+ VTermRect rect;
+ int selective;
+
+#define LBOUND(v,min) if((v) < (min)) (v) = (min)
+#define UBOUND(v,max) if((v) > (max)) (v) = (max)
+
+#define LEADER(l,b) ((l << 8) | b)
+#define INTERMED(i,b) ((i << 16) | b)
+
+ switch(intermed_byte << 16 | leader_byte << 8 | command) {
+ case 0x40: // ICH - ECMA-48 8.3.64
+ count = CSI_ARG_COUNT(args[0]);
+
+ if(!is_cursor_in_scrollregion(state))
+ break;
+
+ rect.start_row = state->pos.row;
+ rect.end_row = state->pos.row + 1;
+ rect.start_col = state->pos.col;
+ if(state->mode.leftrightmargin)
+ rect.end_col = SCROLLREGION_RIGHT(state);
+ else
+ rect.end_col = THISROWWIDTH(state);
+
+ scroll(state, rect, 0, -count);
+
+ break;
+
+ case 0x41: // CUU - ECMA-48 8.3.22
+ count = CSI_ARG_COUNT(args[0]);
+ state->pos.row -= count;
+ state->at_phantom = 0;
+ break;
+
+ case 0x42: // CUD - ECMA-48 8.3.19
+ count = CSI_ARG_COUNT(args[0]);
+ state->pos.row += count;
+ state->at_phantom = 0;
+ break;
+
+ case 0x43: // CUF - ECMA-48 8.3.20
+ count = CSI_ARG_COUNT(args[0]);
+ state->pos.col += count;
+ state->at_phantom = 0;
+ break;
+
+ case 0x44: // CUB - ECMA-48 8.3.18
+ count = CSI_ARG_COUNT(args[0]);
+ state->pos.col -= count;
+ state->at_phantom = 0;
+ break;
+
+ case 0x45: // CNL - ECMA-48 8.3.12
+ count = CSI_ARG_COUNT(args[0]);
+ state->pos.col = 0;
+ state->pos.row += count;
+ state->at_phantom = 0;
+ break;
+
+ case 0x46: // CPL - ECMA-48 8.3.13
+ count = CSI_ARG_COUNT(args[0]);
+ state->pos.col = 0;
+ state->pos.row -= count;
+ state->at_phantom = 0;
+ break;
+
+ case 0x47: // CHA - ECMA-48 8.3.9
+ val = CSI_ARG_OR(args[0], 1);
+ state->pos.col = val-1;
+ state->at_phantom = 0;
+ break;
+
+ case 0x48: // CUP - ECMA-48 8.3.21
+ row = CSI_ARG_OR(args[0], 1);
+ col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]);
+ // zero-based
+ state->pos.row = row-1;
+ state->pos.col = col-1;
+ if(state->mode.origin) {
+ state->pos.row += state->scrollregion_top;
+ state->pos.col += SCROLLREGION_LEFT(state);
+ }
+ state->at_phantom = 0;
+ break;
+
+ case 0x49: // CHT - ECMA-48 8.3.10
+ count = CSI_ARG_COUNT(args[0]);
+ tab(state, count, +1);
+ break;
+
+ case 0x4a: // ED - ECMA-48 8.3.39
+ case LEADER('?', 0x4a): // DECSED - Selective Erase in Display
+ selective = (leader_byte == '?');
+ switch(CSI_ARG(args[0])) {
+ case CSI_ARG_MISSING:
+ case 0:
+ rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1;
+ rect.start_col = state->pos.col; rect.end_col = state->cols;
+ if(rect.end_col > rect.start_col)
+ erase(state, rect, selective);
+
+ rect.start_row = state->pos.row + 1; rect.end_row = state->rows;
+ rect.start_col = 0;
+ for(int row = rect.start_row; row < rect.end_row; row++)
+ set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
+ if(rect.end_row > rect.start_row)
+ erase(state, rect, selective);
+ break;
+
+ case 1:
+ rect.start_row = 0; rect.end_row = state->pos.row;
+ rect.start_col = 0; rect.end_col = state->cols;
+ for(int row = rect.start_row; row < rect.end_row; row++)
+ set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
+ if(rect.end_col > rect.start_col)
+ erase(state, rect, selective);
+
+ rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1;
+ rect.end_col = state->pos.col + 1;
+ if(rect.end_row > rect.start_row)
+ erase(state, rect, selective);
+ break;
+
+ case 2:
+ rect.start_row = 0; rect.end_row = state->rows;
+ rect.start_col = 0; rect.end_col = state->cols;
+ for(int row = rect.start_row; row < rect.end_row; row++)
+ set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
+ erase(state, rect, selective);
+ break;
+
+ case 3:
+ if(state->callbacks && state->callbacks->sb_clear)
+ if((*state->callbacks->sb_clear)(state->cbdata))
+ return 1;
+ break;
+ }
+ break;
+
+ case 0x4b: // EL - ECMA-48 8.3.41
+ case LEADER('?', 0x4b): // DECSEL - Selective Erase in Line
+ selective = (leader_byte == '?');
+ rect.start_row = state->pos.row;
+ rect.end_row = state->pos.row + 1;
+
+ switch(CSI_ARG(args[0])) {
+ case CSI_ARG_MISSING:
+ case 0:
+ rect.start_col = state->pos.col; rect.end_col = THISROWWIDTH(state); break;
+ case 1:
+ rect.start_col = 0; rect.end_col = state->pos.col + 1; break;
+ case 2:
+ rect.start_col = 0; rect.end_col = THISROWWIDTH(state); break;
+ default:
+ return 0;
+ }
+
+ if(rect.end_col > rect.start_col)
+ erase(state, rect, selective);
+
+ break;
+
+ case 0x4c: // IL - ECMA-48 8.3.67
+ count = CSI_ARG_COUNT(args[0]);
+
+ if(!is_cursor_in_scrollregion(state))
+ break;
+
+ rect.start_row = state->pos.row;
+ rect.end_row = SCROLLREGION_BOTTOM(state);
+ rect.start_col = SCROLLREGION_LEFT(state);
+ rect.end_col = SCROLLREGION_RIGHT(state);
+
+ scroll(state, rect, -count, 0);
+
+ break;
+
+ case 0x4d: // DL - ECMA-48 8.3.32
+ count = CSI_ARG_COUNT(args[0]);
+
+ if(!is_cursor_in_scrollregion(state))
+ break;
+
+ rect.start_row = state->pos.row;
+ rect.end_row = SCROLLREGION_BOTTOM(state);
+ rect.start_col = SCROLLREGION_LEFT(state);
+ rect.end_col = SCROLLREGION_RIGHT(state);
+
+ scroll(state, rect, count, 0);
+
+ break;
+
+ case 0x50: // DCH - ECMA-48 8.3.26
+ count = CSI_ARG_COUNT(args[0]);
+
+ if(!is_cursor_in_scrollregion(state))
+ break;
+
+ rect.start_row = state->pos.row;
+ rect.end_row = state->pos.row + 1;
+ rect.start_col = state->pos.col;
+ if(state->mode.leftrightmargin)
+ rect.end_col = SCROLLREGION_RIGHT(state);
+ else
+ rect.end_col = THISROWWIDTH(state);
+
+ scroll(state, rect, 0, count);
+
+ break;
+
+ case 0x53: // SU - ECMA-48 8.3.147
+ count = CSI_ARG_COUNT(args[0]);
+
+ rect.start_row = state->scrollregion_top;
+ rect.end_row = SCROLLREGION_BOTTOM(state);
+ rect.start_col = SCROLLREGION_LEFT(state);
+ rect.end_col = SCROLLREGION_RIGHT(state);
+
+ scroll(state, rect, count, 0);
+
+ break;
+
+ case 0x54: // SD - ECMA-48 8.3.113
+ count = CSI_ARG_COUNT(args[0]);
+
+ rect.start_row = state->scrollregion_top;
+ rect.end_row = SCROLLREGION_BOTTOM(state);
+ rect.start_col = SCROLLREGION_LEFT(state);
+ rect.end_col = SCROLLREGION_RIGHT(state);
+
+ scroll(state, rect, -count, 0);
+
+ break;
+
+ case 0x58: // ECH - ECMA-48 8.3.38
+ count = CSI_ARG_COUNT(args[0]);
+
+ rect.start_row = state->pos.row;
+ rect.end_row = state->pos.row + 1;
+ rect.start_col = state->pos.col;
+ rect.end_col = state->pos.col + count;
+ UBOUND(rect.end_col, THISROWWIDTH(state));
+
+ erase(state, rect, 0);
+ break;
+
+ case 0x5a: // CBT - ECMA-48 8.3.7
+ count = CSI_ARG_COUNT(args[0]);
+ tab(state, count, -1);
+ break;
+
+ case 0x60: // HPA - ECMA-48 8.3.57
+ col = CSI_ARG_OR(args[0], 1);
+ state->pos.col = col-1;
+ state->at_phantom = 0;
+ break;
+
+ case 0x61: // HPR - ECMA-48 8.3.59
+ count = CSI_ARG_COUNT(args[0]);
+ state->pos.col += count;
+ state->at_phantom = 0;
+ break;
+
+ case 0x62: { // REP - ECMA-48 8.3.103
+ const int row_width = THISROWWIDTH(state);
+ count = CSI_ARG_COUNT(args[0]);
+ col = state->pos.col + count;
+ UBOUND(col, row_width);
+ while (state->pos.col < col) {
+ putglyph(state, state->combine_chars, state->combine_width, state->pos);
+ state->pos.col += state->combine_width;
+ }
+ if (state->pos.col + state->combine_width >= row_width) {
+ if (state->mode.autowrap) {
+ state->at_phantom = 1;
+ cancel_phantom = 0;
+ }
+ }
+ break;
+ }
+
+ case 0x63: // DA - ECMA-48 8.3.24
+ val = CSI_ARG_OR(args[0], 0);
+ if(val == 0)
+ // DEC VT100 response
+ vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?1;2c");
+ break;
+
+ case LEADER('>', 0x63): // DEC secondary Device Attributes
+ vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, ">%d;%d;%dc", 0, 100, 0);
+ break;
+
+ case 0x64: // VPA - ECMA-48 8.3.158
+ row = CSI_ARG_OR(args[0], 1);
+ state->pos.row = row-1;
+ if(state->mode.origin)
+ state->pos.row += state->scrollregion_top;
+ state->at_phantom = 0;
+ break;
+
+ case 0x65: // VPR - ECMA-48 8.3.160
+ count = CSI_ARG_COUNT(args[0]);
+ state->pos.row += count;
+ state->at_phantom = 0;
+ break;
+
+ case 0x66: // HVP - ECMA-48 8.3.63
+ row = CSI_ARG_OR(args[0], 1);
+ col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]);
+ // zero-based
+ state->pos.row = row-1;
+ state->pos.col = col-1;
+ if(state->mode.origin) {
+ state->pos.row += state->scrollregion_top;
+ state->pos.col += SCROLLREGION_LEFT(state);
+ }
+ state->at_phantom = 0;
+ break;
+
+ case 0x67: // TBC - ECMA-48 8.3.154
+ val = CSI_ARG_OR(args[0], 0);
+
+ switch(val) {
+ case 0:
+ clear_col_tabstop(state, state->pos.col);
+ break;
+ case 3:
+ case 5:
+ for(col = 0; col < state->cols; col++)
+ clear_col_tabstop(state, col);
+ break;
+ case 1:
+ case 2:
+ case 4:
+ break;
+ /* TODO: 1, 2 and 4 aren't meaningful yet without line tab stops */
+ default:
+ return 0;
+ }
+ break;
+
+ case 0x68: // SM - ECMA-48 8.3.125
+ if(!CSI_ARG_IS_MISSING(args[0]))
+ set_mode(state, CSI_ARG(args[0]), 1);
+ break;
+
+ case LEADER('?', 0x68): // DEC private mode set
+ if(!CSI_ARG_IS_MISSING(args[0]))
+ set_dec_mode(state, CSI_ARG(args[0]), 1);
+ break;
+
+ case 0x6a: // HPB - ECMA-48 8.3.58
+ count = CSI_ARG_COUNT(args[0]);
+ state->pos.col -= count;
+ state->at_phantom = 0;
+ break;
+
+ case 0x6b: // VPB - ECMA-48 8.3.159
+ count = CSI_ARG_COUNT(args[0]);
+ state->pos.row -= count;
+ state->at_phantom = 0;
+ break;
+
+ case 0x6c: // RM - ECMA-48 8.3.106
+ if(!CSI_ARG_IS_MISSING(args[0]))
+ set_mode(state, CSI_ARG(args[0]), 0);
+ break;
+
+ case LEADER('?', 0x6c): // DEC private mode reset
+ if(!CSI_ARG_IS_MISSING(args[0]))
+ set_dec_mode(state, CSI_ARG(args[0]), 0);
+ break;
+
+ case 0x6d: // SGR - ECMA-48 8.3.117
+ vterm_state_setpen(state, args, argcount);
+ break;
+
+ case LEADER('?', 0x6d): // DECSGR
+ /* No actual DEC terminal recognised these, but some printers did. These
+ * are alternative ways to request subscript/superscript/off
+ */
+ for(int argi = 0; argi < argcount; argi++) {
+ long arg;
+ switch(arg = CSI_ARG(args[argi])) {
+ case 4: // Superscript on
+ arg = 73;
+ vterm_state_setpen(state, &arg, 1);
+ break;
+ case 5: // Subscript on
+ arg = 74;
+ vterm_state_setpen(state, &arg, 1);
+ break;
+ case 24: // Super+subscript off
+ arg = 75;
+ vterm_state_setpen(state, &arg, 1);
+ break;
+ }
+ }
+ break;
+
+ case 0x6e: // DSR - ECMA-48 8.3.35
+ case LEADER('?', 0x6e): // DECDSR
+ val = CSI_ARG_OR(args[0], 0);
+
+ {
+ char *qmark = (leader_byte == '?') ? "?" : "";
+
+ switch(val) {
+ case 0: case 1: case 2: case 3: case 4:
+ // ignore - these are replies
+ break;
+ case 5:
+ vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s0n", qmark);
+ break;
+ case 6: // CPR - cursor position report
+ vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s%d;%dR", qmark, state->pos.row + 1, state->pos.col + 1);
+ break;
+ }
+ }
+ break;
+
+
+ case LEADER('!', 0x70): // DECSTR - DEC soft terminal reset
+ vterm_state_reset(state, 0);
+ break;
+
+ case LEADER('?', INTERMED('$', 0x70)):
+ request_dec_mode(state, CSI_ARG(args[0]));
+ break;
+
+ case LEADER('>', 0x71): // XTVERSION - xterm query version string
+ request_version_string(state);
+ break;
+
+ case INTERMED(' ', 0x71): // DECSCUSR - DEC set cursor shape
+ val = CSI_ARG_OR(args[0], 1);
+
+ switch(val) {
+ case 0: case 1:
+ settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
+ settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK);
+ break;
+ case 2:
+ settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0);
+ settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK);
+ break;
+ case 3:
+ settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
+ settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE);
+ break;
+ case 4:
+ settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0);
+ settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE);
+ break;
+ case 5:
+ settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
+ settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT);
+ break;
+ case 6:
+ settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0);
+ settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT);
+ break;
+ }
+
+ break;
+
+ case INTERMED('"', 0x71): // DECSCA - DEC select character protection attribute
+ val = CSI_ARG_OR(args[0], 0);
+
+ switch(val) {
+ case 0: case 2:
+ state->protected_cell = 0;
+ break;
+ case 1:
+ state->protected_cell = 1;
+ break;
+ }
+
+ break;
+
+ case 0x72: // DECSTBM - DEC custom
+ state->scrollregion_top = CSI_ARG_OR(args[0], 1) - 1;
+ state->scrollregion_bottom = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]);
+ LBOUND(state->scrollregion_top, 0);
+ UBOUND(state->scrollregion_top, state->rows);
+ LBOUND(state->scrollregion_bottom, -1);
+ if(state->scrollregion_top == 0 && state->scrollregion_bottom == state->rows)
+ state->scrollregion_bottom = -1;
+ else
+ UBOUND(state->scrollregion_bottom, state->rows);
+
+ if(SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) {
+ // Invalid
+ state->scrollregion_top = 0;
+ state->scrollregion_bottom = -1;
+ }
+
+ // Setting the scrolling region restores the cursor to the home position
+ state->pos.row = 0;
+ state->pos.col = 0;
+ if(state->mode.origin) {
+ state->pos.row += state->scrollregion_top;
+ state->pos.col += SCROLLREGION_LEFT(state);
+ }
+
+ break;
+
+ case 0x73: // DECSLRM - DEC custom
+ // Always allow setting these margins, just they won't take effect without DECVSSM
+ state->scrollregion_left = CSI_ARG_OR(args[0], 1) - 1;
+ state->scrollregion_right = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]);
+ LBOUND(state->scrollregion_left, 0);
+ UBOUND(state->scrollregion_left, state->cols);
+ LBOUND(state->scrollregion_right, -1);
+ if(state->scrollregion_left == 0 && state->scrollregion_right == state->cols)
+ state->scrollregion_right = -1;
+ else
+ UBOUND(state->scrollregion_right, state->cols);
+
+ if(state->scrollregion_right > -1 &&
+ state->scrollregion_right <= state->scrollregion_left) {
+ // Invalid
+ state->scrollregion_left = 0;
+ state->scrollregion_right = -1;
+ }
+
+ // Setting the scrolling region restores the cursor to the home position
+ state->pos.row = 0;
+ state->pos.col = 0;
+ if(state->mode.origin) {
+ state->pos.row += state->scrollregion_top;
+ state->pos.col += SCROLLREGION_LEFT(state);
+ }
+
+ break;
+
+ case INTERMED('\'', 0x7D): // DECIC
+ count = CSI_ARG_COUNT(args[0]);
+
+ if(!is_cursor_in_scrollregion(state))
+ break;
+
+ rect.start_row = state->scrollregion_top;
+ rect.end_row = SCROLLREGION_BOTTOM(state);
+ rect.start_col = state->pos.col;
+ rect.end_col = SCROLLREGION_RIGHT(state);
+
+ scroll(state, rect, 0, -count);
+
+ break;
+
+ case INTERMED('\'', 0x7E): // DECDC
+ count = CSI_ARG_COUNT(args[0]);
+
+ if(!is_cursor_in_scrollregion(state))
+ break;
+
+ rect.start_row = state->scrollregion_top;
+ rect.end_row = SCROLLREGION_BOTTOM(state);
+ rect.start_col = state->pos.col;
+ rect.end_col = SCROLLREGION_RIGHT(state);
+
+ scroll(state, rect, 0, count);
+
+ break;
+
+ default:
+ if(state->fallbacks && state->fallbacks->csi)
+ if((*state->fallbacks->csi)(leader, args, argcount, intermed, command, state->fbdata))
+ return 1;
+
+ return 0;
+ }
+
+ if(state->mode.origin) {
+ LBOUND(state->pos.row, state->scrollregion_top);
+ UBOUND(state->pos.row, SCROLLREGION_BOTTOM(state)-1);
+ LBOUND(state->pos.col, SCROLLREGION_LEFT(state));
+ UBOUND(state->pos.col, SCROLLREGION_RIGHT(state)-1);
+ }
+ else {
+ LBOUND(state->pos.row, 0);
+ UBOUND(state->pos.row, state->rows-1);
+ LBOUND(state->pos.col, 0);
+ UBOUND(state->pos.col, THISROWWIDTH(state)-1);
+ }
+
+ updatecursor(state, &oldpos, cancel_phantom);
+
+#ifdef DEBUG
+ if(state->pos.row < 0 || state->pos.row >= state->rows ||
+ state->pos.col < 0 || state->pos.col >= state->cols) {
+ fprintf(stderr, "Position out of bounds after CSI %c: (%d,%d)\n",
+ command, state->pos.row, state->pos.col);
+ abort();
+ }
+
+ if(SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) {
+ fprintf(stderr, "Scroll region height out of bounds after CSI %c: %d <= %d\n",
+ command, SCROLLREGION_BOTTOM(state), state->scrollregion_top);
+ abort();
+ }
+
+ if(SCROLLREGION_RIGHT(state) <= SCROLLREGION_LEFT(state)) {
+ fprintf(stderr, "Scroll region width out of bounds after CSI %c: %d <= %d\n",
+ command, SCROLLREGION_RIGHT(state), SCROLLREGION_LEFT(state));
+ abort();
+ }
+#endif
+
+ return 1;
+}
+
+static char base64_one(uint8_t b)
+{
+ if(b < 26)
+ return 'A' + b;
+ else if(b < 52)
+ return 'a' + b - 26;
+ else if(b < 62)
+ return '0' + b - 52;
+ else if(b == 62)
+ return '+';
+ else if(b == 63)
+ return '/';
+ return 0;
+}
+
+static uint8_t unbase64one(char c)
+{
+ if(c >= 'A' && c <= 'Z')
+ return c - 'A';
+ else if(c >= 'a' && c <= 'z')
+ return c - 'a' + 26;
+ else if(c >= '0' && c <= '9')
+ return c - '0' + 52;
+ else if(c == '+')
+ return 62;
+ else if(c == '/')
+ return 63;
+
+ return 0xFF;
+}
+
+static void osc_selection(VTermState *state, VTermStringFragment frag)
+{
+ if(frag.initial) {
+ state->tmp.selection.mask = 0;
+ state->tmp.selection.state = SELECTION_INITIAL;
+ }
+
+ while(!state->tmp.selection.state && frag.len) {
+ /* Parse selection parameter */
+ switch(frag.str[0]) {
+ case 'c':
+ state->tmp.selection.mask |= VTERM_SELECTION_CLIPBOARD;
+ break;
+ case 'p':
+ state->tmp.selection.mask |= VTERM_SELECTION_PRIMARY;
+ break;
+ case 'q':
+ state->tmp.selection.mask |= VTERM_SELECTION_SECONDARY;
+ break;
+ case 's':
+ state->tmp.selection.mask |= VTERM_SELECTION_SELECT;
+ break;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ state->tmp.selection.mask |= (VTERM_SELECTION_CUT0 << (frag.str[0] - '0'));
+ break;
+
+ case ';':
+ state->tmp.selection.state = SELECTION_SELECTED;
+ if(!state->tmp.selection.mask)
+ state->tmp.selection.mask = VTERM_SELECTION_SELECT|VTERM_SELECTION_CUT0;
+ break;
+ }
+
+ frag.str++;
+ frag.len--;
+ }
+
+ if(!frag.len)
+ return;
+
+ if(state->tmp.selection.state == SELECTION_SELECTED) {
+ if(frag.str[0] == '?') {
+ state->tmp.selection.state = SELECTION_QUERY;
+ }
+ else {
+ state->tmp.selection.state = SELECTION_SET_INITIAL;
+ state->tmp.selection.recvpartial = 0;
+ }
+ }
+
+ if(state->tmp.selection.state == SELECTION_QUERY) {
+ if(state->selection.callbacks->query)
+ (*state->selection.callbacks->query)(state->tmp.selection.mask, state->selection.user);
+ return;
+ }
+
+ if(state->selection.callbacks->set) {
+ size_t bufcur = 0;
+ char *buffer = state->selection.buffer;
+
+ uint32_t x = 0; /* Current decoding value */
+ int n = 0; /* Number of sextets consumed */
+
+ if(state->tmp.selection.recvpartial) {
+ n = state->tmp.selection.recvpartial >> 24;
+ x = state->tmp.selection.recvpartial & 0x03FFFF; /* could be up to 18 bits of state in here */
+
+ state->tmp.selection.recvpartial = 0;
+ }
+
+ while((state->selection.buflen - bufcur) >= 3 && frag.len) {
+ if(frag.str[0] == '=') {
+ if(n == 2) {
+ buffer[0] = (x >> 4) & 0xFF;
+ buffer += 1, bufcur += 1;
+ }
+ if(n == 3) {
+ buffer[0] = (x >> 10) & 0xFF;
+ buffer[1] = (x >> 2) & 0xFF;
+ buffer += 2, bufcur += 2;
+ }
+
+ while(frag.len && frag.str[0] == '=')
+ frag.str++, frag.len--;
+
+ n = 0;
+ }
+ else {
+ uint8_t b = unbase64one(frag.str[0]);
+ if(b == 0xFF) {
+ DEBUG_LOG("base64decode bad input %02X\n", (uint8_t)frag.str[0]);
+ }
+ else {
+ x = (x << 6) | b;
+ n++;
+ }
+ frag.str++, frag.len--;
+
+ if(n == 4) {
+ buffer[0] = (x >> 16) & 0xFF;
+ buffer[1] = (x >> 8) & 0xFF;
+ buffer[2] = (x >> 0) & 0xFF;
+
+ buffer += 3, bufcur += 3;
+ x = 0;
+ n = 0;
+ }
+ }
+
+ if(!frag.len || (state->selection.buflen - bufcur) < 3) {
+ if(bufcur) {
+ (*state->selection.callbacks->set)(state->tmp.selection.mask, (VTermStringFragment){
+ .str = state->selection.buffer,
+ .len = bufcur,
+ .initial = state->tmp.selection.state == SELECTION_SET_INITIAL,
+ .final = frag.final,
+ }, state->selection.user);
+ state->tmp.selection.state = SELECTION_SET;
+ }
+
+ buffer = state->selection.buffer;
+ bufcur = 0;
+ }
+ }
+
+ if(n)
+ state->tmp.selection.recvpartial = (n << 24) | x;
+ }
+}
+
+static int on_osc(int command, VTermStringFragment frag, void *user)
+{
+ VTermState *state = user;
+
+ switch(command) {
+ case 0:
+ settermprop_string(state, VTERM_PROP_ICONNAME, frag);
+ settermprop_string(state, VTERM_PROP_TITLE, frag);
+ return 1;
+
+ case 1:
+ settermprop_string(state, VTERM_PROP_ICONNAME, frag);
+ return 1;
+
+ case 2:
+ settermprop_string(state, VTERM_PROP_TITLE, frag);
+ return 1;
+
+ case 52:
+ if(state->selection.callbacks)
+ osc_selection(state, frag);
+
+ return 1;
+
+ default:
+ if(state->fallbacks && state->fallbacks->osc)
+ if((*state->fallbacks->osc)(command, frag, state->fbdata))
+ return 1;
+ }
+
+ return 0;
+}
+
+static void request_status_string(VTermState *state, VTermStringFragment frag)
+{
+ VTerm *vt = state->vt;
+
+ char *tmp = state->tmp.decrqss;
+
+ if(frag.initial)
+ tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0;
+
+ int i = 0;
+ while(i < sizeof(state->tmp.decrqss)-1 && tmp[i])
+ i++;
+ while(i < sizeof(state->tmp.decrqss)-1 && frag.len--)
+ tmp[i++] = (frag.str++)[0];
+ tmp[i] = 0;
+
+ if(!frag.final)
+ return;
+
+ switch(tmp[0] | tmp[1]<<8 | tmp[2]<<16) {
+ case 'm': {
+ // Query SGR
+ long args[20];
+ int argc = vterm_state_getpen(state, args, sizeof(args)/sizeof(args[0]));
+ size_t cur = 0;
+
+ cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,
+ vt->mode.ctrl8bit ? "\x90" "1$r" : ESC_S "P" "1$r"); // DCS 1$r ...
+ if(cur >= vt->tmpbuffer_len)
+ return;
+
+ for(int argi = 0; argi < argc; argi++) {
+ cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,
+ argi == argc - 1 ? "%ld" :
+ CSI_ARG_HAS_MORE(args[argi]) ? "%ld:" :
+ "%ld;",
+ CSI_ARG(args[argi]));
+ if(cur >= vt->tmpbuffer_len)
+ return;
+ }
+
+ cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,
+ vt->mode.ctrl8bit ? "m" "\x9C" : "m" ESC_S "\\"); // ... m ST
+ if(cur >= vt->tmpbuffer_len)
+ return;
+
+ vterm_push_output_bytes(vt, vt->tmpbuffer, cur);
+ return;
+ }
+
+ case 'r':
+ // Query DECSTBM
+ vterm_push_output_sprintf_str(vt, C1_DCS, true,
+ "1$r%d;%dr", state->scrollregion_top+1, SCROLLREGION_BOTTOM(state));
+ return;
+
+ case 's':
+ // Query DECSLRM
+ vterm_push_output_sprintf_str(vt, C1_DCS, true,
+ "1$r%d;%ds", SCROLLREGION_LEFT(state)+1, SCROLLREGION_RIGHT(state));
+ return;
+
+ case ' '|('q'<<8): {
+ // Query DECSCUSR
+ int reply = 2;
+ switch(state->mode.cursor_shape) {
+ case VTERM_PROP_CURSORSHAPE_BLOCK: reply = 2; break;
+ case VTERM_PROP_CURSORSHAPE_UNDERLINE: reply = 4; break;
+ case VTERM_PROP_CURSORSHAPE_BAR_LEFT: reply = 6; break;
+ }
+ if(state->mode.cursor_blink)
+ reply--;
+ vterm_push_output_sprintf_str(vt, C1_DCS, true,
+ "1$r%d q", reply);
+ return;
+ }
+
+ case '\"'|('q'<<8):
+ // Query DECSCA
+ vterm_push_output_sprintf_str(vt, C1_DCS, true,
+ "1$r%d\"q", state->protected_cell ? 1 : 2);
+ return;
+ }
+
+ vterm_push_output_sprintf_str(state->vt, C1_DCS, true, "0$r");
+}
+
+static int on_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user)
+{
+ VTermState *state = user;
+
+ if(commandlen == 2 && strneq(command, "$q", 2)) {
+ request_status_string(state, frag);
+ return 1;
+ }
+ else if(state->fallbacks && state->fallbacks->dcs)
+ if((*state->fallbacks->dcs)(command, commandlen, frag, state->fbdata))
+ return 1;
+
+ DEBUG_LOG("libvterm: Unhandled DCS %.*s\n", (int)commandlen, command);
+ return 0;
+}
+
+static int on_apc(VTermStringFragment frag, void *user)
+{
+ VTermState *state = user;
+
+ if(state->fallbacks && state->fallbacks->apc)
+ if((*state->fallbacks->apc)(frag, state->fbdata))
+ return 1;
+
+ /* No DEBUG_LOG because all APCs are unhandled */
+ return 0;
+}
+
+static int on_pm(VTermStringFragment frag, void *user)
+{
+ VTermState *state = user;
+
+ if(state->fallbacks && state->fallbacks->pm)
+ if((*state->fallbacks->pm)(frag, state->fbdata))
+ return 1;
+
+ /* No DEBUG_LOG because all PMs are unhandled */
+ return 0;
+}
+
+static int on_sos(VTermStringFragment frag, void *user)
+{
+ VTermState *state = user;
+
+ if(state->fallbacks && state->fallbacks->sos)
+ if((*state->fallbacks->sos)(frag, state->fbdata))
+ return 1;
+
+ /* No DEBUG_LOG because all SOSs are unhandled */
+ return 0;
+}
+
+static int on_resize(int rows, int cols, void *user)
+{
+ VTermState *state = user;
+ VTermPos oldpos = state->pos;
+
+ if(cols != state->cols) {
+ unsigned char *newtabstops = vterm_allocator_malloc(state->vt, (cols + 7) / 8);
+
+ /* TODO: This can all be done much more efficiently bytewise */
+ int col;
+ for(col = 0; col < state->cols && col < cols; col++) {
+ unsigned char mask = 1 << (col & 7);
+ if(state->tabstops[col >> 3] & mask)
+ newtabstops[col >> 3] |= mask;
+ else
+ newtabstops[col >> 3] &= ~mask;
+ }
+
+ for( ; col < cols; col++) {
+ unsigned char mask = 1 << (col & 7);
+ if(col % 8 == 0)
+ newtabstops[col >> 3] |= mask;
+ else
+ newtabstops[col >> 3] &= ~mask;
+ }
+
+ vterm_allocator_free(state->vt, state->tabstops);
+ state->tabstops = newtabstops;
+ }
+
+ state->rows = rows;
+ state->cols = cols;
+
+ if(state->scrollregion_bottom > -1)
+ UBOUND(state->scrollregion_bottom, state->rows);
+ if(state->scrollregion_right > -1)
+ UBOUND(state->scrollregion_right, state->cols);
+
+ VTermStateFields fields = {
+ .pos = state->pos,
+ .lineinfos = { [0] = state->lineinfos[0], [1] = state->lineinfos[1] },
+ };
+
+ if(state->callbacks && state->callbacks->resize) {
+ (*state->callbacks->resize)(rows, cols, &fields, state->cbdata);
+ state->pos = fields.pos;
+
+ state->lineinfos[0] = fields.lineinfos[0];
+ state->lineinfos[1] = fields.lineinfos[1];
+ }
+ else {
+ if(rows != state->rows) {
+ for(int bufidx = BUFIDX_PRIMARY; bufidx <= BUFIDX_ALTSCREEN; bufidx++) {
+ VTermLineInfo *oldlineinfo = state->lineinfos[bufidx];
+ if(!oldlineinfo)
+ continue;
+
+ VTermLineInfo *newlineinfo = vterm_allocator_malloc(state->vt, rows * sizeof(VTermLineInfo));
+
+ int row;
+ for(row = 0; row < state->rows && row < rows; row++) {
+ newlineinfo[row] = oldlineinfo[row];
+ }
+
+ for( ; row < rows; row++) {
+ newlineinfo[row] = (VTermLineInfo){
+ .doublewidth = 0,
+ };
+ }
+
+ vterm_allocator_free(state->vt, state->lineinfos[bufidx]);
+ state->lineinfos[bufidx] = newlineinfo;
+ }
+ }
+ }
+
+ state->lineinfo = state->lineinfos[state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY];
+
+ if(state->at_phantom && state->pos.col < cols-1) {
+ state->at_phantom = 0;
+ state->pos.col++;
+ }
+
+ if(state->pos.row < 0)
+ state->pos.row = 0;
+ if(state->pos.row >= rows)
+ state->pos.row = rows - 1;
+ if(state->pos.col < 0)
+ state->pos.col = 0;
+ if(state->pos.col >= cols)
+ state->pos.col = cols - 1;
+
+ updatecursor(state, &oldpos, 1);
+
+ return 1;
+}
+
+static const VTermParserCallbacks parser_callbacks = {
+ .text = on_text,
+ .control = on_control,
+ .escape = on_escape,
+ .csi = on_csi,
+ .osc = on_osc,
+ .dcs = on_dcs,
+ .apc = on_apc,
+ .pm = on_pm,
+ .sos = on_sos,
+ .resize = on_resize,
+};
+
+VTermState *vterm_obtain_state(VTerm *vt)
+{
+ if(vt->state)
+ return vt->state;
+
+ VTermState *state = vterm_state_new(vt);
+ vt->state = state;
+
+ vterm_parser_set_callbacks(vt, &parser_callbacks, state);
+
+ return state;
+}
+
+void vterm_state_reset(VTermState *state, int hard)
+{
+ state->scrollregion_top = 0;
+ state->scrollregion_bottom = -1;
+ state->scrollregion_left = 0;
+ state->scrollregion_right = -1;
+
+ state->mode.keypad = 0;
+ state->mode.cursor = 0;
+ state->mode.autowrap = 1;
+ state->mode.insert = 0;
+ state->mode.newline = 0;
+ state->mode.alt_screen = 0;
+ state->mode.origin = 0;
+ state->mode.leftrightmargin = 0;
+ state->mode.bracketpaste = 0;
+ state->mode.report_focus = 0;
+
+ state->mouse_flags = 0;
+
+ state->vt->mode.ctrl8bit = 0;
+
+ for(int col = 0; col < state->cols; col++)
+ if(col % 8 == 0)
+ set_col_tabstop(state, col);
+ else
+ clear_col_tabstop(state, col);
+
+ for(int row = 0; row < state->rows; row++)
+ set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
+
+ if(state->callbacks && state->callbacks->initpen)
+ (*state->callbacks->initpen)(state->cbdata);
+
+ vterm_state_resetpen(state);
+
+ VTermEncoding *default_enc = state->vt->mode.utf8 ?
+ vterm_lookup_encoding(ENC_UTF8, 'u') :
+ vterm_lookup_encoding(ENC_SINGLE_94, 'B');
+
+ for(int i = 0; i < 4; i++) {
+ state->encoding[i].enc = default_enc;
+ if(default_enc->init)
+ (*default_enc->init)(default_enc, state->encoding[i].data);
+ }
+
+ state->gl_set = 0;
+ state->gr_set = 1;
+ state->gsingle_set = 0;
+
+ state->protected_cell = 0;
+
+ // Initialise the props
+ settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, 1);
+ settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1);
+ settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK);
+
+ if(hard) {
+ state->pos.row = 0;
+ state->pos.col = 0;
+ state->at_phantom = 0;
+
+ VTermRect rect = { 0, state->rows, 0, state->cols };
+ erase(state, rect, 0);
+ }
+}
+
+void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos)
+{
+ *cursorpos = state->pos;
+}
+
+void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user)
+{
+ if(callbacks) {
+ state->callbacks = callbacks;
+ state->cbdata = user;
+
+ if(state->callbacks && state->callbacks->initpen)
+ (*state->callbacks->initpen)(state->cbdata);
+ }
+ else {
+ state->callbacks = NULL;
+ state->cbdata = NULL;
+ }
+}
+
+void *vterm_state_get_cbdata(VTermState *state)
+{
+ return state->cbdata;
+}
+
+void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermStateFallbacks *fallbacks, void *user)
+{
+ if(fallbacks) {
+ state->fallbacks = fallbacks;
+ state->fbdata = user;
+ }
+ else {
+ state->fallbacks = NULL;
+ state->fbdata = NULL;
+ }
+}
+
+void *vterm_state_get_unrecognised_fbdata(VTermState *state)
+{
+ return state->fbdata;
+}
+
+int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val)
+{
+ /* Only store the new value of the property if usercode said it was happy.
+ * This is especially important for altscreen switching */
+ if(state->callbacks && state->callbacks->settermprop)
+ if(!(*state->callbacks->settermprop)(prop, val, state->cbdata))
+ return 0;
+
+ switch(prop) {
+ case VTERM_PROP_TITLE:
+ case VTERM_PROP_ICONNAME:
+ // we don't store these, just transparently pass through
+ return 1;
+ case VTERM_PROP_CURSORVISIBLE:
+ state->mode.cursor_visible = val->boolean;
+ return 1;
+ case VTERM_PROP_CURSORBLINK:
+ state->mode.cursor_blink = val->boolean;
+ return 1;
+ case VTERM_PROP_CURSORSHAPE:
+ state->mode.cursor_shape = val->number;
+ return 1;
+ case VTERM_PROP_REVERSE:
+ state->mode.screen = val->boolean;
+ return 1;
+ case VTERM_PROP_ALTSCREEN:
+ state->mode.alt_screen = val->boolean;
+ state->lineinfo = state->lineinfos[state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY];
+ if(state->mode.alt_screen) {
+ VTermRect rect = {
+ .start_row = 0,
+ .start_col = 0,
+ .end_row = state->rows,
+ .end_col = state->cols,
+ };
+ erase(state, rect, 0);
+ }
+ return 1;
+ case VTERM_PROP_MOUSE:
+ state->mouse_flags = 0;
+ if(val->number)
+ state->mouse_flags |= MOUSE_WANT_CLICK;
+ if(val->number == VTERM_PROP_MOUSE_DRAG)
+ state->mouse_flags |= MOUSE_WANT_DRAG;
+ if(val->number == VTERM_PROP_MOUSE_MOVE)
+ state->mouse_flags |= MOUSE_WANT_MOVE;
+ return 1;
+
+ case VTERM_N_PROPS:
+ return 0;
+ }
+
+ return 0;
+}
+
+void vterm_state_focus_in(VTermState *state)
+{
+ if(state->mode.report_focus)
+ vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "I");
+}
+
+void vterm_state_focus_out(VTermState *state)
+{
+ if(state->mode.report_focus)
+ vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "O");
+}
+
+const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row)
+{
+ return state->lineinfo + row;
+}
+
+void vterm_state_set_selection_callbacks(VTermState *state, const VTermSelectionCallbacks *callbacks, void *user,
+ char *buffer, size_t buflen)
+{
+ if(buflen && !buffer)
+ buffer = vterm_allocator_malloc(state->vt, buflen);
+
+ state->selection.callbacks = callbacks;
+ state->selection.user = user;
+ state->selection.buffer = buffer;
+ state->selection.buflen = buflen;
+}
+
+void vterm_state_send_selection(VTermState *state, VTermSelectionMask mask, VTermStringFragment frag)
+{
+ VTerm *vt = state->vt;
+
+ if(frag.initial) {
+ /* TODO: support sending more than one mask bit */
+ const static char selection_chars[] = "cpqs";
+ int idx;
+ for(idx = 0; idx < 4; idx++)
+ if(mask & (1 << idx))
+ break;
+
+ vterm_push_output_sprintf_str(vt, C1_OSC, false, "52;%c;", selection_chars[idx]);
+
+ state->tmp.selection.sendpartial = 0;
+ }
+
+ if(frag.len) {
+ size_t bufcur = 0;
+ char *buffer = state->selection.buffer;
+
+ uint32_t x = 0;
+ int n = 0;
+
+ if(state->tmp.selection.sendpartial) {
+ n = state->tmp.selection.sendpartial >> 24;
+ x = state->tmp.selection.sendpartial & 0xFFFFFF;
+
+ state->tmp.selection.sendpartial = 0;
+ }
+
+ while((state->selection.buflen - bufcur) >= 4 && frag.len) {
+ x = (x << 8) | frag.str[0];
+ n++;
+ frag.str++, frag.len--;
+
+ if(n == 3) {
+ buffer[0] = base64_one((x >> 18) & 0x3F);
+ buffer[1] = base64_one((x >> 12) & 0x3F);
+ buffer[2] = base64_one((x >> 6) & 0x3F);
+ buffer[3] = base64_one((x >> 0) & 0x3F);
+
+ buffer += 4, bufcur += 4;
+ x = 0;
+ n = 0;
+ }
+
+ if(!frag.len || (state->selection.buflen - bufcur) < 4) {
+ if(bufcur)
+ vterm_push_output_bytes(vt, state->selection.buffer, bufcur);
+
+ buffer = state->selection.buffer;
+ bufcur = 0;
+ }
+ }
+
+ if(n)
+ state->tmp.selection.sendpartial = (n << 24) | x;
+ }
+
+ if(frag.final) {
+ if(state->tmp.selection.sendpartial) {
+ int n = state->tmp.selection.sendpartial >> 24;
+ uint32_t x = state->tmp.selection.sendpartial & 0xFFFFFF;
+ char *buffer = state->selection.buffer;
+
+ /* n is either 1 or 2 now */
+ x <<= (n == 1) ? 16 : 8;
+
+ buffer[0] = base64_one((x >> 18) & 0x3F);
+ buffer[1] = base64_one((x >> 12) & 0x3F);
+ buffer[2] = (n == 1) ? '=' : base64_one((x >> 6) & 0x3F);
+ buffer[3] = '=';
+
+ vterm_push_output_sprintf_str(vt, 0, true, "%.*s", 4, buffer);
+ }
+ else
+ vterm_push_output_sprintf_str(vt, 0, true, "");
+ }
+}
diff --git a/src/libs/3rdparty/libvterm/src/unicode.c b/src/libs/3rdparty/libvterm/src/unicode.c
new file mode 100644
index 00000000000..269244ff6b9
--- /dev/null
+++ b/src/libs/3rdparty/libvterm/src/unicode.c
@@ -0,0 +1,313 @@
+#include "vterm_internal.h"
+
+// ### The following from http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
+// With modifications:
+// made functions static
+// moved 'combining' table to file scope, so other functions can see it
+// ###################################################################
+
+/*
+ * This is an implementation of wcwidth() and wcswidth() (defined in
+ * IEEE Std 1002.1-2001) for Unicode.
+ *
+ * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html
+ * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html
+ *
+ * In fixed-width output devices, Latin characters all occupy a single
+ * "cell" position of equal width, whereas ideographic CJK characters
+ * occupy two such cells. Interoperability between terminal-line
+ * applications and (teletype-style) character terminals using the
+ * UTF-8 encoding requires agreement on which character should advance
+ * the cursor by how many cell positions. No established formal
+ * standards exist at present on which Unicode character shall occupy
+ * how many cell positions on character terminals. These routines are
+ * a first attempt of defining such behavior based on simple rules
+ * applied to data provided by the Unicode Consortium.
+ *
+ * For some graphical characters, the Unicode standard explicitly
+ * defines a character-cell width via the definition of the East Asian
+ * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes.
+ * In all these cases, there is no ambiguity about which width a
+ * terminal shall use. For characters in the East Asian Ambiguous (A)
+ * class, the width choice depends purely on a preference of backward
+ * compatibility with either historic CJK or Western practice.
+ * Choosing single-width for these characters is easy to justify as
+ * the appropriate long-term solution, as the CJK practice of
+ * displaying these characters as double-width comes from historic
+ * implementation simplicity (8-bit encoded characters were displayed
+ * single-width and 16-bit ones double-width, even for Greek,
+ * Cyrillic, etc.) and not any typographic considerations.
+ *
+ * Much less clear is the choice of width for the Not East Asian
+ * (Neutral) class. Existing practice does not dictate a width for any
+ * of these characters. It would nevertheless make sense
+ * typographically to allocate two character cells to characters such
+ * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be
+ * represented adequately with a single-width glyph. The following
+ * routines at present merely assign a single-cell width to all
+ * neutral characters, in the interest of simplicity. This is not
+ * entirely satisfactory and should be reconsidered before
+ * establishing a formal standard in this area. At the moment, the
+ * decision which Not East Asian (Neutral) characters should be
+ * represented by double-width glyphs cannot yet be answered by
+ * applying a simple rule from the Unicode database content. Setting
+ * up a proper standard for the behavior of UTF-8 character terminals
+ * will require a careful analysis not only of each Unicode character,
+ * but also of each presentation form, something the author of these
+ * routines has avoided to do so far.
+ *
+ * http://www.unicode.org/unicode/reports/tr11/
+ *
+ * Markus Kuhn -- 2007-05-26 (Unicode 5.0)
+ *
+ * Permission to use, copy, modify, and distribute this software
+ * for any purpose and without fee is hereby granted. The author
+ * disclaims all warranties with regard to this software.
+ *
+ * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
+ */
+
+struct interval {
+ int first;
+ int last;
+};
+
+/* sorted list of non-overlapping intervals of non-spacing characters */
+/* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */
+static const struct interval combining[] = {
+ { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 },
+ { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 },
+ { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 },
+ { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 },
+ { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED },
+ { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A },
+ { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 },
+ { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D },
+ { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 },
+ { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD },
+ { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C },
+ { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D },
+ { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC },
+ { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD },
+ { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C },
+ { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D },
+ { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 },
+ { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 },
+ { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC },
+ { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD },
+ { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D },
+ { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 },
+ { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E },
+ { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC },
+ { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 },
+ { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E },
+ { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 },
+ { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 },
+ { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 },
+ { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F },
+ { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 },
+ { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD },
+ { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD },
+ { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 },
+ { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B },
+ { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 },
+ { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 },
+ { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF },
+ { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 },
+ { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F },
+ { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B },
+ { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F },
+ { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB },
+ { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F },
+ { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 },
+ { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD },
+ { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F },
+ { 0xE0100, 0xE01EF }
+};
+
+
+/* auxiliary function for binary search in interval table */
+static int bisearch(uint32_t ucs, const struct interval *table, int max) {
+ int min = 0;
+ int mid;
+
+ if (ucs < table[0].first || ucs > table[max].last)
+ return 0;
+ while (max >= min) {
+ mid = (min + max) / 2;
+ if (ucs > table[mid].last)
+ min = mid + 1;
+ else if (ucs < table[mid].first)
+ max = mid - 1;
+ else
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/* The following two functions define the column width of an ISO 10646
+ * character as follows:
+ *
+ * - The null character (U+0000) has a column width of 0.
+ *
+ * - Other C0/C1 control characters and DEL will lead to a return
+ * value of -1.
+ *
+ * - Non-spacing and enclosing combining characters (general
+ * category code Mn or Me in the Unicode database) have a
+ * column width of 0.
+ *
+ * - SOFT HYPHEN (U+00AD) has a column width of 1.
+ *
+ * - Other format characters (general category code Cf in the Unicode
+ * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0.
+ *
+ * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF)
+ * have a column width of 0.
+ *
+ * - Spacing characters in the East Asian Wide (W) or East Asian
+ * Full-width (F) category as defined in Unicode Technical
+ * Report #11 have a column width of 2.
+ *
+ * - All remaining characters (including all printable
+ * ISO 8859-1 and WGL4 characters, Unicode control characters,
+ * etc.) have a column width of 1.
+ *
+ * This implementation assumes that uint32_t characters are encoded
+ * in ISO 10646.
+ */
+
+
+static int mk_wcwidth(uint32_t ucs)
+{
+ /* test for 8-bit control characters */
+ if (ucs == 0)
+ return 0;
+ if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0))
+ return -1;
+
+ /* binary search in table of non-spacing characters */
+ if (bisearch(ucs, combining,
+ sizeof(combining) / sizeof(struct interval) - 1))
+ return 0;
+
+ /* if we arrive here, ucs is not a combining or C0/C1 control character */
+
+ return 1 +
+ (ucs >= 0x1100 &&
+ (ucs <= 0x115f || /* Hangul Jamo init. consonants */
+ ucs == 0x2329 || ucs == 0x232a ||
+ (ucs >= 0x2e80 && ucs <= 0xa4cf &&
+ ucs != 0x303f) || /* CJK ... Yi */
+ (ucs >= 0xac00 && ucs <= 0xd7a3) || /* Hangul Syllables */
+ (ucs >= 0xf900 && ucs <= 0xfaff) || /* CJK Compatibility Ideographs */
+ (ucs >= 0xfe10 && ucs <= 0xfe19) || /* Vertical forms */
+ (ucs >= 0xfe30 && ucs <= 0xfe6f) || /* CJK Compatibility Forms */
+ (ucs >= 0xff00 && ucs <= 0xff60) || /* Fullwidth Forms */
+ (ucs >= 0xffe0 && ucs <= 0xffe6) ||
+ (ucs >= 0x20000 && ucs <= 0x2fffd) ||
+ (ucs >= 0x30000 && ucs <= 0x3fffd)));
+}
+
+
+#ifdef USE_MK_WCWIDTH_CJK
+
+/*
+ * The following functions are the same as mk_wcwidth() and
+ * mk_wcswidth(), except that spacing characters in the East Asian
+ * Ambiguous (A) category as defined in Unicode Technical Report #11
+ * have a column width of 2. This variant might be useful for users of
+ * CJK legacy encodings who want to migrate to UCS without changing
+ * the traditional terminal character-width behaviour. It is not
+ * otherwise recommended for general use.
+ */
+static int mk_wcwidth_cjk(uint32_t ucs)
+{
+ /* sorted list of non-overlapping intervals of East Asian Ambiguous
+ * characters, generated by "uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf c" */
+ static const struct interval ambiguous[] = {
+ { 0x00A1, 0x00A1 }, { 0x00A4, 0x00A4 }, { 0x00A7, 0x00A8 },
+ { 0x00AA, 0x00AA }, { 0x00AE, 0x00AE }, { 0x00B0, 0x00B4 },
+ { 0x00B6, 0x00BA }, { 0x00BC, 0x00BF }, { 0x00C6, 0x00C6 },
+ { 0x00D0, 0x00D0 }, { 0x00D7, 0x00D8 }, { 0x00DE, 0x00E1 },
+ { 0x00E6, 0x00E6 }, { 0x00E8, 0x00EA }, { 0x00EC, 0x00ED },
+ { 0x00F0, 0x00F0 }, { 0x00F2, 0x00F3 }, { 0x00F7, 0x00FA },
+ { 0x00FC, 0x00FC }, { 0x00FE, 0x00FE }, { 0x0101, 0x0101 },
+ { 0x0111, 0x0111 }, { 0x0113, 0x0113 }, { 0x011B, 0x011B },
+ { 0x0126, 0x0127 }, { 0x012B, 0x012B }, { 0x0131, 0x0133 },
+ { 0x0138, 0x0138 }, { 0x013F, 0x0142 }, { 0x0144, 0x0144 },
+ { 0x0148, 0x014B }, { 0x014D, 0x014D }, { 0x0152, 0x0153 },
+ { 0x0166, 0x0167 }, { 0x016B, 0x016B }, { 0x01CE, 0x01CE },
+ { 0x01D0, 0x01D0 }, { 0x01D2, 0x01D2 }, { 0x01D4, 0x01D4 },
+ { 0x01D6, 0x01D6 }, { 0x01D8, 0x01D8 }, { 0x01DA, 0x01DA },
+ { 0x01DC, 0x01DC }, { 0x0251, 0x0251 }, { 0x0261, 0x0261 },
+ { 0x02C4, 0x02C4 }, { 0x02C7, 0x02C7 }, { 0x02C9, 0x02CB },
+ { 0x02CD, 0x02CD }, { 0x02D0, 0x02D0 }, { 0x02D8, 0x02DB },
+ { 0x02DD, 0x02DD }, { 0x02DF, 0x02DF }, { 0x0391, 0x03A1 },
+ { 0x03A3, 0x03A9 }, { 0x03B1, 0x03C1 }, { 0x03C3, 0x03C9 },
+ { 0x0401, 0x0401 }, { 0x0410, 0x044F }, { 0x0451, 0x0451 },
+ { 0x2010, 0x2010 }, { 0x2013, 0x2016 }, { 0x2018, 0x2019 },
+ { 0x201C, 0x201D }, { 0x2020, 0x2022 }, { 0x2024, 0x2027 },
+ { 0x2030, 0x2030 }, { 0x2032, 0x2033 }, { 0x2035, 0x2035 },
+ { 0x203B, 0x203B }, { 0x203E, 0x203E }, { 0x2074, 0x2074 },
+ { 0x207F, 0x207F }, { 0x2081, 0x2084 }, { 0x20AC, 0x20AC },
+ { 0x2103, 0x2103 }, { 0x2105, 0x2105 }, { 0x2109, 0x2109 },
+ { 0x2113, 0x2113 }, { 0x2116, 0x2116 }, { 0x2121, 0x2122 },
+ { 0x2126, 0x2126 }, { 0x212B, 0x212B }, { 0x2153, 0x2154 },
+ { 0x215B, 0x215E }, { 0x2160, 0x216B }, { 0x2170, 0x2179 },
+ { 0x2190, 0x2199 }, { 0x21B8, 0x21B9 }, { 0x21D2, 0x21D2 },
+ { 0x21D4, 0x21D4 }, { 0x21E7, 0x21E7 }, { 0x2200, 0x2200 },
+ { 0x2202, 0x2203 }, { 0x2207, 0x2208 }, { 0x220B, 0x220B },
+ { 0x220F, 0x220F }, { 0x2211, 0x2211 }, { 0x2215, 0x2215 },
+ { 0x221A, 0x221A }, { 0x221D, 0x2220 }, { 0x2223, 0x2223 },
+ { 0x2225, 0x2225 }, { 0x2227, 0x222C }, { 0x222E, 0x222E },
+ { 0x2234, 0x2237 }, { 0x223C, 0x223D }, { 0x2248, 0x2248 },
+ { 0x224C, 0x224C }, { 0x2252, 0x2252 }, { 0x2260, 0x2261 },
+ { 0x2264, 0x2267 }, { 0x226A, 0x226B }, { 0x226E, 0x226F },
+ { 0x2282, 0x2283 }, { 0x2286, 0x2287 }, { 0x2295, 0x2295 },
+ { 0x2299, 0x2299 }, { 0x22A5, 0x22A5 }, { 0x22BF, 0x22BF },
+ { 0x2312, 0x2312 }, { 0x2460, 0x24E9 }, { 0x24EB, 0x254B },
+ { 0x2550, 0x2573 }, { 0x2580, 0x258F }, { 0x2592, 0x2595 },
+ { 0x25A0, 0x25A1 }, { 0x25A3, 0x25A9 }, { 0x25B2, 0x25B3 },
+ { 0x25B6, 0x25B7 }, { 0x25BC, 0x25BD }, { 0x25C0, 0x25C1 },
+ { 0x25C6, 0x25C8 }, { 0x25CB, 0x25CB }, { 0x25CE, 0x25D1 },
+ { 0x25E2, 0x25E5 }, { 0x25EF, 0x25EF }, { 0x2605, 0x2606 },
+ { 0x2609, 0x2609 }, { 0x260E, 0x260F }, { 0x2614, 0x2615 },
+ { 0x261C, 0x261C }, { 0x261E, 0x261E }, { 0x2640, 0x2640 },
+ { 0x2642, 0x2642 }, { 0x2660, 0x2661 }, { 0x2663, 0x2665 },
+ { 0x2667, 0x266A }, { 0x266C, 0x266D }, { 0x266F, 0x266F },
+ { 0x273D, 0x273D }, { 0x2776, 0x277F }, { 0xE000, 0xF8FF },
+ { 0xFFFD, 0xFFFD }, { 0xF0000, 0xFFFFD }, { 0x100000, 0x10FFFD }
+ };
+
+ /* binary search in table of non-spacing characters */
+ if (bisearch(ucs, ambiguous,
+ sizeof(ambiguous) / sizeof(struct interval) - 1))
+ return 2;
+
+ return mk_wcwidth(ucs);
+}
+
+#endif
+
+// ################################
+// ### The rest added by Paul Evans
+
+static const struct interval fullwidth[] = {
+#include "fullwidth.inc"
+};
+
+INTERNAL int vterm_unicode_width(uint32_t codepoint)
+{
+ if(bisearch(codepoint, fullwidth, sizeof(fullwidth) / sizeof(fullwidth[0]) - 1))
+ return 2;
+
+ return mk_wcwidth(codepoint);
+}
+
+INTERNAL int vterm_unicode_is_combining(uint32_t codepoint)
+{
+ return bisearch(codepoint, combining, sizeof(combining) / sizeof(struct interval) - 1);
+}
diff --git a/src/libs/3rdparty/libvterm/src/utf8.h b/src/libs/3rdparty/libvterm/src/utf8.h
new file mode 100644
index 00000000000..9a336d357ff
--- /dev/null
+++ b/src/libs/3rdparty/libvterm/src/utf8.h
@@ -0,0 +1,39 @@
+/* The following functions copied and adapted from libtermkey
+ *
+ * http://www.leonerd.org.uk/code/libtermkey/
+ */
+static inline unsigned int utf8_seqlen(long codepoint)
+{
+ if(codepoint < 0x0000080) return 1;
+ if(codepoint < 0x0000800) return 2;
+ if(codepoint < 0x0010000) return 3;
+ if(codepoint < 0x0200000) return 4;
+ if(codepoint < 0x4000000) return 5;
+ return 6;
+}
+
+/* Does NOT NUL-terminate the buffer */
+static int fill_utf8(long codepoint, char *str)
+{
+ int nbytes = utf8_seqlen(codepoint);
+
+ // This is easier done backwards
+ int b = nbytes;
+ while(b > 1) {
+ b--;
+ str[b] = 0x80 | (codepoint & 0x3f);
+ codepoint >>= 6;
+ }
+
+ switch(nbytes) {
+ case 1: str[0] = (codepoint & 0x7f); break;
+ case 2: str[0] = 0xc0 | (codepoint & 0x1f); break;
+ case 3: str[0] = 0xe0 | (codepoint & 0x0f); break;
+ case 4: str[0] = 0xf0 | (codepoint & 0x07); break;
+ case 5: str[0] = 0xf8 | (codepoint & 0x03); break;
+ case 6: str[0] = 0xfc | (codepoint & 0x01); break;
+ }
+
+ return nbytes;
+}
+/* end copy */
diff --git a/src/libs/3rdparty/libvterm/src/vterm.c b/src/libs/3rdparty/libvterm/src/vterm.c
new file mode 100644
index 00000000000..0997887f5fb
--- /dev/null
+++ b/src/libs/3rdparty/libvterm/src/vterm.c
@@ -0,0 +1,429 @@
+#include "vterm_internal.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+
+/*****************
+ * API functions *
+ *****************/
+
+static void *default_malloc(size_t size, void *allocdata)
+{
+ void *ptr = malloc(size);
+ if(ptr)
+ memset(ptr, 0, size);
+ return ptr;
+}
+
+static void default_free(void *ptr, void *allocdata)
+{
+ free(ptr);
+}
+
+static VTermAllocatorFunctions default_allocator = {
+ .malloc = &default_malloc,
+ .free = &default_free,
+};
+
+VTerm *vterm_new(int rows, int cols)
+{
+ return vterm_build(&(const struct VTermBuilder){
+ .rows = rows,
+ .cols = cols,
+ });
+}
+
+VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata)
+{
+ return vterm_build(&(const struct VTermBuilder){
+ .rows = rows,
+ .cols = cols,
+ .allocator = funcs,
+ .allocdata = allocdata,
+ });
+}
+
+/* A handy macro for defaulting values out of builder fields */
+#define DEFAULT(v, def) ((v) ? (v) : (def))
+
+VTerm *vterm_build(const struct VTermBuilder *builder)
+{
+ const VTermAllocatorFunctions *allocator = DEFAULT(builder->allocator, &default_allocator);
+
+ /* Need to bootstrap using the allocator function directly */
+ VTerm *vt = (*allocator->malloc)(sizeof(VTerm), builder->allocdata);
+
+ vt->allocator = allocator;
+ vt->allocdata = builder->allocdata;
+
+ vt->rows = builder->rows;
+ vt->cols = builder->cols;
+
+ vt->parser.state = NORMAL;
+
+ vt->parser.callbacks = NULL;
+ vt->parser.cbdata = NULL;
+
+ vt->parser.emit_nul = false;
+
+ vt->outfunc = NULL;
+ vt->outdata = NULL;
+
+ vt->outbuffer_len = DEFAULT(builder->outbuffer_len, 4096);
+ vt->outbuffer_cur = 0;
+ vt->outbuffer = vterm_allocator_malloc(vt, vt->outbuffer_len);
+
+ vt->tmpbuffer_len = DEFAULT(builder->tmpbuffer_len, 4096);
+ vt->tmpbuffer = vterm_allocator_malloc(vt, vt->tmpbuffer_len);
+
+ return vt;
+}
+
+void vterm_free(VTerm *vt)
+{
+ if(vt->screen)
+ vterm_screen_free(vt->screen);
+
+ if(vt->state)
+ vterm_state_free(vt->state);
+
+ vterm_allocator_free(vt, vt->outbuffer);
+ vterm_allocator_free(vt, vt->tmpbuffer);
+
+ vterm_allocator_free(vt, vt);
+}
+
+INTERNAL void *vterm_allocator_malloc(VTerm *vt, size_t size)
+{
+ return (*vt->allocator->malloc)(size, vt->allocdata);
+}
+
+INTERNAL void vterm_allocator_free(VTerm *vt, void *ptr)
+{
+ (*vt->allocator->free)(ptr, vt->allocdata);
+}
+
+void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp)
+{
+ if(rowsp)
+ *rowsp = vt->rows;
+ if(colsp)
+ *colsp = vt->cols;
+}
+
+void vterm_set_size(VTerm *vt, int rows, int cols)
+{
+ if(rows < 1 || cols < 1)
+ return;
+
+ vt->rows = rows;
+ vt->cols = cols;
+
+ if(vt->parser.callbacks && vt->parser.callbacks->resize)
+ (*vt->parser.callbacks->resize)(rows, cols, vt->parser.cbdata);
+}
+
+int vterm_get_utf8(const VTerm *vt)
+{
+ return vt->mode.utf8;
+}
+
+void vterm_set_utf8(VTerm *vt, int is_utf8)
+{
+ vt->mode.utf8 = is_utf8;
+}
+
+void vterm_output_set_callback(VTerm *vt, VTermOutputCallback *func, void *user)
+{
+ vt->outfunc = func;
+ vt->outdata = user;
+}
+
+INTERNAL void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len)
+{
+ if(vt->outfunc) {
+ (vt->outfunc)(bytes, len, vt->outdata);
+ return;
+ }
+
+ if(len > vt->outbuffer_len - vt->outbuffer_cur)
+ return;
+
+ memcpy(vt->outbuffer + vt->outbuffer_cur, bytes, len);
+ vt->outbuffer_cur += len;
+}
+
+INTERNAL void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args)
+{
+ size_t len = vsnprintf(vt->tmpbuffer, vt->tmpbuffer_len,
+ format, args);
+
+ vterm_push_output_bytes(vt, vt->tmpbuffer, len);
+}
+
+INTERNAL void vterm_push_output_sprintf(VTerm *vt, const char *format, ...)
+{
+ va_list args;
+ va_start(args, format);
+ vterm_push_output_vsprintf(vt, format, args);
+ va_end(args);
+}
+
+INTERNAL void vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ctrl, const char *fmt, ...)
+{
+ size_t cur;
+
+ if(ctrl >= 0x80 && !vt->mode.ctrl8bit)
+ cur = snprintf(vt->tmpbuffer, vt->tmpbuffer_len,
+ ESC_S "%c", ctrl - 0x40);
+ else
+ cur = snprintf(vt->tmpbuffer, vt->tmpbuffer_len,
+ "%c", ctrl);
+
+ if(cur >= vt->tmpbuffer_len)
+ return;
+
+ va_list args;
+ va_start(args, fmt);
+ cur += vsnprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,
+ fmt, args);
+ va_end(args);
+
+ if(cur >= vt->tmpbuffer_len)
+ return;
+
+ vterm_push_output_bytes(vt, vt->tmpbuffer, cur);
+}
+
+INTERNAL void vterm_push_output_sprintf_str(VTerm *vt, unsigned char ctrl, bool term, const char *fmt, ...)
+{
+ size_t cur = 0;
+
+ if(ctrl) {
+ if(ctrl >= 0x80 && !vt->mode.ctrl8bit)
+ cur = snprintf(vt->tmpbuffer, vt->tmpbuffer_len,
+ ESC_S "%c", ctrl - 0x40);
+ else
+ cur = snprintf(vt->tmpbuffer, vt->tmpbuffer_len,
+ "%c", ctrl);
+
+ if(cur >= vt->tmpbuffer_len)
+ return;
+ }
+
+ va_list args;
+ va_start(args, fmt);
+ cur += vsnprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,
+ fmt, args);
+ va_end(args);
+
+ if(cur >= vt->tmpbuffer_len)
+ return;
+
+ if(term) {
+ cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur,
+ vt->mode.ctrl8bit ? "\x9C" : ESC_S "\\"); // ST
+
+ if(cur >= vt->tmpbuffer_len)
+ return;
+ }
+
+ vterm_push_output_bytes(vt, vt->tmpbuffer, cur);
+}
+
+size_t vterm_output_get_buffer_size(const VTerm *vt)
+{
+ return vt->outbuffer_len;
+}
+
+size_t vterm_output_get_buffer_current(const VTerm *vt)
+{
+ return vt->outbuffer_cur;
+}
+
+size_t vterm_output_get_buffer_remaining(const VTerm *vt)
+{
+ return vt->outbuffer_len - vt->outbuffer_cur;
+}
+
+size_t vterm_output_read(VTerm *vt, char *buffer, size_t len)
+{
+ if(len > vt->outbuffer_cur)
+ len = vt->outbuffer_cur;
+
+ memcpy(buffer, vt->outbuffer, len);
+
+ if(len < vt->outbuffer_cur)
+ memmove(vt->outbuffer, vt->outbuffer + len, vt->outbuffer_cur - len);
+
+ vt->outbuffer_cur -= len;
+
+ return len;
+}
+
+VTermValueType vterm_get_attr_type(VTermAttr attr)
+{
+ switch(attr) {
+ case VTERM_ATTR_BOLD: return VTERM_VALUETYPE_BOOL;
+ case VTERM_ATTR_UNDERLINE: return VTERM_VALUETYPE_INT;
+ case VTERM_ATTR_ITALIC: return VTERM_VALUETYPE_BOOL;
+ case VTERM_ATTR_BLINK: return VTERM_VALUETYPE_BOOL;
+ case VTERM_ATTR_REVERSE: return VTERM_VALUETYPE_BOOL;
+ case VTERM_ATTR_CONCEAL: return VTERM_VALUETYPE_BOOL;
+ case VTERM_ATTR_STRIKE: return VTERM_VALUETYPE_BOOL;
+ case VTERM_ATTR_FONT: return VTERM_VALUETYPE_INT;
+ case VTERM_ATTR_FOREGROUND: return VTERM_VALUETYPE_COLOR;
+ case VTERM_ATTR_BACKGROUND: return VTERM_VALUETYPE_COLOR;
+ case VTERM_ATTR_SMALL: return VTERM_VALUETYPE_BOOL;
+ case VTERM_ATTR_BASELINE: return VTERM_VALUETYPE_INT;
+
+ case VTERM_N_ATTRS: return 0;
+ }
+ return 0; /* UNREACHABLE */
+}
+
+VTermValueType vterm_get_prop_type(VTermProp prop)
+{
+ switch(prop) {
+ case VTERM_PROP_CURSORVISIBLE: return VTERM_VALUETYPE_BOOL;
+ case VTERM_PROP_CURSORBLINK: return VTERM_VALUETYPE_BOOL;
+ case VTERM_PROP_ALTSCREEN: return VTERM_VALUETYPE_BOOL;
+ case VTERM_PROP_TITLE: return VTERM_VALUETYPE_STRING;
+ case VTERM_PROP_ICONNAME: return VTERM_VALUETYPE_STRING;
+ case VTERM_PROP_REVERSE: return VTERM_VALUETYPE_BOOL;
+ case VTERM_PROP_CURSORSHAPE: return VTERM_VALUETYPE_INT;
+ case VTERM_PROP_MOUSE: return VTERM_VALUETYPE_INT;
+
+ case VTERM_N_PROPS: return 0;
+ }
+ return 0; /* UNREACHABLE */
+}
+
+void vterm_scroll_rect(VTermRect rect,
+ int downward,
+ int rightward,
+ int (*moverect)(VTermRect src, VTermRect dest, void *user),
+ int (*eraserect)(VTermRect rect, int selective, void *user),
+ void *user)
+{
+ VTermRect src;
+ VTermRect dest;
+
+ if(abs(downward) >= rect.end_row - rect.start_row ||
+ abs(rightward) >= rect.end_col - rect.start_col) {
+ /* Scroll more than area; just erase the lot */
+ (*eraserect)(rect, 0, user);
+ return;
+ }
+
+ if(rightward >= 0) {
+ /* rect: [XXX................]
+ * src: [----------------]
+ * dest: [----------------]
+ */
+ dest.start_col = rect.start_col;
+ dest.end_col = rect.end_col - rightward;
+ src.start_col = rect.start_col + rightward;
+ src.end_col = rect.end_col;
+ }
+ else {
+ /* rect: [................XXX]
+ * src: [----------------]
+ * dest: [----------------]
+ */
+ int leftward = -rightward;
+ dest.start_col = rect.start_col + leftward;
+ dest.end_col = rect.end_col;
+ src.start_col = rect.start_col;
+ src.end_col = rect.end_col - leftward;
+ }
+
+ if(downward >= 0) {
+ dest.start_row = rect.start_row;
+ dest.end_row = rect.end_row - downward;
+ src.start_row = rect.start_row + downward;
+ src.end_row = rect.end_row;
+ }
+ else {
+ int upward = -downward;
+ dest.start_row = rect.start_row + upward;
+ dest.end_row = rect.end_row;
+ src.start_row = rect.start_row;
+ src.end_row = rect.end_row - upward;
+ }
+
+ if(moverect)
+ (*moverect)(dest, src, user);
+
+ if(downward > 0)
+ rect.start_row = rect.end_row - downward;
+ else if(downward < 0)
+ rect.end_row = rect.start_row - downward;
+
+ if(rightward > 0)
+ rect.start_col = rect.end_col - rightward;
+ else if(rightward < 0)
+ rect.end_col = rect.start_col - rightward;
+
+ (*eraserect)(rect, 0, user);
+}
+
+void vterm_copy_cells(VTermRect dest,
+ VTermRect src,
+ void (*copycell)(VTermPos dest, VTermPos src, void *user),
+ void *user)
+{
+ int downward = src.start_row - dest.start_row;
+ int rightward = src.start_col - dest.start_col;
+
+ int init_row, test_row, init_col, test_col;
+ int inc_row, inc_col;
+
+ if(downward < 0) {
+ init_row = dest.end_row - 1;
+ test_row = dest.start_row - 1;
+ inc_row = -1;
+ }
+ else /* downward >= 0 */ {
+ init_row = dest.start_row;
+ test_row = dest.end_row;
+ inc_row = +1;
+ }
+
+ if(rightward < 0) {
+ init_col = dest.end_col - 1;
+ test_col = dest.start_col - 1;
+ inc_col = -1;
+ }
+ else /* rightward >= 0 */ {
+ init_col = dest.start_col;
+ test_col = dest.end_col;
+ inc_col = +1;
+ }
+
+ VTermPos pos;
+ for(pos.row = init_row; pos.row != test_row; pos.row += inc_row)
+ for(pos.col = init_col; pos.col != test_col; pos.col += inc_col) {
+ VTermPos srcpos = { pos.row + downward, pos.col + rightward };
+ (*copycell)(pos, srcpos, user);
+ }
+}
+
+void vterm_check_version(int major, int minor)
+{
+ if(major != VTERM_VERSION_MAJOR) {
+ fprintf(stderr, "libvterm major version mismatch; %d (wants) != %d (library)\n",
+ major, VTERM_VERSION_MAJOR);
+ exit(1);
+ }
+
+ if(minor > VTERM_VERSION_MINOR) {
+ fprintf(stderr, "libvterm minor version mismatch; %d (wants) > %d (library)\n",
+ minor, VTERM_VERSION_MINOR);
+ exit(1);
+ }
+
+ // Happy
+}
diff --git a/src/libs/3rdparty/libvterm/src/vterm_internal.h b/src/libs/3rdparty/libvterm/src/vterm_internal.h
new file mode 100644
index 00000000000..ad61bff8b0f
--- /dev/null
+++ b/src/libs/3rdparty/libvterm/src/vterm_internal.h
@@ -0,0 +1,296 @@
+#ifndef __VTERM_INTERNAL_H__
+#define __VTERM_INTERNAL_H__
+
+#include "vterm.h"
+
+#include <stdarg.h>
+
+#if defined(__GNUC__)
+# define INTERNAL __attribute__((visibility("internal")))
+#else
+# define INTERNAL
+#endif
+
+#ifdef DEBUG
+# define DEBUG_LOG(...) fprintf(stderr, __VA_ARGS__)
+#else
+# define DEBUG_LOG(...)
+#endif
+
+#define ESC_S "\x1b"
+
+#define INTERMED_MAX 16
+
+#define CSI_ARGS_MAX 16
+#define CSI_LEADER_MAX 16
+
+#define BUFIDX_PRIMARY 0
+#define BUFIDX_ALTSCREEN 1
+
+typedef struct VTermEncoding VTermEncoding;
+
+typedef struct {
+ VTermEncoding *enc;
+
+ // This size should be increased if required by other stateful encodings
+ char data[4*sizeof(uint32_t)];
+} VTermEncodingInstance;
+
+struct VTermPen
+{
+ VTermColor fg;
+ VTermColor bg;
+ unsigned int bold:1;
+ unsigned int underline:3;
+ unsigned int italic:1;
+ unsigned int blink:1;
+ unsigned int reverse:1;
+ unsigned int conceal:1;
+ unsigned int strike:1;
+ unsigned int font:4; /* To store 0-9 */
+ unsigned int small:1;
+ unsigned int baseline:2;
+};
+
+struct VTermState
+{
+ VTerm *vt;
+
+ const VTermStateCallbacks *callbacks;
+ void *cbdata;
+
+ const VTermStateFallbacks *fallbacks;
+ void *fbdata;
+
+ int rows;
+ int cols;
+
+ /* Current cursor position */
+ VTermPos pos;
+
+ int at_phantom; /* True if we're on the "81st" phantom column to defer a wraparound */
+
+ int scrollregion_top;
+ int scrollregion_bottom; /* -1 means unbounded */
+#define SCROLLREGION_BOTTOM(state) ((state)->scrollregion_bottom > -1 ? (state)->scrollregion_bottom : (state)->rows)
+ int scrollregion_left;
+#define SCROLLREGION_LEFT(state) ((state)->mode.leftrightmargin ? (state)->scrollregion_left : 0)
+ int scrollregion_right; /* -1 means unbounded */
+#define SCROLLREGION_RIGHT(state) ((state)->mode.leftrightmargin && (state)->scrollregion_right > -1 ? (state)->scrollregion_right : (state)->cols)
+
+ /* Bitvector of tab stops */
+ unsigned char *tabstops;
+
+ /* Primary and Altscreen; lineinfos[1] is lazily allocated as needed */
+ VTermLineInfo *lineinfos[2];
+
+ /* lineinfo will == lineinfos[0] or lineinfos[1], depending on altscreen */
+ VTermLineInfo *lineinfo;
+#define ROWWIDTH(state,row) ((state)->lineinfo[(row)].doublewidth ? ((state)->cols / 2) : (state)->cols)
+#define THISROWWIDTH(state) ROWWIDTH(state, (state)->pos.row)
+
+ /* Mouse state */
+ int mouse_col, mouse_row;
+ int mouse_buttons;
+ int mouse_flags;
+#define MOUSE_WANT_CLICK 0x01
+#define MOUSE_WANT_DRAG 0x02
+#define MOUSE_WANT_MOVE 0x04
+
+ enum { MOUSE_X10, MOUSE_UTF8, MOUSE_SGR, MOUSE_RXVT } mouse_protocol;
+
+ /* Last glyph output, for Unicode recombining purposes */
+ uint32_t *combine_chars;
+ size_t combine_chars_size; // Number of ELEMENTS in the above
+ int combine_width; // The width of the glyph above
+ VTermPos combine_pos; // Position before movement
+
+ struct {
+ unsigned int keypad:1;
+ unsigned int cursor:1;
+ unsigned int autowrap:1;
+ unsigned int insert:1;
+ unsigned int newline:1;
+ unsigned int cursor_visible:1;
+ unsigned int cursor_blink:1;
+ unsigned int cursor_shape:2;
+ unsigned int alt_screen:1;
+ unsigned int origin:1;
+ unsigned int screen:1;
+ unsigned int leftrightmargin:1;
+ unsigned int bracketpaste:1;
+ unsigned int report_focus:1;
+ } mode;
+
+ VTermEncodingInstance encoding[4], encoding_utf8;
+ int gl_set, gr_set, gsingle_set;
+
+ struct VTermPen pen;
+
+ VTermColor default_fg;
+ VTermColor default_bg;
+ VTermColor colors[16]; // Store the 8 ANSI and the 8 ANSI high-brights only
+
+ int bold_is_highbright;
+
+ unsigned int protected_cell : 1;
+
+ /* Saved state under DEC mode 1048/1049 */
+ struct {
+ VTermPos pos;
+ struct VTermPen pen;
+
+ struct {
+ unsigned int cursor_visible:1;
+ unsigned int cursor_blink:1;
+ unsigned int cursor_shape:2;
+ } mode;
+ } saved;
+
+ /* Temporary state for DECRQSS parsing */
+ union {
+ char decrqss[4];
+ struct {
+ uint16_t mask;
+ enum {
+ SELECTION_INITIAL,
+ SELECTION_SELECTED,
+ SELECTION_QUERY,
+ SELECTION_SET_INITIAL,
+ SELECTION_SET,
+ } state : 8;
+ uint32_t recvpartial;
+ uint32_t sendpartial;
+ } selection;
+ } tmp;
+
+ struct {
+ const VTermSelectionCallbacks *callbacks;
+ void *user;
+ char *buffer;
+ size_t buflen;
+ } selection;
+};
+
+struct VTerm
+{
+ const VTermAllocatorFunctions *allocator;
+ void *allocdata;
+
+ int rows;
+ int cols;
+
+ struct {
+ unsigned int utf8:1;
+ unsigned int ctrl8bit:1;
+ } mode;
+
+ struct {
+ enum VTermParserState {
+ NORMAL,
+ CSI_LEADER,
+ CSI_ARGS,
+ CSI_INTERMED,
+ DCS_COMMAND,
+ /* below here are the "string states" */
+ OSC_COMMAND,
+ OSC,
+ DCS,
+ APC,
+ PM,
+ SOS,
+ } state;
+
+ bool in_esc : 1;
+
+ int intermedlen;
+ char intermed[INTERMED_MAX];
+
+ union {
+ struct {
+ int leaderlen;
+ char leader[CSI_LEADER_MAX];
+
+ int argi;
+ long args[CSI_ARGS_MAX];
+ } csi;
+ struct {
+ int command;
+ } osc;
+ struct {
+ int commandlen;
+ char command[CSI_LEADER_MAX];
+ } dcs;
+ } v;
+
+ const VTermParserCallbacks *callbacks;
+ void *cbdata;
+
+ bool string_initial;
+
+ bool emit_nul;
+ } parser;
+
+ /* len == malloc()ed size; cur == number of valid bytes */
+
+ VTermOutputCallback *outfunc;
+ void *outdata;
+
+ char *outbuffer;
+ size_t outbuffer_len;
+ size_t outbuffer_cur;
+
+ char *tmpbuffer;
+ size_t tmpbuffer_len;
+
+ VTermState *state;
+ VTermScreen *screen;
+};
+
+struct VTermEncoding {
+ void (*init) (VTermEncoding *enc, void *data);
+ void (*decode)(VTermEncoding *enc, void *data,
+ uint32_t cp[], int *cpi, int cplen,
+ const char bytes[], size_t *pos, size_t len);
+};
+
+typedef enum {
+ ENC_UTF8,
+ ENC_SINGLE_94
+} VTermEncodingType;
+
+void *vterm_allocator_malloc(VTerm *vt, size_t size);
+void vterm_allocator_free(VTerm *vt, void *ptr);
+
+void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len);
+void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args);
+void vterm_push_output_sprintf(VTerm *vt, const char *format, ...);
+void vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ctrl, const char *fmt, ...);
+void vterm_push_output_sprintf_str(VTerm *vt, unsigned char ctrl, bool term, const char *fmt, ...);
+
+void vterm_state_free(VTermState *state);
+
+void vterm_state_newpen(VTermState *state);
+void vterm_state_resetpen(VTermState *state);
+void vterm_state_setpen(VTermState *state, const long args[], int argcount);
+int vterm_state_getpen(VTermState *state, long args[], int argcount);
+void vterm_state_savepen(VTermState *state, int save);
+
+enum {
+ C1_SS3 = 0x8f,
+ C1_DCS = 0x90,
+ C1_CSI = 0x9b,
+ C1_ST = 0x9c,
+ C1_OSC = 0x9d,
+};
+
+void vterm_state_push_output_sprintf_CSI(VTermState *vts, const char *format, ...);
+
+void vterm_screen_free(VTermScreen *screen);
+
+VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation);
+
+int vterm_unicode_width(uint32_t codepoint);
+int vterm_unicode_is_combining(uint32_t codepoint);
+
+#endif
diff --git a/src/libs/3rdparty/libvterm/vterm.pc.in b/src/libs/3rdparty/libvterm/vterm.pc.in
new file mode 100644
index 00000000000..681a270d513
--- /dev/null
+++ b/src/libs/3rdparty/libvterm/vterm.pc.in
@@ -0,0 +1,8 @@
+libdir=@LIBDIR@
+includedir=@INCDIR@
+
+Name: vterm
+Description: Abstract VT220/Xterm/ECMA-48 emulation library
+Version: 0.3.1
+Libs: -L${libdir} -lvterm
+Cflags: -I${includedir}
diff --git a/src/libs/3rdparty/libvterm/vterm.qbs b/src/libs/3rdparty/libvterm/vterm.qbs
new file mode 100644
index 00000000000..18ccb638aab
--- /dev/null
+++ b/src/libs/3rdparty/libvterm/vterm.qbs
@@ -0,0 +1,34 @@
+Project {
+ QtcLibrary {
+ name: "vterm"
+ type: "staticlibrary"
+
+ Depends { name: "cpp" }
+ cpp.includePaths: base.concat("include")
+ cpp.warningLevel: "none"
+
+ Group {
+ prefix: "src/"
+ files: [
+ "encoding.c",
+ "fullwidth.inc",
+ "keyboard.c",
+ "mouse.c",
+ "parser.c",
+ "pen.c",
+ "rect.h",
+ "screen.c",
+ "state.c",
+ "unicode.c",
+ "utf8.h",
+ "vterm.c",
+ "vterm_internal.h",
+ ]
+ }
+
+ Export {
+ Depends { name: "cpp" }
+ cpp.includePaths: base.concat("include")
+ }
+ }
+}
diff --git a/src/libs/3rdparty/winpty/.gitattributes b/src/libs/3rdparty/winpty/.gitattributes
new file mode 100644
index 00000000000..36d4c60f1a7
--- /dev/null
+++ b/src/libs/3rdparty/winpty/.gitattributes
@@ -0,0 +1,19 @@
+* text=auto
+*.bat text eol=crlf
+*.c text
+*.cc text
+*.gyp text
+*.gypi text
+*.h text
+*.ps1 text eol=crlf
+*.rst text
+*.sh text
+*.txt text
+.gitignore text
+.gitattributes text
+Makefile text
+configure text
+
+*.sh eol=lf
+configure eol=lf
+VERSION.txt eol=lf
diff --git a/src/libs/3rdparty/winpty/.gitignore b/src/libs/3rdparty/winpty/.gitignore
new file mode 100644
index 00000000000..68c6b47fb3d
--- /dev/null
+++ b/src/libs/3rdparty/winpty/.gitignore
@@ -0,0 +1,16 @@
+*.sln
+*.suo
+*.vcxproj
+*.vcxproj.filters
+*.pyc
+winpty.sdf
+winpty.opensdf
+/config.mk
+/build
+/build-gyp
+/build-libpty
+/ship/packages
+/ship/tmp
+/src/Default
+/src/Release
+/src/gen
diff --git a/src/libs/3rdparty/winpty/CMakeLists.txt b/src/libs/3rdparty/winpty/CMakeLists.txt
new file mode 100644
index 00000000000..febd4f0ab6f
--- /dev/null
+++ b/src/libs/3rdparty/winpty/CMakeLists.txt
@@ -0,0 +1 @@
+add_subdirectory(src)
diff --git a/src/libs/3rdparty/winpty/LICENSE b/src/libs/3rdparty/winpty/LICENSE
new file mode 100644
index 00000000000..246fbe01135
--- /dev/null
+++ b/src/libs/3rdparty/winpty/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2011-2016 Ryan Prichard
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to
+deal in the Software without restriction, including without limitation the
+rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+sell copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+IN THE SOFTWARE.
diff --git a/src/libs/3rdparty/winpty/README.md b/src/libs/3rdparty/winpty/README.md
new file mode 100644
index 00000000000..bc8e7d6e5f4
--- /dev/null
+++ b/src/libs/3rdparty/winpty/README.md
@@ -0,0 +1,151 @@
+# winpty
+
+[![Build Status](https://ci.appveyor.com/api/projects/status/69tb9gylsph1ee1x/branch/master?svg=true)](https://ci.appveyor.com/project/rprichard/winpty/branch/master)
+
+winpty is a Windows software package providing an interface similar to a Unix
+pty-master for communicating with Windows console programs. The package
+consists of a library (libwinpty) and a tool for Cygwin and MSYS for running
+Windows console programs in a Cygwin/MSYS pty.
+
+The software works by starting the `winpty-agent.exe` process with a new,
+hidden console window, which bridges between the console API and terminal
+input/output escape codes. It polls the hidden console's screen buffer for
+changes and generates a corresponding stream of output.
+
+The Unix adapter allows running Windows console programs (e.g. CMD, PowerShell,
+IronPython, etc.) under `mintty` or Cygwin's `sshd` with
+properly-functioning input (e.g. arrow and function keys) and output (e.g. line
+buffering). The library could be also useful for writing a non-Cygwin SSH
+server.
+
+## Supported Windows versions
+
+winpty runs on Windows XP through Windows 10, including server versions. It
+can be compiled into either 32-bit or 64-bit binaries.
+
+## Cygwin/MSYS adapter (`winpty.exe`)
+
+### Prerequisites
+
+You need the following to build winpty:
+
+* A Cygwin or MSYS installation
+* GNU make
+* A MinGW g++ toolchain capable of compiling C++11 code to build `winpty.dll`
+ and `winpty-agent.exe`
+* A g++ toolchain targeting Cygwin or MSYS to build `winpty.exe`
+
+Winpty requires two g++ toolchains as it is split into two parts. The
+`winpty.dll` and `winpty-agent.exe` binaries interface with the native
+Windows command prompt window so they are compiled with the native MinGW
+toolchain. The `winpty.exe` binary interfaces with the MSYS/Cygwin terminal so
+it is compiled with the MSYS/Cygwin toolchain.
+
+MinGW appears to be split into two distributions -- MinGW (creates 32-bit
+binaries) and MinGW-w64 (creates both 32-bit and 64-bit binaries). Either
+one is generally acceptable.
+
+#### Cygwin packages
+
+The default g++ compiler for Cygwin targets Cygwin itself, but Cygwin also
+packages MinGW-w64 compilers. As of this writing, the necessary packages are:
+
+* Either `mingw64-i686-gcc-g++` or `mingw64-x86_64-gcc-g++`. Select the
+ appropriate compiler for your CPU architecture.
+* `gcc-g++`
+* `make`
+
+As of this writing (2016-01-23), only the MinGW-w64 compiler is acceptable.
+The MinGW compiler (e.g. from the `mingw-gcc-g++` package) is no longer
+maintained and is too buggy.
+
+#### MSYS packages
+
+For the original MSYS, use the `mingw-get` tool (MinGW Installation Manager),
+and select at least these components:
+
+* `mingw-developer-toolkit`
+* `mingw32-base`
+* `mingw32-gcc-g++`
+* `msys-base`
+* `msys-system-builder`
+
+When running `./configure`, make sure that `mingw32-g++` is in your
+`PATH`. It will be in the `C:\MinGW\bin` directory.
+
+#### MSYS2 packages
+
+For MSYS2, use `pacman` and install at least these packages:
+
+* `msys/gcc`
+* `mingw32/mingw-w64-i686-gcc` or `mingw64/mingw-w64-x86_64-gcc`. Select
+ the appropriate compiler for your CPU architecture.
+* `make`
+
+MSYS2 provides three start menu shortcuts for starting MSYS2:
+
+* MinGW-w64 Win32 Shell
+* MinGW-w64 Win64 Shell
+* MSYS2 Shell
+
+To build winpty, use the MinGW-w64 {Win32,Win64} shortcut of the architecture
+matching MSYS2. These shortcuts will put the g++ compiler from the
+`{mingw32,mingw64}/mingw-w64-{i686,x86_64}-gcc` packages into the `PATH`.
+
+Alternatively, instead of installing `mingw32/mingw-w64-i686-gcc` or
+`mingw64/mingw-w64-x86_64-gcc`, install the `mingw-w64-cross-gcc` and
+`mingw-w64-cross-crt-git` packages. These packages install cross-compilers
+into `/opt/bin`, and then any of the three shortcuts will work.
+
+### Building the Unix adapter
+
+In the project directory, run `./configure`, then `make`, then `make install`.
+By default, winpty is installed into `/usr/local`. Pass `PREFIX=<path>` to
+`make install` to override this default.
+
+### Using the Unix adapter
+
+To run a Windows console program in `mintty` or Cygwin `sshd`, prepend
+`winpty` to the command-line:
+
+ $ winpty powershell
+ Windows PowerShell
+ Copyright (C) 2009 Microsoft Corporation. All rights reserved.
+
+ PS C:\rprichard\proj\winpty> 10 + 20
+ 30
+ PS C:\rprichard\proj\winpty> exit
+
+## Embedding winpty / MSVC compilation
+
+See `src/include/winpty.h` for the prototypes of functions exported by
+`winpty.dll`.
+
+Only the `winpty.exe` binary uses Cygwin; all the other binaries work without
+it and can be compiled with either MinGW or MSVC. To compile using MSVC,
+download gyp and run `gyp -I configurations.gypi` in the `src` subdirectory.
+This will generate a `winpty.sln` and associated project files. See the
+`src/winpty.gyp` and `src/configurations.gypi` files for notes on dealing with
+MSVC versions and different architectures.
+
+Compiling winpty with MSVC currently requires MSVC 2013 or newer.
+
+## Debugging winpty
+
+winpty comes with a tool for collecting timestamped debugging output. To use
+it:
+
+1. Run `winpty-debugserver.exe` on the same computer as winpty.
+2. Set the `WINPTY_DEBUG` environment variable to `trace` for the
+ `winpty.exe` process and/or the process using `libwinpty.dll`.
+
+winpty also recognizes a `WINPTY_SHOW_CONSOLE` environment variable. Set it
+to 1 to prevent winpty from hiding the console window.
+
+## Copyright
+
+This project is distributed under the MIT license (see the `LICENSE` file in
+the project root).
+
+By submitting a pull request for this project, you agree to license your
+contribution under the MIT license to this project.
diff --git a/src/libs/3rdparty/winpty/RELEASES.md b/src/libs/3rdparty/winpty/RELEASES.md
new file mode 100644
index 00000000000..768cdf90e36
--- /dev/null
+++ b/src/libs/3rdparty/winpty/RELEASES.md
@@ -0,0 +1,280 @@
+# Next Version
+
+Input handling changes:
+
+ * Improve Ctrl-C handling with programs that use unprocessed input. (e.g.
+ Ctrl-C now cancels input with PowerShell on Windows 10.)
+ [#116](https://github.com/rprichard/winpty/issues/116)
+ * Fix a theoretical issue with input event ordering.
+ [#117](https://github.com/rprichard/winpty/issues/117)
+ * Ctrl/Shift+{Arrow,Home,End} keys now work with IntelliJ.
+ [#118](https://github.com/rprichard/winpty/issues/118)
+
+# Version 0.4.3 (2017-05-17)
+
+Input handling changes:
+
+ * winpty sets `ENHANCED_KEY` for arrow and navigation keys. This fixes an
+ issue with the Ruby REPL.
+ [#99](https://github.com/rprichard/winpty/issues/99)
+ * AltGr keys are handled better now.
+ [#109](https://github.com/rprichard/winpty/issues/109)
+ * In `ENABLE_VIRTUAL_TERMINAL_INPUT` mode, when typing Home/End with a
+ modifier (e.g. Ctrl), winpty now generates an H/F escape sequence like
+ `^[[1;5F` rather than a 1/4 escape like `^[[4;5~`.
+ [#114](https://github.com/rprichard/winpty/issues/114)
+
+Resizing and scraping fixes:
+
+ * winpty now synthesizes a `WINDOW_BUFFER_SIZE_EVENT` event after resizing
+ the console to better propagate window size changes to console programs.
+ In particular, this affects WSL and Cygwin.
+ [#110](https://github.com/rprichard/winpty/issues/110)
+ * Better handling of resizing for certain full-screen programs, like
+ WSL less.
+ [#112](https://github.com/rprichard/winpty/issues/112)
+ * Hide the cursor if it's currently outside the console window. This change
+ fixes an issue with Far Manager.
+ [#113](https://github.com/rprichard/winpty/issues/113)
+ * winpty now avoids using console fonts smaller than 5px high to improve
+ half-vs-full-width character handling. See
+ https://github.com/Microsoft/vscode/issues/19665.
+ [b4db322010](https://github.com/rprichard/winpty/commit/b4db322010d2d897e6c496fefc4f0ecc9b84c2f3)
+
+Cygwin/MSYS adapter fix:
+
+ * The way the `winpty` Cygwin/MSYS2 adapter searches for the program to
+ launch changed. It now resolves symlinks and searches the PATH explicitly.
+ [#81](https://github.com/rprichard/winpty/issues/81)
+ [#98](https://github.com/rprichard/winpty/issues/98)
+
+This release does not include binaries for the old MSYS1 project anymore.
+MSYS2 will continue to be supported. See
+https://github.com/rprichard/winpty/issues/97.
+
+# Version 0.4.2 (2017-01-18)
+
+This release improves WSL support (i.e. Bash-on-Windows):
+
+ * winpty generates more correct input escape sequences for WSL programs that
+ enable an alternate input mode using DECCKM. This bug affected arrow keys
+ and Home/End in WSL programs such as `vim`, `mc`, and `less`.
+ [#90](https://github.com/rprichard/winpty/issues/90)
+ * winpty now recognizes the `COMMON_LVB_REVERSE_VIDEO` and
+ `COMMON_LVB_UNDERSCORE` text attributes. The Windows console uses these
+ attributes to implement the SGR.4(Underline) and SGR.7(Negative) modes in
+ its VT handling. This change affects WSL pager status bars, man pages, etc.
+
+The build system no longer has a "version suffix" mechanism, so passing
+`VERSION_SUFFIX=<suffix>` to make or `-D VERSION_SUFFIX=<suffix>` to gyp now
+has no effect. AFAIK, the mechanism was never used publicly.
+[67a34b6c03](https://github.com/rprichard/winpty/commit/67a34b6c03557a5c2e0a2bdd502c2210921d8f3e)
+
+# Version 0.4.1 (2017-01-03)
+
+Bug fixes:
+
+ * This version fixes a bug where the `winpty-agent.exe` process could read
+ past the end of a buffer.
+ [#94](https://github.com/rprichard/winpty/issues/94)
+
+# Version 0.4.0 (2016-06-28)
+
+The winpty library has a new API that should be easier for embedding.
+[880c00c69e](https://github.com/rprichard/winpty/commit/880c00c69eeca73643ddb576f02c5badbec81f56)
+
+User-visible changes:
+
+ * winpty now automatically puts the terminal into mouse mode when it detects
+ that the console has left QuickEdit mode. The `--mouse` option still forces
+ the terminal into mouse mode. In principle, an option could be added to
+ suppress terminal mode, but hopefully it won't be necessary. There is a
+ script in the `misc` subdirectory, `misc/ConinMode.ps1`, that can change
+ the QuickEdit mode from the command-line.
+ * winpty now passes keyboard escapes to `bash.exe` in the Windows Subsystem
+ for Linux.
+ [#82](https://github.com/rprichard/winpty/issues/82)
+
+Bug fixes:
+
+ * By default, `winpty.dll` avoids calling `SetProcessWindowStation` within
+ the calling process.
+ [#58](https://github.com/rprichard/winpty/issues/58)
+ * Fixed an uninitialized memory bug that could have crashed winpty.
+ [#80](https://github.com/rprichard/winpty/issues/80)
+ * winpty now works better with very large and very small terminal windows.
+ It resizes the console font according to the number of columns.
+ [#61](https://github.com/rprichard/winpty/issues/61)
+ * winpty no longer uses Mark to freeze the console on Windows 10. The Mark
+ command could interfere with the cursor position, corrupting the data in
+ the screen buffer.
+ [#79](https://github.com/rprichard/winpty/issues/79)
+
+# Version 0.3.0 (2016-05-20)
+
+User-visible changes:
+
+ * The UNIX adapter is renamed from `console.exe` to `winpty.exe` to be
+ consistent with MSYS2. The name `winpty.exe` is less likely to conflict
+ with another program and is easier to search for online (e.g. for someone
+ unfamiliar with winpty).
+ * The UNIX adapter now clears the `TERM` variable.
+ [#43](https://github.com/rprichard/winpty/issues/43)
+ * An escape character appearing in a console screen buffer cell is converted
+ to a '?'.
+ [#47](https://github.com/rprichard/winpty/issues/47)
+
+Bug fixes:
+
+ * A major bug affecting XP users was fixed.
+ [#67](https://github.com/rprichard/winpty/issues/67)
+ * Fixed an incompatibility with ConEmu where winpty hung if ConEmu's
+ "Process 'start'" feature was enabled.
+ [#70](https://github.com/rprichard/winpty/issues/70)
+ * Fixed a bug where `cmd.exe` sometimes printed the message,
+ `Not enough storage is available to process this command.`.
+ [#74](https://github.com/rprichard/winpty/issues/74)
+
+Many changes internally:
+
+ * The codebase is switched from C++03 to C++11 and uses exceptions internally.
+ No exceptions are thrown across the C APIs defined in `winpty.h`.
+ * This version drops support for the original MinGW compiler packaged with
+ Cygwin (`i686-pc-mingw32-g++`). The MinGW-w64 compiler is still supported,
+ as is the MinGW distributed at mingw.org. Compiling with MSVC now requires
+ MSVC 2013 or newer. Windows XP is still supported.
+ [ec3eae8df5](https://github.com/rprichard/winpty/commit/ec3eae8df5bbbb36d7628d168b0815638d122f37)
+ * Pipe security is improved. winpty works harder to produce unique pipe names
+ and includes a random component in the name. winpty secures pipes with a
+ DACL that prevents arbitrary users from connecting to its pipes. winpty now
+ passes `PIPE_REJECT_REMOTE_CLIENTS` on Vista and up, and it verifies that
+ the pipe client PID is correct, again on Vista and up. When connecting to a
+ named pipe, winpty uses the `SECURITY_IDENTIFICATION` flag to restrict
+ impersonation. Previous versions *should* still be secure.
+ * `winpty-debugserver.exe` now has an `--everyone` flag that allows capturing
+ debug output from other users.
+ * The code now compiles cleanly with MSVC's "Security Development Lifecycle"
+ (`/SDL`) checks enabled.
+
+# Version 0.2.2 (2016-02-25)
+
+Minor bug fixes and enhancements:
+
+ * Fix a bug that generated spurious mouse input records when an incomplete
+ mouse escape sequence was seen.
+ * Fix a buffer overflow bug in `winpty-debugserver.exe` affecting messages of
+ exactly 4096 bytes.
+ * For MSVC builds, add a `src/configurations.gypi` file that can be included
+ on the gyp command-line to enable 32-bit and 64-bit builds.
+ * `winpty-agent --show-input` mode: Flush stdout after each line.
+ * Makefile builds: generate a `build/winpty.lib` import library to accompany
+ `build/winpty.dll`.
+
+# Version 0.2.1 (2015-12-19)
+
+ * The main project source was moved into a `src` directory for better code
+ organization and to fix
+ [#51](https://github.com/rprichard/winpty/issues/51).
+ * winpty recognizes many more escape sequences, including:
+ * putty/rxvt's F1-F4 keys
+ [#40](https://github.com/rprichard/winpty/issues/40)
+ * the Linux virtual console's F1-F5 keys
+ * the "application numpad" keys (e.g. enabled with DECPAM)
+ * Fixed handling of Shift-Alt-O and Alt-[.
+ * Added support for mouse input. The UNIX adapter has a `--mouse` argument
+ that puts the terminal into mouse mode, but the agent recognizes mouse
+ input even without the argument. The agent recognizes double-clicks using
+ Windows' double-click interval setting (i.e. GetDoubleClickTime).
+ [#57](https://github.com/rprichard/winpty/issues/57)
+
+Changes to debugging interfaces:
+
+ * The `WINPTY_DEBUG` variable is now a comma-separated list. The old
+ behavior (i.e. tracing) is enabled with `WINPTY_DEBUG=trace`.
+ * The UNIX adapter program now has a `--showkey` argument that dumps input
+ bytes.
+ * The `winpty-agent.exe` program has a `--show-input` argument that dumps
+ `INPUT_RECORD` records. (It omits mouse events unless `--with-mouse` is
+ also specified.) The agent also responds to `WINPTY_DEBUG=trace,input`,
+ which logs input bytes and synthesized console events, and it responds to
+ `WINPTY_DEBUG=trace,dump_input_map`, which dumps the internal table of
+ escape sequences.
+
+# Version 0.2.0 (2015-11-13)
+
+No changes to the API, but many small changes to the implementation. The big
+changes include:
+
+ * Support for 64-bit Cygwin and MSYS2
+ * Support for Windows 10
+ * Better Unicode support (especially East Asian languages)
+
+Details:
+
+ * The `configure` script recognizes 64-bit Cygwin and MSYS2 environments and
+ selects the appropriate compiler.
+ * winpty works much better with the upgraded console in Windows 10. The
+ `conhost.exe` hang can still occur, but only with certain programs, and
+ is much less likely to occur. With the new console, use Mark instead of
+ SelectAll, for better performance.
+ [#31](https://github.com/rprichard/winpty/issues/31)
+ [#30](https://github.com/rprichard/winpty/issues/30)
+ [#53](https://github.com/rprichard/winpty/issues/53)
+ * The UNIX adapter now calls `setlocale(LC_ALL, "")` to set the locale.
+ * Improved Unicode support. When a console is started with an East Asian code
+ page, winpty now chooses an East Asian font rather than Consolas / Lucida
+ Console. Selecting the right font helps synchronize character widths
+ between the console and terminal. (It's not perfect, though.)
+ [#41](https://github.com/rprichard/winpty/issues/41)
+ * winpty now more-or-less works with programs that change the screen buffer
+ or resize the original screen buffer. If the screen buffer height changes,
+ winpty switches to a "direct mode", where it makes no effort to track
+ scrolling. In direct mode, it merely syncs snapshots of the console to the
+ terminal. Caveats:
+ * Changing the screen buffer (i.e. `SetConsoleActiveScreenBuffer`)
+ breaks winpty on Windows 7. This problem can eventually be mitigated,
+ but never completely fixed, due to Windows 7 bugginess.
+ * Resizing the original screen buffer can hang `conhost.exe` on Windows 10.
+ Enabling the legacy console is a workaround.
+ * If a program changes the screen buffer and then exits, relying on the OS
+ to restore the original screen buffer, that restoration probably will not
+ happen with winpty. winpty's behavior can probably be improved here.
+ * Improved color handling:
+ * DkGray-on-Black text was previously hiddenly completely. Now it is
+ output as DkGray, with a fallback to LtGray on terminals that don't
+ recognize the intense colors.
+ [#39](https://github.com/rprichard/winpty/issues/39).
+ * The console is always initialized to LtGray-on-Black, regardless of the
+ user setting, which matches the console color heuristic, which translates
+ LtGray-on-Black to "reset SGR parameters."
+ * Shift-Tab is recognized correctly now.
+ [#19](https://github.com/rprichard/winpty/issues/19)
+ * Add a `--version` argument to `winpty-agent.exe` and the UNIX adapter. The
+ argument reports the nominal version (i.e. the `VERSION.txt`) file, with a
+ "VERSION_SUFFIX" appended (defaulted to `-dev`), and a git commit hash, if
+ the `git` command successfully reports a hash during the build. The `git`
+ command is invoked by either `make` or `gyp`.
+ * The agent now combines `ReadConsoleOutputW` calls when it polls the console
+ buffer for changes, which may slightly reduce its CPU overhead.
+ [#44](https://github.com/rprichard/winpty/issues/44).
+ * A `gyp` file is added to help compile with MSVC.
+ * The code can now be compiled as C++11 code, though it isn't by default.
+ [bde8922e08](https://github.com/rprichard/winpty/commit/bde8922e08c3638e01ecc7b581b676c314163e3c)
+ * If winpty can't create a new window station, it charges ahead rather than
+ aborting. This situation might happen if winpty were started from an SSH
+ session.
+ * Debugging improvements:
+ * `WINPTYDBG` is renamed to `WINPTY_DEBUG`, and a new `WINPTY_SHOW_CONSOLE`
+ variable keeps the underlying console visible.
+ * A `winpty-debugserver.exe` program is built and shipped by default. It
+ collects the trace output enabled with `WINPTY_DEBUG`.
+ * The `Makefile` build of winpty now compiles `winpty-agent.exe` and
+ `winpty.dll` with -O2.
+
+# Version 0.1.1 (2012-07-28)
+
+Minor bugfix release.
+
+# Version 0.1 (2012-04-17)
+
+Initial release.
diff --git a/src/libs/3rdparty/winpty/VERSION.txt b/src/libs/3rdparty/winpty/VERSION.txt
new file mode 100644
index 00000000000..5d47ff8c45a
--- /dev/null
+++ b/src/libs/3rdparty/winpty/VERSION.txt
@@ -0,0 +1 @@
+0.4.4-dev
diff --git a/src/libs/3rdparty/winpty/appveyor.yml b/src/libs/3rdparty/winpty/appveyor.yml
new file mode 100644
index 00000000000..a9e8726fc13
--- /dev/null
+++ b/src/libs/3rdparty/winpty/appveyor.yml
@@ -0,0 +1,16 @@
+image: Visual Studio 2015
+
+init:
+ - C:\msys64\usr\bin\bash --login -c "pacman -S --needed --noconfirm --noprogressbar msys/make msys/tar msys/gcc mingw-w64-cross-toolchain"
+ - C:\cygwin\setup-x86 -q -P mingw64-i686-gcc-g++,mingw64-x86_64-gcc-g++,make
+ - C:\cygwin64\setup-x86_64 -q -P mingw64-i686-gcc-g++,mingw64-x86_64-gcc-g++,make
+
+build_script:
+ - C:\Python27-x64\python.exe ship\ship.py --kind msys2 --arch x64 --syspath C:\msys64
+ - C:\Python27-x64\python.exe ship\ship.py --kind cygwin --arch ia32 --syspath C:\cygwin
+ - C:\Python27-x64\python.exe ship\ship.py --kind cygwin --arch x64 --syspath C:\cygwin64
+ - C:\Python27-x64\python.exe ship\make_msvc_package.py
+
+artifacts:
+ - path: ship\packages\*.tar.gz
+ - path: ship\packages\*.zip
diff --git a/src/libs/3rdparty/winpty/configure b/src/libs/3rdparty/winpty/configure
new file mode 100644
index 00000000000..6d37d65b091
--- /dev/null
+++ b/src/libs/3rdparty/winpty/configure
@@ -0,0 +1,167 @@
+#!/bin/bash
+#
+# Copyright (c) 2011-2015 Ryan Prichard
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+#
+# findTool(desc, commandList)
+#
+# Searches commandLine for the first command in the PATH and returns it.
+# Prints an error and aborts the script if no match is found.
+#
+FINDTOOL_OUT=""
+function findTool {
+ DESC=$1
+ OPTIONS=$2
+ for CMD in ${OPTIONS}; do
+ if (which $CMD &>/dev/null) then
+ echo "Found $DESC: $CMD"
+ FINDTOOL_OUT="$CMD"
+ return
+ fi
+ done
+ echo "Error: could not find $DESC. One of these should be in your PATH:"
+ for CMD in ${OPTIONS}; do
+ echo " * $CMD"
+ done
+ exit 1
+}
+
+IS_CYGWIN=0
+IS_MSYS1=0
+IS_MSYS2=0
+
+# Link parts of the Cygwin binary statically to aid in redistribution? The
+# binary still links dynamically against the main DLL. The MinGW binaries are
+# also statically linked and therefore depend only on Windows DLLs. I started
+# linking the Cygwin/MSYS binary statically, because G++ 4.7 changed the
+# Windows C++ ABI.
+UNIX_LDFLAGS_STATIC='-static -static-libgcc -static-libstdc++'
+
+# Detect the environment -- Cygwin or MSYS.
+case $(uname -s) in
+ CYGWIN*)
+ echo 'uname -s identifies a Cygwin environment.'
+ IS_CYGWIN=1
+ case $(uname -m) in
+ i686)
+ echo 'uname -m identifies an i686 environment.'
+ UNIX_CXX=i686-pc-cygwin-g++
+ MINGW_CXX=i686-w64-mingw32-g++
+ ;;
+ x86_64)
+ echo 'uname -m identifies an x86_64 environment.'
+ UNIX_CXX=x86_64-pc-cygwin-g++
+ MINGW_CXX=x86_64-w64-mingw32-g++
+ ;;
+ *)
+ echo 'Error: uname -m did not match either i686 or x86_64.'
+ exit 1
+ ;;
+ esac
+ ;;
+ MSYS*|MINGW*)
+ # MSYS2 notes:
+ # - MSYS2 offers two shortcuts to open an environment:
+ # - MinGW-w64 Win32 Shell. This env reports a `uname -s` of
+ # MINGW32_NT-6.1 on 32-bit Win7. The MinGW-w64 compiler
+ # (i686-w64-mingw32-g++.exe) is in the PATH.
+ # - MSYS2 Shell. `uname -s` instead reports MSYS_NT-6.1.
+ # The i686-w64-mingw32-g++ compiler is not in the PATH.
+ # - MSYS2 appears to use MinGW-w64, not the older mingw.org.
+ # MSYS notes:
+ # - `uname -s` is always MINGW32_NT-6.1 on Win7.
+ echo 'uname -s identifies an MSYS/MSYS2 environment.'
+ case $(uname -m) in
+ i686)
+ echo 'uname -m identifies an i686 environment.'
+ UNIX_CXX=i686-pc-msys-g++
+ if echo "$(uname -r)" | grep '^1[.]' > /dev/null; then
+ # The MSYS-targeting compiler for the original 32-bit-only
+ # MSYS does not recognize the -static-libstdc++ flag, and
+ # it does not work with -static, because it tries to link
+ # statically with the core MSYS library and fails.
+ #
+ # Distinguish between the two using the major version
+ # number of `uname -r`:
+ #
+ # MSYS uname -r: 1.0.18(0.48/3/2)
+ # MSYS2 uname -r: 2.0.0(0.284/5/3)
+ #
+ # This is suboptimal because MSYS2 is not actually the
+ # second version of MSYS--it's a brand-new fork of Cygwin.
+ #
+ IS_MSYS1=1
+ UNIX_LDFLAGS_STATIC=
+ MINGW_CXX=mingw32-g++
+ else
+ IS_MSYS2=1
+ MINGW_CXX=i686-w64-mingw32-g++.exe
+ fi
+ ;;
+ x86_64)
+ echo 'uname -m identifies an x86_64 environment.'
+ IS_MSYS2=1
+ UNIX_CXX=x86_64-pc-msys-g++
+ MINGW_CXX=x86_64-w64-mingw32-g++
+ ;;
+ *)
+ echo 'Error: uname -m did not match either i686 or x86_64.'
+ exit 1
+ ;;
+ esac
+ ;;
+ *)
+ echo 'Error: uname -s did not match either CYGWIN* or MINGW*.'
+ exit 1
+ ;;
+esac
+
+# Search the PATH and pick the first match.
+findTool "Cygwin/MSYS G++ compiler" "$UNIX_CXX"
+UNIX_CXX=$FINDTOOL_OUT
+findTool "MinGW G++ compiler" "$MINGW_CXX"
+MINGW_CXX=$FINDTOOL_OUT
+
+# Write config files.
+echo Writing config.mk
+echo UNIX_CXX=$UNIX_CXX > config.mk
+echo UNIX_LDFLAGS_STATIC=$UNIX_LDFLAGS_STATIC >> config.mk
+echo MINGW_CXX=$MINGW_CXX >> config.mk
+
+if test $IS_MSYS1 = 1; then
+ echo UNIX_CXXFLAGS += -DWINPTY_TARGET_MSYS1 >> config.mk
+ # The MSYS1 MinGW compiler has a bug that prevents inclusion of algorithm
+ # and math.h in normal C++11 mode. The workaround is to enable the gnu++11
+ # mode instead. The bug was fixed on 2015-07-31, but as of 2016-02-26, the
+ # fix apparently hasn't been released. See
+ # http://ehc.ac/p/mingw/bugs/2250/.
+ echo MINGW_ENABLE_CXX11_FLAG := -std=gnu++11 >> config.mk
+fi
+
+if test -d .git -a -f .git/HEAD -a -f .git/index && git rev-parse HEAD >&/dev/null; then
+ echo "Commit info: git"
+ echo 'COMMIT_HASH = $(shell git rev-parse HEAD)' >> config.mk
+ echo 'COMMIT_HASH_DEP := config.mk .git/HEAD .git/index' >> config.mk
+else
+ echo "Commit info: none"
+ echo 'COMMIT_HASH := none' >> config.mk
+ echo 'COMMIT_HASH_DEP := config.mk' >> config.mk
+fi
diff --git a/src/libs/3rdparty/winpty/misc/.gitignore b/src/libs/3rdparty/winpty/misc/.gitignore
new file mode 100644
index 00000000000..23751645fa1
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/.gitignore
@@ -0,0 +1,2 @@
+*.exe
+UnixEcho \ No newline at end of file
diff --git a/src/libs/3rdparty/winpty/misc/BufferResizeTests.cc b/src/libs/3rdparty/winpty/misc/BufferResizeTests.cc
new file mode 100644
index 00000000000..a5bb074826f
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/BufferResizeTests.cc
@@ -0,0 +1,90 @@
+#include <windows.h>
+#include <cassert>
+
+#include "TestUtil.cc"
+
+void dumpInfoToTrace() {
+ CONSOLE_SCREEN_BUFFER_INFO info;
+ assert(GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info));
+ trace("win=(%d,%d,%d,%d)",
+ (int)info.srWindow.Left,
+ (int)info.srWindow.Top,
+ (int)info.srWindow.Right,
+ (int)info.srWindow.Bottom);
+ trace("buf=(%d,%d)",
+ (int)info.dwSize.X,
+ (int)info.dwSize.Y);
+ trace("cur=(%d,%d)",
+ (int)info.dwCursorPosition.X,
+ (int)info.dwCursorPosition.Y);
+}
+
+int main(int argc, char *argv[]) {
+ if (argc == 1) {
+ startChildProcess(L"CHILD");
+ return 0;
+ }
+
+ setWindowPos(0, 0, 1, 1);
+
+ if (false) {
+ // Reducing the buffer height can move the window up.
+ setBufferSize(80, 25);
+ setWindowPos(0, 20, 80, 5);
+ Sleep(2000);
+ setBufferSize(80, 10);
+ }
+
+ if (false) {
+ // Reducing the buffer height moves the window up and the buffer
+ // contents up too.
+ setBufferSize(80, 25);
+ setWindowPos(0, 20, 80, 5);
+ setCursorPos(0, 20);
+ printf("TEST1\nTEST2\nTEST3\nTEST4\n");
+ fflush(stdout);
+ Sleep(2000);
+ setBufferSize(80, 10);
+ }
+
+ if (false) {
+ // Reducing the buffer width can move the window left.
+ setBufferSize(80, 25);
+ setWindowPos(40, 0, 40, 25);
+ Sleep(2000);
+ setBufferSize(60, 25);
+ }
+
+ if (false) {
+ // Sometimes the buffer contents are shifted up; sometimes they're
+ // shifted down. It seems to depend on the cursor position?
+
+ // setBufferSize(80, 25);
+ // setWindowPos(0, 20, 80, 5);
+ // setCursorPos(0, 20);
+ // printf("TESTa\nTESTb\nTESTc\nTESTd\nTESTe");
+ // fflush(stdout);
+ // setCursorPos(0, 0);
+ // printf("TEST1\nTEST2\nTEST3\nTEST4\nTEST5");
+ // fflush(stdout);
+ // setCursorPos(0, 24);
+ // Sleep(5000);
+ // setBufferSize(80, 24);
+
+ setBufferSize(80, 20);
+ setWindowPos(0, 10, 80, 10);
+ setCursorPos(0, 18);
+
+ printf("TEST1\nTEST2");
+ fflush(stdout);
+ setCursorPos(0, 18);
+
+ Sleep(2000);
+ setBufferSize(80, 18);
+ }
+
+ dumpInfoToTrace();
+ Sleep(30000);
+
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/ChangeScreenBuffer.cc b/src/libs/3rdparty/winpty/misc/ChangeScreenBuffer.cc
new file mode 100644
index 00000000000..701a2cb4a3c
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/ChangeScreenBuffer.cc
@@ -0,0 +1,53 @@
+// A test program for CreateConsoleScreenBuffer / SetConsoleActiveScreenBuffer
+//
+
+#include <windows.h>
+#include <stdio.h>
+#include <conio.h>
+#include <io.h>
+#include <cassert>
+
+#include "TestUtil.cc"
+
+int main()
+{
+ HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE);
+ HANDLE childBuffer = CreateConsoleScreenBuffer(
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
+
+ SetConsoleActiveScreenBuffer(childBuffer);
+
+ while (true) {
+ char buf[1024];
+ CONSOLE_SCREEN_BUFFER_INFO info;
+
+ assert(GetConsoleScreenBufferInfo(origBuffer, &info));
+ trace("child.size=(%d,%d)", (int)info.dwSize.X, (int)info.dwSize.Y);
+ trace("child.cursor=(%d,%d)", (int)info.dwCursorPosition.X, (int)info.dwCursorPosition.Y);
+ trace("child.window=(%d,%d,%d,%d)",
+ (int)info.srWindow.Left, (int)info.srWindow.Top,
+ (int)info.srWindow.Right, (int)info.srWindow.Bottom);
+ trace("child.maxSize=(%d,%d)", (int)info.dwMaximumWindowSize.X, (int)info.dwMaximumWindowSize.Y);
+
+ int ch = getch();
+ sprintf(buf, "%02x\n", ch);
+ DWORD actual = 0;
+ WriteFile(childBuffer, buf, strlen(buf), &actual, NULL);
+ if (ch == 0x1b/*ESC*/ || ch == 0x03/*CTRL-C*/)
+ break;
+
+ if (ch == 'b') {
+ setBufferSize(origBuffer, 40, 25);
+ } else if (ch == 'w') {
+ setWindowPos(origBuffer, 1, 1, 38, 23);
+ } else if (ch == 'c') {
+ setCursorPos(origBuffer, 10, 10);
+ }
+ }
+
+ SetConsoleActiveScreenBuffer(origBuffer);
+
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/ClearConsole.cc b/src/libs/3rdparty/winpty/misc/ClearConsole.cc
new file mode 100644
index 00000000000..f95f8c84caa
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/ClearConsole.cc
@@ -0,0 +1,72 @@
+/*
+ * Demonstrates that console clearing sets each cell's character to SP, not
+ * NUL, and it sets the attribute of each cell to the current text attribute.
+ *
+ * This confirms the MSDN instruction in the "Clearing the Screen" article.
+ * https://msdn.microsoft.com/en-us/library/windows/desktop/ms682022(v=vs.85).aspx
+ * It advises using GetConsoleScreenBufferInfo to get the current text
+ * attribute, then FillConsoleOutputCharacter and FillConsoleOutputAttribute to
+ * write to the console buffer.
+ */
+
+#include <windows.h>
+
+#include <cassert>
+#include <cstdio>
+#include <cstdlib>
+
+#include "TestUtil.cc"
+
+int main(int argc, char *argv[]) {
+ if (argc == 1) {
+ startChildProcess(L"CHILD");
+ return 0;
+ }
+
+ const HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE);
+
+ SetConsoleTextAttribute(conout, 0x24);
+ system("cls");
+
+ setWindowPos(0, 0, 1, 1);
+ setBufferSize(80, 25);
+ setWindowPos(0, 0, 80, 25);
+
+ CHAR_INFO buf;
+ COORD bufSize = { 1, 1 };
+ COORD bufCoord = { 0, 0 };
+ SMALL_RECT rect = { 5, 5, 5, 5 };
+ BOOL ret;
+ DWORD actual;
+ COORD writeCoord = { 5, 5 };
+
+ // After cls, each cell's character is a space, and its attributes are the
+ // default text attributes.
+ ret = ReadConsoleOutputW(conout, &buf, bufSize, bufCoord, &rect);
+ assert(ret && buf.Char.UnicodeChar == L' ' && buf.Attributes == 0x24);
+
+ // Nevertheless, it is possible to change a cell to NUL.
+ ret = FillConsoleOutputCharacterW(conout, L'\0', 1, writeCoord, &actual);
+ assert(ret && actual == 1);
+ ret = ReadConsoleOutputW(conout, &buf, bufSize, bufCoord, &rect);
+ assert(ret && buf.Char.UnicodeChar == L'\0' && buf.Attributes == 0x24);
+
+ // As well as a 0 attribute. (As one would expect, the cell is
+ // black-on-black.)
+ ret = FillConsoleOutputAttribute(conout, 0, 1, writeCoord, &actual);
+ assert(ret && actual == 1);
+ ret = ReadConsoleOutputW(conout, &buf, bufSize, bufCoord, &rect);
+ assert(ret && buf.Char.UnicodeChar == L'\0' && buf.Attributes == 0);
+ ret = FillConsoleOutputCharacterW(conout, L'X', 1, writeCoord, &actual);
+ assert(ret && actual == 1);
+ ret = ReadConsoleOutputW(conout, &buf, bufSize, bufCoord, &rect);
+ assert(ret && buf.Char.UnicodeChar == L'X' && buf.Attributes == 0);
+
+ // The 'X' is invisible.
+ countDown(3);
+
+ ret = FillConsoleOutputAttribute(conout, 0x42, 1, writeCoord, &actual);
+ assert(ret && actual == 1);
+
+ countDown(5);
+}
diff --git a/src/libs/3rdparty/winpty/misc/ConinMode.cc b/src/libs/3rdparty/winpty/misc/ConinMode.cc
new file mode 100644
index 00000000000..1e1428d8b0c
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/ConinMode.cc
@@ -0,0 +1,117 @@
+#include <windows.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <string>
+#include <vector>
+
+static HANDLE getConin() {
+ HANDLE conin = GetStdHandle(STD_INPUT_HANDLE);
+ if (conin == INVALID_HANDLE_VALUE) {
+ fprintf(stderr, "error: cannot get stdin\n");
+ exit(1);
+ }
+ return conin;
+}
+
+static DWORD getConsoleMode() {
+ DWORD mode = 0;
+ if (!GetConsoleMode(getConin(), &mode)) {
+ fprintf(stderr, "error: GetConsoleMode failed (is stdin a console?)\n");
+ exit(1);
+ }
+ return mode;
+}
+
+static void setConsoleMode(DWORD mode) {
+ if (!SetConsoleMode(getConin(), mode)) {
+ fprintf(stderr, "error: SetConsoleMode failed (is stdin a console?)\n");
+ exit(1);
+ }
+}
+
+static long parseInt(const std::string &s) {
+ errno = 0;
+ char *endptr = nullptr;
+ long result = strtol(s.c_str(), &endptr, 0);
+ if (errno != 0 || !endptr || *endptr != '\0') {
+ fprintf(stderr, "error: could not parse integral argument '%s'\n", s.c_str());
+ exit(1);
+ }
+ return result;
+}
+
+static void usage() {
+ printf("Usage: ConinMode [verb] [options]\n");
+ printf("Verbs:\n");
+ printf(" [info] Dumps info about mode flags.\n");
+ printf(" get Prints the mode DWORD.\n");
+ printf(" set VALUE Sets the mode to VALUE, which can be decimal, hex, or octal.\n");
+ printf(" set VALUE MASK\n");
+ printf(" Same as `set VALUE`, but only alters the bits in MASK.\n");
+ exit(1);
+}
+
+struct {
+ const char *name;
+ DWORD value;
+} kInputFlags[] = {
+ "ENABLE_PROCESSED_INPUT", ENABLE_PROCESSED_INPUT, // 0x0001
+ "ENABLE_LINE_INPUT", ENABLE_LINE_INPUT, // 0x0002
+ "ENABLE_ECHO_INPUT", ENABLE_ECHO_INPUT, // 0x0004
+ "ENABLE_WINDOW_INPUT", ENABLE_WINDOW_INPUT, // 0x0008
+ "ENABLE_MOUSE_INPUT", ENABLE_MOUSE_INPUT, // 0x0010
+ "ENABLE_INSERT_MODE", ENABLE_INSERT_MODE, // 0x0020
+ "ENABLE_QUICK_EDIT_MODE", ENABLE_QUICK_EDIT_MODE, // 0x0040
+ "ENABLE_EXTENDED_FLAGS", ENABLE_EXTENDED_FLAGS, // 0x0080
+ "ENABLE_VIRTUAL_TERMINAL_INPUT", 0x0200/*ENABLE_VIRTUAL_TERMINAL_INPUT*/, // 0x0200
+};
+
+int main(int argc, char *argv[]) {
+ std::vector<std::string> args;
+ for (size_t i = 1; i < argc; ++i) {
+ args.push_back(argv[i]);
+ }
+
+ if (args.empty() || args.size() == 1 && args[0] == "info") {
+ DWORD mode = getConsoleMode();
+ printf("mode: 0x%lx\n", mode);
+ for (const auto &flag : kInputFlags) {
+ printf("%-29s 0x%04lx %s\n", flag.name, flag.value, flag.value & mode ? "ON" : "off");
+ mode &= ~flag.value;
+ }
+ for (int i = 0; i < 32; ++i) {
+ if (mode & (1u << i)) {
+ printf("Unrecognized flag: %04x\n", (1u << i));
+ }
+ }
+ return 0;
+ }
+
+ const auto verb = args[0];
+
+ if (verb == "set") {
+ if (args.size() == 2) {
+ const DWORD newMode = parseInt(args[1]);
+ setConsoleMode(newMode);
+ } else if (args.size() == 3) {
+ const DWORD mode = parseInt(args[1]);
+ const DWORD mask = parseInt(args[2]);
+ const int newMode = (getConsoleMode() & ~mask) | (mode & mask);
+ setConsoleMode(newMode);
+ } else {
+ usage();
+ }
+ } else if (verb == "get") {
+ if (args.size() != 1) {
+ usage();
+ }
+ printf("0x%lx\n", getConsoleMode());
+ } else {
+ usage();
+ }
+
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/ConinMode.ps1 b/src/libs/3rdparty/winpty/misc/ConinMode.ps1
new file mode 100644
index 00000000000..ecfe8f039e4
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/ConinMode.ps1
@@ -0,0 +1,116 @@
+#
+# PowerShell script for controlling the console QuickEdit and InsertMode flags.
+#
+# Turn QuickEdit off to interact with mouse-driven console programs.
+#
+# Usage:
+#
+# powershell .\ConinMode.ps1 [Options]
+#
+# Options:
+# -QuickEdit [on/off]
+# -InsertMode [on/off]
+# -Mode [integer]
+#
+
+param (
+ [ValidateSet("on", "off")][string] $QuickEdit,
+ [ValidateSet("on", "off")][string] $InsertMode,
+ [int] $Mode
+)
+
+$signature = @'
+[DllImport("kernel32.dll", SetLastError = true)]
+public static extern IntPtr GetStdHandle(int nStdHandle);
+
+[DllImport("kernel32.dll", SetLastError = true)]
+public static extern uint GetConsoleMode(
+ IntPtr hConsoleHandle,
+ out uint lpMode);
+
+[DllImport("kernel32.dll", SetLastError = true)]
+public static extern uint SetConsoleMode(
+ IntPtr hConsoleHandle,
+ uint dwMode);
+
+public const int STD_INPUT_HANDLE = -10;
+public const int ENABLE_INSERT_MODE = 0x0020;
+public const int ENABLE_QUICK_EDIT_MODE = 0x0040;
+public const int ENABLE_EXTENDED_FLAGS = 0x0080;
+'@
+
+$WinAPI = Add-Type -MemberDefinition $signature `
+ -Name WinAPI -Namespace ConinModeScript `
+ -PassThru
+
+function GetConIn {
+ $ret = $WinAPI::GetStdHandle($WinAPI::STD_INPUT_HANDLE)
+ if ($ret -eq -1) {
+ throw "error: cannot get stdin"
+ }
+ return $ret
+}
+
+function GetConsoleMode {
+ $conin = GetConIn
+ $mode = 0
+ $ret = $WinAPI::GetConsoleMode($conin, [ref]$mode)
+ if ($ret -eq 0) {
+ throw "GetConsoleMode failed (is stdin a console?)"
+ }
+ return $mode
+}
+
+function SetConsoleMode($mode) {
+ $conin = GetConIn
+ $ret = $WinAPI::SetConsoleMode($conin, $mode)
+ if ($ret -eq 0) {
+ throw "SetConsoleMode failed (is stdin a console?)"
+ }
+}
+
+$oldMode = GetConsoleMode
+$newMode = $oldMode
+$doingSomething = $false
+
+if ($PSBoundParameters.ContainsKey("Mode")) {
+ $newMode = $Mode
+ $doingSomething = $true
+}
+
+if ($QuickEdit + $InsertMode -ne "") {
+ if (!($newMode -band $WinAPI::ENABLE_EXTENDED_FLAGS)) {
+ # We can't enable an extended flag without overwriting the existing
+ # QuickEdit/InsertMode flags. AFAICT, there is no way to query their
+ # existing values, so at least we can choose sensible defaults.
+ $newMode = $newMode -bor $WinAPI::ENABLE_EXTENDED_FLAGS
+ $newMode = $newMode -bor $WinAPI::ENABLE_QUICK_EDIT_MODE
+ $newMode = $newMode -bor $WinAPI::ENABLE_INSERT_MODE
+ $doingSomething = $true
+ }
+}
+
+if ($QuickEdit -eq "on") {
+ $newMode = $newMode -bor $WinAPI::ENABLE_QUICK_EDIT_MODE
+ $doingSomething = $true
+} elseif ($QuickEdit -eq "off") {
+ $newMode = $newMode -band (-bnot $WinAPI::ENABLE_QUICK_EDIT_MODE)
+ $doingSomething = $true
+}
+
+if ($InsertMode -eq "on") {
+ $newMode = $newMode -bor $WinAPI::ENABLE_INSERT_MODE
+ $doingSomething = $true
+} elseif ($InsertMode -eq "off") {
+ $newMode = $newMode -band (-bnot $WinAPI::ENABLE_INSERT_MODE)
+ $doingSomething = $true
+}
+
+if ($doingSomething) {
+ echo "old mode: $oldMode"
+ SetConsoleMode $newMode
+ $newMode = GetConsoleMode
+ echo "new mode: $newMode"
+} else {
+ echo "mode: $oldMode"
+}
diff --git a/src/libs/3rdparty/winpty/misc/ConoutMode.cc b/src/libs/3rdparty/winpty/misc/ConoutMode.cc
new file mode 100644
index 00000000000..100e0c7bea9
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/ConoutMode.cc
@@ -0,0 +1,113 @@
+#include <windows.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <string>
+#include <vector>
+
+static HANDLE getConout() {
+ HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE);
+ if (conout == INVALID_HANDLE_VALUE) {
+ fprintf(stderr, "error: cannot get stdout\n");
+ exit(1);
+ }
+ return conout;
+}
+
+static DWORD getConsoleMode() {
+ DWORD mode = 0;
+ if (!GetConsoleMode(getConout(), &mode)) {
+ fprintf(stderr, "error: GetConsoleMode failed (is stdout a console?)\n");
+ exit(1);
+ }
+ return mode;
+}
+
+static void setConsoleMode(DWORD mode) {
+ if (!SetConsoleMode(getConout(), mode)) {
+ fprintf(stderr, "error: SetConsoleMode failed (is stdout a console?)\n");
+ exit(1);
+ }
+}
+
+static long parseInt(const std::string &s) {
+ errno = 0;
+ char *endptr = nullptr;
+ long result = strtol(s.c_str(), &endptr, 0);
+ if (errno != 0 || !endptr || *endptr != '\0') {
+ fprintf(stderr, "error: could not parse integral argument '%s'\n", s.c_str());
+ exit(1);
+ }
+ return result;
+}
+
+static void usage() {
+ printf("Usage: ConoutMode [verb] [options]\n");
+ printf("Verbs:\n");
+ printf(" [info] Dumps info about mode flags.\n");
+ printf(" get Prints the mode DWORD.\n");
+ printf(" set VALUE Sets the mode to VALUE, which can be decimal, hex, or octal.\n");
+ printf(" set VALUE MASK\n");
+ printf(" Same as `set VALUE`, but only alters the bits in MASK.\n");
+ exit(1);
+}
+
+struct {
+ const char *name;
+ DWORD value;
+} kOutputFlags[] = {
+ "ENABLE_PROCESSED_OUTPUT", ENABLE_PROCESSED_OUTPUT, // 0x0001
+ "ENABLE_WRAP_AT_EOL_OUTPUT", ENABLE_WRAP_AT_EOL_OUTPUT, // 0x0002
+ "ENABLE_VIRTUAL_TERMINAL_PROCESSING", 0x0004/*ENABLE_VIRTUAL_TERMINAL_PROCESSING*/, // 0x0004
+ "DISABLE_NEWLINE_AUTO_RETURN", 0x0008/*DISABLE_NEWLINE_AUTO_RETURN*/, // 0x0008
+ "ENABLE_LVB_GRID_WORLDWIDE", 0x0010/*ENABLE_LVB_GRID_WORLDWIDE*/, //0x0010
+};
+
+int main(int argc, char *argv[]) {
+ std::vector<std::string> args;
+ for (size_t i = 1; i < argc; ++i) {
+ args.push_back(argv[i]);
+ }
+
+ if (args.empty() || args.size() == 1 && args[0] == "info") {
+ DWORD mode = getConsoleMode();
+ printf("mode: 0x%lx\n", mode);
+ for (const auto &flag : kOutputFlags) {
+ printf("%-34s 0x%04lx %s\n", flag.name, flag.value, flag.value & mode ? "ON" : "off");
+ mode &= ~flag.value;
+ }
+ for (int i = 0; i < 32; ++i) {
+ if (mode & (1u << i)) {
+ printf("Unrecognized flag: %04x\n", (1u << i));
+ }
+ }
+ return 0;
+ }
+
+ const auto verb = args[0];
+
+ if (verb == "set") {
+ if (args.size() == 2) {
+ const DWORD newMode = parseInt(args[1]);
+ setConsoleMode(newMode);
+ } else if (args.size() == 3) {
+ const DWORD mode = parseInt(args[1]);
+ const DWORD mask = parseInt(args[2]);
+ const int newMode = (getConsoleMode() & ~mask) | (mode & mask);
+ setConsoleMode(newMode);
+ } else {
+ usage();
+ }
+ } else if (verb == "get") {
+ if (args.size() != 1) {
+ usage();
+ }
+ printf("0x%lx\n", getConsoleMode());
+ } else {
+ usage();
+ }
+
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/DebugClient.py b/src/libs/3rdparty/winpty/misc/DebugClient.py
new file mode 100644
index 00000000000..cd12df8924a
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/DebugClient.py
@@ -0,0 +1,42 @@
+#!python
+# Run with native CPython. Needs pywin32 extensions.
+
+# Copyright (c) 2011-2012 Ryan Prichard
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+import winerror
+import win32pipe
+import win32file
+import win32api
+import sys
+import pywintypes
+import time
+
+if len(sys.argv) != 2:
+ print("Usage: %s message" % sys.argv[0])
+ sys.exit(1)
+
+message = "[%05.3f %s]: %s" % (time.time() % 100000, sys.argv[0], sys.argv[1])
+
+win32pipe.CallNamedPipe(
+ "\\\\.\\pipe\\DebugServer",
+ message.encode(),
+ 16,
+ win32pipe.NMPWAIT_WAIT_FOREVER)
diff --git a/src/libs/3rdparty/winpty/misc/DebugServer.py b/src/libs/3rdparty/winpty/misc/DebugServer.py
new file mode 100644
index 00000000000..3fc068bae70
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/DebugServer.py
@@ -0,0 +1,63 @@
+#!python
+#
+# Run with native CPython. Needs pywin32 extensions.
+
+# Copyright (c) 2011-2012 Ryan Prichard
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+import win32pipe
+import win32api
+import win32file
+import time
+import threading
+import sys
+
+# A message may not be larger than this size.
+MSG_SIZE=4096
+
+serverPipe = win32pipe.CreateNamedPipe(
+ "\\\\.\\pipe\\DebugServer",
+ win32pipe.PIPE_ACCESS_DUPLEX,
+ win32pipe.PIPE_TYPE_MESSAGE | win32pipe.PIPE_READMODE_MESSAGE,
+ win32pipe.PIPE_UNLIMITED_INSTANCES,
+ MSG_SIZE,
+ MSG_SIZE,
+ 10 * 1000,
+ None)
+while True:
+ win32pipe.ConnectNamedPipe(serverPipe, None)
+ (ret, data) = win32file.ReadFile(serverPipe, MSG_SIZE)
+ print(data.decode())
+ sys.stdout.flush()
+
+ # The client uses CallNamedPipe to send its message. CallNamedPipe waits
+ # for a reply message. If I send a reply, however, using WriteFile, then
+ # sometimes WriteFile fails with:
+ # pywintypes.error: (232, 'WriteFile', 'The pipe is being closed.')
+ # I can't figure out how to write a strictly correct pipe server, but if
+ # I comment out the WriteFile line, then everything seems to work. I
+ # think the DisconnectNamedPipe call aborts the client's CallNamedPipe
+ # call normally.
+
+ try:
+ win32file.WriteFile(serverPipe, b'OK')
+ except:
+ pass
+ win32pipe.DisconnectNamedPipe(serverPipe)
diff --git a/src/libs/3rdparty/winpty/misc/DumpLines.py b/src/libs/3rdparty/winpty/misc/DumpLines.py
new file mode 100644
index 00000000000..40049961b5c
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/DumpLines.py
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+import sys
+
+for i in range(1, int(sys.argv[1]) + 1):
+ print i, "X" * 78
diff --git a/src/libs/3rdparty/winpty/misc/EnableExtendedFlags.txt b/src/libs/3rdparty/winpty/misc/EnableExtendedFlags.txt
new file mode 100644
index 00000000000..37914dac268
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/EnableExtendedFlags.txt
@@ -0,0 +1,46 @@
+Note regarding ENABLE_EXTENDED_FLAGS (2016-05-30)
+
+There is a complicated interaction between the ENABLE_EXTENDED_FLAGS flag
+and the ENABLE_QUICK_EDIT_MODE and ENABLE_INSERT_MODE flags (presumably for
+backwards compatibility?). I studied the behavior on Windows 7 and Windows
+10, with both the old and new consoles, and I didn't see any differences
+between versions. Here's what I seemed to observe:
+
+ - The console has three flags internally:
+ - QuickEdit
+ - InsertMode
+ - ExtendedFlags
+
+ - SetConsoleMode psuedocode:
+ void SetConsoleMode(..., DWORD mode) {
+ ExtendedFlags = (mode & (ENABLE_EXTENDED_FLAGS
+ | ENABLE_QUICK_EDIT_MODE
+ | ENABLE_INSERT_MODE )) != 0;
+ if (ExtendedFlags) {
+ QuickEdit = (mode & ENABLE_QUICK_EDIT_MODE) != 0;
+ InsertMode = (mode & ENABLE_INSERT_MODE) != 0;
+ }
+ }
+
+ - Setting QuickEdit or InsertMode from the properties dialog GUI does not
+ affect the ExtendedFlags setting -- it simply toggles the one flag.
+
+ - GetConsoleMode psuedocode:
+ GetConsoleMode(..., DWORD *result) {
+ if (ExtendedFlags) {
+ *result |= ENABLE_EXTENDED_FLAGS;
+ if (QuickEdit) { *result |= ENABLE_QUICK_EDIT_MODE; }
+ if (InsertMode) { *result |= ENABLE_INSERT_MODE; }
+ }
+ }
+
+Effectively, the ExtendedFlags flags controls whether the other two flags
+are visible/controlled by the user application. If they aren't visible,
+though, there is no way for the user application to make them visible,
+except by overwriting their values! Calling SetConsoleMode with just
+ENABLE_EXTENDED_FLAGS would clear the extended flags we want to read.
+
+Consequently, if a program temporarily alters the QuickEdit flag (e.g. to
+enable mouse input), it cannot restore the original values of the QuickEdit
+and InsertMode flags, UNLESS every other console program cooperates by
+keeping the ExtendedFlags flag set.
diff --git a/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP437-Consolas.txt b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP437-Consolas.txt
new file mode 100644
index 00000000000..067bd3824a8
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP437-Consolas.txt
@@ -0,0 +1,528 @@
+==================================
+Code Page 437, Consolas font
+==================================
+
+Options: -face "Consolas" -family 0x36
+Chars: A2 A3 2014 3044 30FC 4000
+
+FontSurvey "-face \"Consolas\" -family 0x36"
+
+Windows 7
+---------
+
+Size 1: 1,3 BAD (HHHHHH)
+Size 2: 1,2 BAD (HHHHHH)
+Size 3: 1,3 BAD (HHHHHH)
+Size 4: 2,4 BAD (HHHHHH)
+Size 5: 2,5 BAD (HHHHHH)
+Size 6: 3,6 BAD (HHHHHH)
+Size 7: 3,6 BAD (HHHHHH)
+Size 8: 4,8 BAD (HHHHHH)
+Size 9: 4,9 BAD (HHHHHH)
+Size 10: 5,10 BAD (HHHHHH)
+Size 11: 5,11 BAD (HHHHHH)
+Size 12: 6,12 BAD (HHHHHH)
+Size 13: 6,13 BAD (HHHHHH)
+Size 14: 7,14 BAD (HHHHHH)
+Size 15: 7,15 BAD (HHHHHH)
+Size 16: 8,16 BAD (HHHHHH)
+Size 17: 8,17 BAD (HHHHHH)
+Size 18: 8,18 BAD (HHHHHH)
+Size 19: 9,19 BAD (HHHHHH)
+Size 20: 9,20 BAD (HHHHHH)
+Size 21: 10,22 BAD (HHHHHH)
+Size 22: 10,22 BAD (HHHHHH)
+Size 23: 11,23 BAD (HHHHHH)
+Size 24: 11,24 BAD (HHHHHH)
+Size 25: 12,25 BAD (HHHHHH)
+Size 26: 12,26 BAD (HHHHHH)
+Size 27: 13,27 BAD (HHHHHH)
+Size 28: 13,28 BAD (HHHHHH)
+Size 29: 14,29 BAD (HHHHHH)
+Size 30: 14,30 BAD (HHHHHH)
+Size 31: 15,31 BAD (HHHHHH)
+Size 32: 15,32 BAD (HHHHHH)
+Size 33: 15,33 BAD (HHHHHH)
+Size 34: 16,34 BAD (HHHHHH)
+Size 35: 16,36 BAD (HHHHHH)
+Size 36: 17,36 BAD (HHHHHH)
+Size 37: 17,37 BAD (HHHHHH)
+Size 38: 18,38 BAD (HHHHHH)
+Size 39: 18,39 BAD (HHHHHH)
+Size 40: 19,40 BAD (HHHHHH)
+Size 41: 19,41 BAD (HHHHHH)
+Size 42: 20,42 BAD (HHHHHH)
+Size 43: 20,43 BAD (HHHHHH)
+Size 44: 21,44 BAD (HHHHHH)
+Size 45: 21,45 BAD (HHHHHH)
+Size 46: 22,46 BAD (HHHHHH)
+Size 47: 22,47 BAD (HHHHHH)
+Size 48: 23,48 BAD (HHHHHH)
+Size 49: 23,49 BAD (HHHHHH)
+Size 50: 23,50 BAD (HHHHHH)
+Size 51: 24,51 BAD (HHHHHH)
+Size 52: 24,52 BAD (HHHHHH)
+Size 53: 25,53 BAD (HHHHHH)
+Size 54: 25,54 BAD (HHHHHH)
+Size 55: 26,55 BAD (HHHHHH)
+Size 56: 26,56 BAD (HHHHHH)
+Size 57: 27,57 BAD (HHHHHH)
+Size 58: 27,58 BAD (HHHHHH)
+Size 59: 28,59 BAD (HHHHHH)
+Size 60: 28,60 BAD (HHHHHH)
+Size 61: 29,61 BAD (HHHHHH)
+Size 62: 29,62 BAD (HHHHHH)
+Size 63: 30,64 BAD (HHHHHH)
+Size 64: 30,64 BAD (HHHHHH)
+Size 65: 31,65 BAD (HHHHHH)
+Size 66: 31,66 BAD (HHHHHH)
+Size 67: 31,67 BAD (HHHHHH)
+Size 68: 32,68 BAD (HHHHHH)
+Size 69: 32,69 BAD (HHHHHH)
+Size 70: 33,70 BAD (HHHHHH)
+Size 71: 33,71 BAD (HHHHHH)
+Size 72: 34,72 BAD (HHHHHH)
+Size 73: 34,73 BAD (HHHHHH)
+Size 74: 35,74 BAD (HHHHHH)
+Size 75: 35,75 BAD (HHHHHH)
+Size 76: 36,76 BAD (HHHHHH)
+Size 77: 36,77 BAD (HHHHHH)
+Size 78: 37,78 BAD (HHHHHH)
+Size 79: 37,79 BAD (HHHHHH)
+Size 80: 38,80 BAD (HHHHHH)
+Size 81: 38,81 BAD (HHHHHH)
+Size 82: 39,82 BAD (HHHHHH)
+Size 83: 39,83 BAD (HHHHHH)
+Size 84: 39,84 BAD (HHHHHH)
+Size 85: 40,85 BAD (HHHHHH)
+Size 86: 40,86 BAD (HHHHHH)
+Size 87: 41,87 BAD (HHHHHH)
+Size 88: 41,88 BAD (HHHHHH)
+Size 89: 42,89 BAD (HHHHHH)
+Size 90: 42,90 BAD (HHHHHH)
+Size 91: 43,91 BAD (HHHHHH)
+Size 92: 43,92 BAD (HHHHHH)
+Size 93: 44,93 BAD (HHHHHH)
+Size 94: 44,94 BAD (HHHHHH)
+Size 95: 45,95 BAD (HHHHHH)
+Size 96: 45,96 BAD (HHHHHH)
+Size 97: 46,97 BAD (HHHHHH)
+Size 98: 46,98 BAD (HHHHHH)
+Size 99: 46,99 BAD (HHHHHH)
+Size 100: 47,100 BAD (HHHHHH)
+
+Windows 8
+---------
+
+Size 1: 1,3 BAD (HHHHHH)
+Size 2: 1,2 BAD (HHHHHH)
+Size 3: 1,3 BAD (HHHHHH)
+Size 4: 2,4 BAD (HHHHHH)
+Size 5: 2,5 BAD (HHHHHH)
+Size 6: 3,6 BAD (HHHHHH)
+Size 7: 3,6 BAD (HHHHHH)
+Size 8: 4,8 BAD (HHHHHH)
+Size 9: 4,9 BAD (HHHHHH)
+Size 10: 5,10 BAD (HHHHHH)
+Size 11: 5,11 BAD (HHHHHH)
+Size 12: 6,12 BAD (HHHHHH)
+Size 13: 6,13 BAD (HHHHHH)
+Size 14: 7,14 BAD (HHHHHH)
+Size 15: 7,15 BAD (HHHHHH)
+Size 16: 8,16 BAD (HHHHHH)
+Size 17: 8,17 BAD (HHHHHH)
+Size 18: 8,18 BAD (HHHHHH)
+Size 19: 9,19 BAD (HHHHHH)
+Size 20: 9,20 BAD (HHHHHH)
+Size 21: 10,22 BAD (HHHHHH)
+Size 22: 10,22 BAD (HHHHHH)
+Size 23: 11,23 BAD (HHHHHH)
+Size 24: 11,24 BAD (HHHHHH)
+Size 25: 12,25 BAD (HHHHHH)
+Size 26: 12,26 BAD (HHHHHH)
+Size 27: 13,27 BAD (HHHHHH)
+Size 28: 13,28 BAD (HHHHHH)
+Size 29: 14,29 BAD (HHHHHH)
+Size 30: 14,30 BAD (HHHHHH)
+Size 31: 15,31 BAD (HHHHHH)
+Size 32: 15,32 BAD (HHHHHH)
+Size 33: 15,33 BAD (HHHHHH)
+Size 34: 16,34 BAD (HHHHHH)
+Size 35: 16,36 BAD (HHHHHH)
+Size 36: 17,36 BAD (HHHHHH)
+Size 37: 17,37 BAD (HHHHHH)
+Size 38: 18,38 BAD (HHHHHH)
+Size 39: 18,39 BAD (HHHHHH)
+Size 40: 19,40 BAD (HHHHHH)
+Size 41: 19,41 BAD (HHHHHH)
+Size 42: 20,42 BAD (HHHHHH)
+Size 43: 20,43 BAD (HHHHHH)
+Size 44: 21,44 BAD (HHHHHH)
+Size 45: 21,45 BAD (HHHHHH)
+Size 46: 22,46 BAD (HHHHHH)
+Size 47: 22,47 BAD (HHHHHH)
+Size 48: 23,48 BAD (HHHHHH)
+Size 49: 23,49 BAD (HHHHHH)
+Size 50: 23,50 BAD (HHHHHH)
+Size 51: 24,51 BAD (HHHHHH)
+Size 52: 24,52 BAD (HHHHHH)
+Size 53: 25,53 BAD (HHHHHH)
+Size 54: 25,54 BAD (HHHHHH)
+Size 55: 26,55 BAD (HHHHHH)
+Size 56: 26,56 BAD (HHHHHH)
+Size 57: 27,57 BAD (HHHHHH)
+Size 58: 27,58 BAD (HHHHHH)
+Size 59: 28,59 BAD (HHHHHH)
+Size 60: 28,60 BAD (HHHHHH)
+Size 61: 29,61 BAD (HHHHHH)
+Size 62: 29,62 BAD (HHHHHH)
+Size 63: 30,64 BAD (HHHHHH)
+Size 64: 30,64 BAD (HHHHHH)
+Size 65: 31,65 BAD (HHHHHH)
+Size 66: 31,66 BAD (HHHHHH)
+Size 67: 31,67 BAD (HHHHHH)
+Size 68: 32,68 BAD (HHHHHH)
+Size 69: 32,69 BAD (HHHHHH)
+Size 70: 33,70 BAD (HHHHHH)
+Size 71: 33,71 BAD (HHHHHH)
+Size 72: 34,72 BAD (HHHHHH)
+Size 73: 34,73 BAD (HHHHHH)
+Size 74: 35,74 BAD (HHHHHH)
+Size 75: 35,75 BAD (HHHHHH)
+Size 76: 36,76 BAD (HHHHHH)
+Size 77: 36,77 BAD (HHHHHH)
+Size 78: 37,78 BAD (HHHHHH)
+Size 79: 37,79 BAD (HHHHHH)
+Size 80: 38,80 BAD (HHHHHH)
+Size 81: 38,81 BAD (HHHHHH)
+Size 82: 39,82 BAD (HHHHHH)
+Size 83: 39,83 BAD (HHHHHH)
+Size 84: 39,84 BAD (HHHHHH)
+Size 85: 40,85 BAD (HHHHHH)
+Size 86: 40,86 BAD (HHHHHH)
+Size 87: 41,87 BAD (HHHHHH)
+Size 88: 41,88 BAD (HHHHHH)
+Size 89: 42,89 BAD (HHHHHH)
+Size 90: 42,90 BAD (HHHHHH)
+Size 91: 43,91 BAD (HHHHHH)
+Size 92: 43,92 BAD (HHHHHH)
+Size 93: 44,93 BAD (HHHHHH)
+Size 94: 44,94 BAD (HHHHHH)
+Size 95: 45,95 BAD (HHHHHH)
+Size 96: 45,96 BAD (HHHHHH)
+Size 97: 46,97 BAD (HHHHHH)
+Size 98: 46,98 BAD (HHHHHH)
+Size 99: 46,99 BAD (HHHHHH)
+Size 100: 47,100 BAD (HHHHHH)
+
+Windows 8.1
+-----------
+
+Size 1: 1,3 BAD (HHHHHH)
+Size 2: 1,2 BAD (HHHHHH)
+Size 3: 1,3 BAD (HHHHHH)
+Size 4: 2,4 BAD (HHHHHH)
+Size 5: 2,5 BAD (HHHHHH)
+Size 6: 3,6 BAD (HHHHHH)
+Size 7: 3,6 BAD (HHHHHH)
+Size 8: 4,8 BAD (HHHHHH)
+Size 9: 4,9 BAD (HHHHHH)
+Size 10: 5,10 BAD (HHHHHH)
+Size 11: 5,11 BAD (HHHHHH)
+Size 12: 6,12 BAD (HHHHHH)
+Size 13: 6,13 BAD (HHHHHH)
+Size 14: 7,14 BAD (HHHHHH)
+Size 15: 7,15 BAD (HHHHHH)
+Size 16: 8,16 BAD (HHHHHH)
+Size 17: 8,17 BAD (HHHHHH)
+Size 18: 8,18 BAD (HHHHHH)
+Size 19: 9,19 BAD (HHHHHH)
+Size 20: 9,20 BAD (HHHHHH)
+Size 21: 10,22 BAD (HHHHHH)
+Size 22: 10,22 BAD (HHHHHH)
+Size 23: 11,23 BAD (HHHHHH)
+Size 24: 11,24 BAD (HHHHHH)
+Size 25: 12,25 BAD (HHHHHH)
+Size 26: 12,26 BAD (HHHHHH)
+Size 27: 13,27 BAD (HHHHHH)
+Size 28: 13,28 BAD (HHHHHH)
+Size 29: 14,29 BAD (HHHHHH)
+Size 30: 14,30 BAD (HHHHHH)
+Size 31: 15,31 BAD (HHHHHH)
+Size 32: 15,32 BAD (HHHHHH)
+Size 33: 15,33 BAD (HHHHHH)
+Size 34: 16,34 BAD (HHHHHH)
+Size 35: 16,36 BAD (HHHHHH)
+Size 36: 17,36 BAD (HHHHHH)
+Size 37: 17,37 BAD (HHHHHH)
+Size 38: 18,38 BAD (HHHHHH)
+Size 39: 18,39 BAD (HHHHHH)
+Size 40: 19,40 BAD (HHHHHH)
+Size 41: 19,41 BAD (HHHHHH)
+Size 42: 20,42 BAD (HHHHHH)
+Size 43: 20,43 BAD (HHHHHH)
+Size 44: 21,44 BAD (HHHHHH)
+Size 45: 21,45 BAD (HHHHHH)
+Size 46: 22,46 BAD (HHHHHH)
+Size 47: 22,47 BAD (HHHHHH)
+Size 48: 23,48 BAD (HHHHHH)
+Size 49: 23,49 BAD (HHHHHH)
+Size 50: 23,50 BAD (HHHHHH)
+Size 51: 24,51 BAD (HHHHHH)
+Size 52: 24,52 BAD (HHHHHH)
+Size 53: 25,53 BAD (HHHHHH)
+Size 54: 25,54 BAD (HHHHHH)
+Size 55: 26,55 BAD (HHHHHH)
+Size 56: 26,56 BAD (HHHHHH)
+Size 57: 27,57 BAD (HHHHHH)
+Size 58: 27,58 BAD (HHHHHH)
+Size 59: 28,59 BAD (HHHHHH)
+Size 60: 28,60 BAD (HHHHHH)
+Size 61: 29,61 BAD (HHHHHH)
+Size 62: 29,62 BAD (HHHHHH)
+Size 63: 30,64 BAD (HHHHHH)
+Size 64: 30,64 BAD (HHHHHH)
+Size 65: 31,65 BAD (HHHHHH)
+Size 66: 31,66 BAD (HHHHHH)
+Size 67: 31,67 BAD (HHHHHH)
+Size 68: 32,68 BAD (HHHHHH)
+Size 69: 32,69 BAD (HHHHHH)
+Size 70: 33,70 BAD (HHHHHH)
+Size 71: 33,71 BAD (HHHHHH)
+Size 72: 34,72 BAD (HHHHHH)
+Size 73: 34,73 BAD (HHHHHH)
+Size 74: 35,74 BAD (HHHHHH)
+Size 75: 35,75 BAD (HHHHHH)
+Size 76: 36,76 BAD (HHHHHH)
+Size 77: 36,77 BAD (HHHHHH)
+Size 78: 37,78 BAD (HHHHHH)
+Size 79: 37,79 BAD (HHHHHH)
+Size 80: 38,80 BAD (HHHHHH)
+Size 81: 38,81 BAD (HHHHHH)
+Size 82: 39,82 BAD (HHHHHH)
+Size 83: 39,83 BAD (HHHHHH)
+Size 84: 39,84 BAD (HHHHHH)
+Size 85: 40,85 BAD (HHHHHH)
+Size 86: 40,86 BAD (HHHHHH)
+Size 87: 41,87 BAD (HHHHHH)
+Size 88: 41,88 BAD (HHHHHH)
+Size 89: 42,89 BAD (HHHHHH)
+Size 90: 42,90 BAD (HHHHHH)
+Size 91: 43,91 BAD (HHHHHH)
+Size 92: 43,92 BAD (HHHHHH)
+Size 93: 44,93 BAD (HHHHHH)
+Size 94: 44,94 BAD (HHHHHH)
+Size 95: 45,95 BAD (HHHHHH)
+Size 96: 45,96 BAD (HHHHHH)
+Size 97: 46,97 BAD (HHHHHH)
+Size 98: 46,98 BAD (HHHHHH)
+Size 99: 46,99 BAD (HHHHHH)
+Size 100: 47,100 BAD (HHHHHH)
+
+Windows 10 14342 Old Console
+----------------------------
+
+Size 1: 1,3 BAD (HHHHHH)
+Size 2: 1,2 BAD (HHHHHH)
+Size 3: 1,3 BAD (HHHHHH)
+Size 4: 2,4 BAD (HHHHHH)
+Size 5: 2,5 BAD (HHHHHH)
+Size 6: 3,6 BAD (HHHHHH)
+Size 7: 3,6 BAD (HHHHHH)
+Size 8: 4,8 BAD (HHHHHH)
+Size 9: 4,9 BAD (HHHHHH)
+Size 10: 5,10 BAD (HHHHHH)
+Size 11: 5,11 BAD (HHHHHH)
+Size 12: 6,12 BAD (HHHHHH)
+Size 13: 6,13 BAD (HHHHHH)
+Size 14: 7,14 BAD (HHHHHH)
+Size 15: 7,15 BAD (HHHHHH)
+Size 16: 8,16 BAD (HHHHHH)
+Size 17: 8,17 BAD (HHHHHH)
+Size 18: 8,18 BAD (HHHHHH)
+Size 19: 9,19 BAD (HHHHHH)
+Size 20: 9,20 BAD (HHHHHH)
+Size 21: 10,22 BAD (HHHHHH)
+Size 22: 10,22 BAD (HHHHHH)
+Size 23: 11,23 BAD (HHHHHH)
+Size 24: 11,24 BAD (HHHHHH)
+Size 25: 12,25 BAD (HHHHHH)
+Size 26: 12,26 BAD (HHHHHH)
+Size 27: 13,27 BAD (HHHHHH)
+Size 28: 13,28 BAD (HHHHHH)
+Size 29: 14,29 BAD (HHHHHH)
+Size 30: 14,30 BAD (HHHHHH)
+Size 31: 15,31 BAD (HHHHHH)
+Size 32: 15,32 BAD (HHHHHH)
+Size 33: 15,33 BAD (HHHHHH)
+Size 34: 16,34 BAD (HHHHHH)
+Size 35: 16,36 BAD (HHHHHH)
+Size 36: 17,36 BAD (HHHHHH)
+Size 37: 17,37 BAD (HHHHHH)
+Size 38: 18,38 BAD (HHHHHH)
+Size 39: 18,39 BAD (HHHHHH)
+Size 40: 19,40 BAD (HHHHHH)
+Size 41: 19,41 BAD (HHHHHH)
+Size 42: 20,42 BAD (HHHHHH)
+Size 43: 20,43 BAD (HHHHHH)
+Size 44: 21,44 BAD (HHHHHH)
+Size 45: 21,45 BAD (HHHHHH)
+Size 46: 22,46 BAD (HHHHHH)
+Size 47: 22,47 BAD (HHHHHH)
+Size 48: 23,48 BAD (HHHHHH)
+Size 49: 23,49 BAD (HHHHHH)
+Size 50: 23,50 BAD (HHHHHH)
+Size 51: 24,51 BAD (HHHHHH)
+Size 52: 24,52 BAD (HHHHHH)
+Size 53: 25,53 BAD (HHHHHH)
+Size 54: 25,54 BAD (HHHHHH)
+Size 55: 26,55 BAD (HHHHHH)
+Size 56: 26,56 BAD (HHHHHH)
+Size 57: 27,57 BAD (HHHHHH)
+Size 58: 27,58 BAD (HHHHHH)
+Size 59: 28,59 BAD (HHHHHH)
+Size 60: 28,60 BAD (HHHHHH)
+Size 61: 29,61 BAD (HHHHHH)
+Size 62: 29,62 BAD (HHHHHH)
+Size 63: 30,64 BAD (HHHHHH)
+Size 64: 30,64 BAD (HHHHHH)
+Size 65: 31,65 BAD (HHHHHH)
+Size 66: 31,66 BAD (HHHHHH)
+Size 67: 31,67 BAD (HHHHHH)
+Size 68: 32,68 BAD (HHHHHH)
+Size 69: 32,69 BAD (HHHHHH)
+Size 70: 33,70 BAD (HHHHHH)
+Size 71: 33,71 BAD (HHHHHH)
+Size 72: 34,72 BAD (HHHHHH)
+Size 73: 34,73 BAD (HHHHHH)
+Size 74: 35,74 BAD (HHHHHH)
+Size 75: 35,75 BAD (HHHHHH)
+Size 76: 36,76 BAD (HHHHHH)
+Size 77: 36,77 BAD (HHHHHH)
+Size 78: 37,78 BAD (HHHHHH)
+Size 79: 37,79 BAD (HHHHHH)
+Size 80: 38,80 BAD (HHHHHH)
+Size 81: 38,81 BAD (HHHHHH)
+Size 82: 39,82 BAD (HHHHHH)
+Size 83: 39,83 BAD (HHHHHH)
+Size 84: 39,84 BAD (HHHHHH)
+Size 85: 40,85 BAD (HHHHHH)
+Size 86: 40,86 BAD (HHHHHH)
+Size 87: 41,87 BAD (HHHHHH)
+Size 88: 41,88 BAD (HHHHHH)
+Size 89: 42,89 BAD (HHHHHH)
+Size 90: 42,90 BAD (HHHHHH)
+Size 91: 43,91 BAD (HHHHHH)
+Size 92: 43,92 BAD (HHHHHH)
+Size 93: 44,93 BAD (HHHHHH)
+Size 94: 44,94 BAD (HHHHHH)
+Size 95: 45,95 BAD (HHHHHH)
+Size 96: 45,96 BAD (HHHHHH)
+Size 97: 46,97 BAD (HHHHHH)
+Size 98: 46,98 BAD (HHHHHH)
+Size 99: 46,99 BAD (HHHHHH)
+Size 100: 47,100 BAD (HHHHHH)
+
+Windows 10 14342 New Console
+----------------------------
+
+Size 1: 1,1 BAD (HHHHHH)
+Size 2: 1,2 BAD (HHHHHH)
+Size 3: 1,3 BAD (HHHHHH)
+Size 4: 2,4 BAD (HHHHHH)
+Size 5: 2,5 BAD (HHHHHH)
+Size 6: 3,6 BAD (HHHHHH)
+Size 7: 3,7 BAD (HHHHHH)
+Size 8: 4,8 BAD (HHHHHH)
+Size 9: 4,9 BAD (HHHHHH)
+Size 10: 5,10 BAD (HHHHHH)
+Size 11: 5,11 BAD (HHHHHH)
+Size 12: 6,12 BAD (HHHHHH)
+Size 13: 6,13 BAD (HHHHHH)
+Size 14: 7,14 BAD (HHHHHH)
+Size 15: 7,15 BAD (HHHHHH)
+Size 16: 8,16 BAD (HHHHHH)
+Size 17: 8,17 BAD (HHHHHH)
+Size 18: 8,18 BAD (HHHHHH)
+Size 19: 9,19 BAD (HHHHHH)
+Size 20: 9,20 BAD (HHHHHH)
+Size 21: 10,21 BAD (HHHHHH)
+Size 22: 10,22 BAD (HHHHHH)
+Size 23: 11,23 BAD (HHHHHH)
+Size 24: 11,24 BAD (HHHHHH)
+Size 25: 12,25 BAD (HHHHHH)
+Size 26: 12,26 BAD (HHHHHH)
+Size 27: 13,27 BAD (HHHHHH)
+Size 28: 13,28 BAD (HHHHHH)
+Size 29: 14,29 BAD (HHHHHH)
+Size 30: 14,30 BAD (HHHHHH)
+Size 31: 15,31 BAD (HHHHHH)
+Size 32: 15,32 BAD (HHHHHH)
+Size 33: 15,33 BAD (HHHHHH)
+Size 34: 16,34 BAD (HHHHHH)
+Size 35: 16,35 BAD (HHHHHH)
+Size 36: 17,36 BAD (HHHHHH)
+Size 37: 17,37 BAD (HHHHHH)
+Size 38: 18,38 BAD (HHHHHH)
+Size 39: 18,39 BAD (HHHHHH)
+Size 40: 19,40 BAD (HHHHHH)
+Size 41: 19,41 BAD (HHHHHH)
+Size 42: 20,42 BAD (HHHHHH)
+Size 43: 20,43 BAD (HHHHHH)
+Size 44: 21,44 BAD (HHHHHH)
+Size 45: 21,45 BAD (HHHHHH)
+Size 46: 22,46 BAD (HHHHHH)
+Size 47: 22,47 BAD (HHHHHH)
+Size 48: 23,48 BAD (HHHHHH)
+Size 49: 23,49 BAD (HHHHHH)
+Size 50: 23,50 BAD (HHHHHH)
+Size 51: 24,51 BAD (HHHHHH)
+Size 52: 24,52 BAD (HHHHHH)
+Size 53: 25,53 BAD (HHHHHH)
+Size 54: 25,54 BAD (HHHHHH)
+Size 55: 26,55 BAD (HHHHHH)
+Size 56: 26,56 BAD (HHHHHH)
+Size 57: 27,57 BAD (HHHHHH)
+Size 58: 27,58 BAD (HHHHHH)
+Size 59: 28,59 BAD (HHHHHH)
+Size 60: 28,60 BAD (HHHHHH)
+Size 61: 29,61 BAD (HHHHHH)
+Size 62: 29,62 BAD (HHHHHH)
+Size 63: 30,63 BAD (HHHHHH)
+Size 64: 30,64 BAD (HHHHHH)
+Size 65: 31,65 BAD (HHHHHH)
+Size 66: 31,66 BAD (HHHHHH)
+Size 67: 31,67 BAD (HHHHHH)
+Size 68: 32,68 BAD (HHHHHH)
+Size 69: 32,69 BAD (HHHHHH)
+Size 70: 33,70 BAD (HHHHHH)
+Size 71: 33,71 BAD (HHHHHH)
+Size 72: 34,72 BAD (HHHHHH)
+Size 73: 34,73 BAD (HHHHHH)
+Size 74: 35,74 BAD (HHHHHH)
+Size 75: 35,75 BAD (HHHHHH)
+Size 76: 36,76 BAD (HHHHHH)
+Size 77: 36,77 BAD (HHHHHH)
+Size 78: 37,78 BAD (HHHHHH)
+Size 79: 37,79 BAD (HHHHHH)
+Size 80: 38,80 BAD (HHHHHH)
+Size 81: 38,81 BAD (HHHHHH)
+Size 82: 39,82 BAD (HHHHHH)
+Size 83: 39,83 BAD (HHHHHH)
+Size 84: 39,84 BAD (HHHHHH)
+Size 85: 40,85 BAD (HHHHHH)
+Size 86: 40,86 BAD (HHHHHH)
+Size 87: 41,87 BAD (HHHHHH)
+Size 88: 41,88 BAD (HHHHHH)
+Size 89: 42,89 BAD (HHHHHH)
+Size 90: 42,90 BAD (HHHHHH)
+Size 91: 43,91 BAD (HHHHHH)
+Size 92: 43,92 BAD (HHHHHH)
+Size 93: 44,93 BAD (HHHHHH)
+Size 94: 44,94 BAD (HHHHHH)
+Size 95: 45,95 BAD (HHHHHH)
+Size 96: 45,96 BAD (HHHHHH)
+Size 97: 46,97 BAD (HHHHHH)
+Size 98: 46,98 BAD (HHHHHH)
+Size 99: 46,99 BAD (HHHHHH)
+Size 100: 47,100 BAD (HHHHHH)
diff --git a/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP437-Lucida.txt b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP437-Lucida.txt
new file mode 100644
index 00000000000..0eed93ad989
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP437-Lucida.txt
@@ -0,0 +1,633 @@
+==================================
+Code Page 437, Lucida Console font
+==================================
+
+Options: -face "Lucida Console" -family 0x36
+Chars: A2 A3 2014 3044 30FC 4000
+
+FontSurvey "-face \"Lucida Console\" -family 0x36"
+
+Vista
+-----
+
+Size 1: 1,2 BAD (HHHHHH)
+Size 2: 1,2 BAD (HHHHHH)
+Size 3: 2,3 BAD (HHHHHH)
+Size 4: 2,4 BAD (HHHHHH)
+Size 5: 3,5 BAD (HHHHHH)
+Size 6: 4,6 BAD (HHHHHH)
+Size 7: 4,7 BAD (HHHHHH)
+Size 8: 5,8 BAD (HHHHHH)
+Size 9: 5,9 BAD (HHHHHH)
+Size 10: 6,10 BAD (HHHHHH)
+Size 11: 7,11 BAD (HHHHHH)
+Size 12: 7,12 BAD (HHHHHH)
+Size 13: 8,13 BAD (HHHHHH)
+Size 14: 8,14 BAD (HHHHHH)
+Size 15: 9,15 BAD (HHHHHH)
+Size 16: 10,16 BAD (HHHHHH)
+Size 17: 10,17 BAD (HHHHHH)
+Size 18: 11,18 BAD (HHHHHH)
+Size 19: 11,19 BAD (HHHHHH)
+Size 20: 12,20 BAD (HHHHHH)
+Size 21: 13,21 BAD (HHHHHH)
+Size 22: 13,22 BAD (HHHHHH)
+Size 23: 14,23 BAD (HHHHHH)
+Size 24: 14,24 BAD (HHHHHH)
+Size 25: 15,25 BAD (HHHHHH)
+Size 26: 16,26 BAD (HHHHHH)
+Size 27: 16,27 BAD (HHHHHH)
+Size 28: 17,28 BAD (HHHHHH)
+Size 29: 17,29 BAD (HHHHHH)
+Size 30: 18,30 BAD (HHHHHH)
+Size 31: 19,31 BAD (HHHHHH)
+Size 32: 19,32 BAD (HHHHHH)
+Size 33: 20,33 BAD (HHHHHH)
+Size 34: 20,34 BAD (HHHHHH)
+Size 35: 21,35 BAD (HHHHHH)
+Size 36: 22,36 BAD (HHHHHH)
+Size 37: 22,37 BAD (HHHHHH)
+Size 38: 23,38 BAD (HHHHHH)
+Size 39: 23,39 BAD (HHHHHH)
+Size 40: 24,40 BAD (HHHHHH)
+Size 41: 25,41 BAD (HHHHHH)
+Size 42: 25,42 BAD (HHHHHH)
+Size 43: 26,43 BAD (HHHHHH)
+Size 44: 27,44 BAD (HHHHHH)
+Size 45: 27,45 BAD (HHHHHH)
+Size 46: 28,46 BAD (HHHHHH)
+Size 47: 28,47 BAD (HHHHHH)
+Size 48: 29,48 BAD (HHHHHH)
+Size 49: 30,49 BAD (HHHHHH)
+Size 50: 30,50 BAD (HHHHHH)
+Size 51: 31,51 BAD (HHHHHH)
+Size 52: 31,52 BAD (HHHHHH)
+Size 53: 32,53 BAD (HHHHHH)
+Size 54: 33,54 BAD (HHHHHH)
+Size 55: 33,55 BAD (HHHHHH)
+Size 56: 34,56 BAD (HHHHHH)
+Size 57: 34,57 BAD (HHHHHH)
+Size 58: 35,58 BAD (HHHHHH)
+Size 59: 36,59 BAD (HHHHHH)
+Size 60: 36,60 BAD (HHHHHH)
+Size 61: 37,61 BAD (HHHHHH)
+Size 62: 37,62 BAD (HHHHHH)
+Size 63: 38,63 BAD (HHHHHH)
+Size 64: 39,65 BAD (HHHHHH)
+Size 65: 39,65 BAD (HHHHHH)
+Size 66: 40,66 BAD (HHHHHH)
+Size 67: 40,67 BAD (HHHHHH)
+Size 68: 41,68 BAD (HHHHHH)
+Size 69: 42,69 BAD (HHHHHH)
+Size 70: 42,70 BAD (HHHHHH)
+Size 71: 43,71 BAD (HHHHHH)
+Size 72: 43,72 BAD (HHHHHH)
+Size 73: 44,73 BAD (HHHHHH)
+Size 74: 45,74 BAD (HHHHHH)
+Size 75: 45,75 BAD (HHHHHH)
+Size 76: 46,76 BAD (HHHHHH)
+Size 77: 46,77 BAD (HHHHHH)
+Size 78: 47,78 BAD (HHHHHH)
+Size 79: 48,79 BAD (HHHHHH)
+Size 80: 48,80 BAD (HHHHHH)
+Size 81: 49,81 BAD (HHHHHH)
+Size 82: 49,82 BAD (HHHHHH)
+Size 83: 50,83 BAD (HHHHHH)
+Size 84: 51,84 BAD (HHHHHH)
+Size 85: 51,85 BAD (HHHHHH)
+Size 86: 52,86 BAD (HHHHHH)
+Size 87: 52,87 BAD (HHHHHH)
+Size 88: 53,88 BAD (HHHHHH)
+Size 89: 54,89 BAD (HHHHHH)
+Size 90: 54,90 BAD (HHHHHH)
+Size 91: 55,91 BAD (HHHHHH)
+Size 92: 55,92 BAD (HHHHHH)
+Size 93: 56,93 BAD (HHHHHH)
+Size 94: 57,94 BAD (HHHHHH)
+Size 95: 57,95 BAD (HHHHHH)
+Size 96: 58,96 BAD (HHHHHH)
+Size 97: 58,97 BAD (HHHHHH)
+Size 98: 59,98 BAD (HHHHHH)
+Size 99: 60,99 BAD (HHHHHH)
+Size 100: 60,100 BAD (HHHHHH)
+
+
+Windows 7
+---------
+
+Size 1: 1,2 BAD (HHHHHH)
+Size 2: 1,2 BAD (HHHHHH)
+Size 3: 2,3 BAD (HHHHHH)
+Size 4: 2,4 BAD (HHHHHH)
+Size 5: 3,5 BAD (HHHHHH)
+Size 6: 4,6 BAD (HHHHHH)
+Size 7: 4,7 BAD (HHHHHH)
+Size 8: 5,8 BAD (HHHHHH)
+Size 9: 5,9 BAD (HHHHHH)
+Size 10: 6,10 BAD (HHHHHH)
+Size 11: 7,11 BAD (HHHHHH)
+Size 12: 7,12 BAD (HHHHHH)
+Size 13: 8,13 BAD (HHHHHH)
+Size 14: 8,14 BAD (HHHHHH)
+Size 15: 9,15 BAD (HHHHHH)
+Size 16: 10,16 BAD (HHHHHH)
+Size 17: 10,17 BAD (HHHHHH)
+Size 18: 11,18 BAD (HHHHHH)
+Size 19: 11,19 BAD (HHHHHH)
+Size 20: 12,20 BAD (HHHHHH)
+Size 21: 13,21 BAD (HHHHHH)
+Size 22: 13,22 BAD (HHHHHH)
+Size 23: 14,23 BAD (HHHHHH)
+Size 24: 14,24 BAD (HHHHHH)
+Size 25: 15,25 BAD (HHHHHH)
+Size 26: 16,26 BAD (HHHHHH)
+Size 27: 16,27 BAD (HHHHHH)
+Size 28: 17,28 BAD (HHHHHH)
+Size 29: 17,29 BAD (HHHHHH)
+Size 30: 18,30 BAD (HHHHHH)
+Size 31: 19,31 BAD (HHHHHH)
+Size 32: 19,32 BAD (HHHHHH)
+Size 33: 20,33 BAD (HHHHHH)
+Size 34: 20,34 BAD (HHHHHH)
+Size 35: 21,35 BAD (HHHHHH)
+Size 36: 22,36 BAD (HHHHHH)
+Size 37: 22,37 BAD (HHHHHH)
+Size 38: 23,38 BAD (HHHHHH)
+Size 39: 23,39 BAD (HHHHHH)
+Size 40: 24,40 BAD (HHHHHH)
+Size 41: 25,41 BAD (HHHHHH)
+Size 42: 25,42 BAD (HHHHHH)
+Size 43: 26,43 BAD (HHHHHH)
+Size 44: 27,44 BAD (HHHHHH)
+Size 45: 27,45 BAD (HHHHHH)
+Size 46: 28,46 BAD (HHHHHH)
+Size 47: 28,47 BAD (HHHHHH)
+Size 48: 29,48 BAD (HHHHHH)
+Size 49: 30,49 BAD (HHHHHH)
+Size 50: 30,50 BAD (HHHHHH)
+Size 51: 31,51 BAD (HHHHHH)
+Size 52: 31,52 BAD (HHHHHH)
+Size 53: 32,53 BAD (HHHHHH)
+Size 54: 33,54 BAD (HHHHHH)
+Size 55: 33,55 BAD (HHHHHH)
+Size 56: 34,56 BAD (HHHHHH)
+Size 57: 34,57 BAD (HHHHHH)
+Size 58: 35,58 BAD (HHHHHH)
+Size 59: 36,59 BAD (HHHHHH)
+Size 60: 36,60 BAD (HHHHHH)
+Size 61: 37,61 BAD (HHHHHH)
+Size 62: 37,62 BAD (HHHHHH)
+Size 63: 38,63 BAD (HHHHHH)
+Size 64: 39,65 BAD (HHHHHH)
+Size 65: 39,65 BAD (HHHHHH)
+Size 66: 40,66 BAD (HHHHHH)
+Size 67: 40,67 BAD (HHHHHH)
+Size 68: 41,68 BAD (HHHHHH)
+Size 69: 42,69 BAD (HHHHHH)
+Size 70: 42,70 BAD (HHHHHH)
+Size 71: 43,71 BAD (HHHHHH)
+Size 72: 43,72 BAD (HHHHHH)
+Size 73: 44,73 BAD (HHHHHH)
+Size 74: 45,74 BAD (HHHHHH)
+Size 75: 45,75 BAD (HHHHHH)
+Size 76: 46,76 BAD (HHHHHH)
+Size 77: 46,77 BAD (HHHHHH)
+Size 78: 47,78 BAD (HHHHHH)
+Size 79: 48,79 BAD (HHHHHH)
+Size 80: 48,80 BAD (HHHHHH)
+Size 81: 49,81 BAD (HHHHHH)
+Size 82: 49,82 BAD (HHHHHH)
+Size 83: 50,83 BAD (HHHHHH)
+Size 84: 51,84 BAD (HHHHHH)
+Size 85: 51,85 BAD (HHHHHH)
+Size 86: 52,86 BAD (HHHHHH)
+Size 87: 52,87 BAD (HHHHHH)
+Size 88: 53,88 BAD (HHHHHH)
+Size 89: 54,89 BAD (HHHHHH)
+Size 90: 54,90 BAD (HHHHHH)
+Size 91: 55,91 BAD (HHHHHH)
+Size 92: 55,92 BAD (HHHHHH)
+Size 93: 56,93 BAD (HHHHHH)
+Size 94: 57,94 BAD (HHHHHH)
+Size 95: 57,95 BAD (HHHHHH)
+Size 96: 58,96 BAD (HHHHHH)
+Size 97: 58,97 BAD (HHHHHH)
+Size 98: 59,98 BAD (HHHHHH)
+Size 99: 60,99 BAD (HHHHHH)
+Size 100: 60,100 BAD (HHHHHH)
+
+Windows 8
+---------
+
+Size 1: 1,2 BAD (HHHHHH)
+Size 2: 1,2 BAD (HHHHHH)
+Size 3: 2,3 BAD (HHHHHH)
+Size 4: 2,4 BAD (HHHHHH)
+Size 5: 3,5 BAD (HHHHHH)
+Size 6: 4,6 BAD (HHHHHH)
+Size 7: 4,7 BAD (HHHHHH)
+Size 8: 5,8 BAD (HHHHHH)
+Size 9: 5,9 BAD (HHHHHH)
+Size 10: 6,10 BAD (HHHHHH)
+Size 11: 7,11 BAD (HHHHHH)
+Size 12: 7,12 BAD (HHHHHH)
+Size 13: 8,13 BAD (HHHHHH)
+Size 14: 8,14 BAD (HHHHHH)
+Size 15: 9,15 BAD (HHHHHH)
+Size 16: 10,16 BAD (HHHHHH)
+Size 17: 10,17 BAD (HHHHHH)
+Size 18: 11,18 BAD (HHHHHH)
+Size 19: 11,19 BAD (HHHHHH)
+Size 20: 12,20 BAD (HHHHHH)
+Size 21: 13,21 BAD (HHHHHH)
+Size 22: 13,22 BAD (HHHHHH)
+Size 23: 14,23 BAD (HHHHHH)
+Size 24: 14,24 BAD (HHHHHH)
+Size 25: 15,25 BAD (HHHHHH)
+Size 26: 16,26 BAD (HHHHHH)
+Size 27: 16,27 BAD (HHHHHH)
+Size 28: 17,28 BAD (HHHHHH)
+Size 29: 17,29 BAD (HHHHHH)
+Size 30: 18,30 BAD (HHHHHH)
+Size 31: 19,31 BAD (HHHHHH)
+Size 32: 19,32 BAD (HHHHHH)
+Size 33: 20,33 BAD (HHHHHH)
+Size 34: 20,34 BAD (HHHHHH)
+Size 35: 21,35 BAD (HHHHHH)
+Size 36: 22,36 BAD (HHHHHH)
+Size 37: 22,37 BAD (HHHHHH)
+Size 38: 23,38 BAD (HHHHHH)
+Size 39: 23,39 BAD (HHHHHH)
+Size 40: 24,40 BAD (HHHHHH)
+Size 41: 25,41 BAD (HHHHHH)
+Size 42: 25,42 BAD (HHHHHH)
+Size 43: 26,43 BAD (HHHHHH)
+Size 44: 27,44 BAD (HHHHHH)
+Size 45: 27,45 BAD (HHHHHH)
+Size 46: 28,46 BAD (HHHHHH)
+Size 47: 28,47 BAD (HHHHHH)
+Size 48: 29,48 BAD (HHHHHH)
+Size 49: 30,49 BAD (HHHHHH)
+Size 50: 30,50 BAD (HHHHHH)
+Size 51: 31,51 BAD (HHHHHH)
+Size 52: 31,52 BAD (HHHHHH)
+Size 53: 32,53 BAD (HHHHHH)
+Size 54: 33,54 BAD (HHHHHH)
+Size 55: 33,55 BAD (HHHHHH)
+Size 56: 34,56 BAD (HHHHHH)
+Size 57: 34,57 BAD (HHHHHH)
+Size 58: 35,58 BAD (HHHHHH)
+Size 59: 36,59 BAD (HHHHHH)
+Size 60: 36,60 BAD (HHHHHH)
+Size 61: 37,61 BAD (HHHHHH)
+Size 62: 37,62 BAD (HHHHHH)
+Size 63: 38,63 BAD (HHHHHH)
+Size 64: 39,65 BAD (HHHHHH)
+Size 65: 39,65 BAD (HHHHHH)
+Size 66: 40,66 BAD (HHHHHH)
+Size 67: 40,67 BAD (HHHHHH)
+Size 68: 41,68 BAD (HHHHHH)
+Size 69: 42,69 BAD (HHHHHH)
+Size 70: 42,70 BAD (HHHHHH)
+Size 71: 43,71 BAD (HHHHHH)
+Size 72: 43,72 BAD (HHHHHH)
+Size 73: 44,73 BAD (HHHHHH)
+Size 74: 45,74 BAD (HHHHHH)
+Size 75: 45,75 BAD (HHHHHH)
+Size 76: 46,76 BAD (HHHHHH)
+Size 77: 46,77 BAD (HHHHHH)
+Size 78: 47,78 BAD (HHHHHH)
+Size 79: 48,79 BAD (HHHHHH)
+Size 80: 48,80 BAD (HHHHHH)
+Size 81: 49,81 BAD (HHHHHH)
+Size 82: 49,82 BAD (HHHHHH)
+Size 83: 50,83 BAD (HHHHHH)
+Size 84: 51,84 BAD (HHHHHH)
+Size 85: 51,85 BAD (HHHHHH)
+Size 86: 52,86 BAD (HHHHHH)
+Size 87: 52,87 BAD (HHHHHH)
+Size 88: 53,88 BAD (HHHHHH)
+Size 89: 54,89 BAD (HHHHHH)
+Size 90: 54,90 BAD (HHHHHH)
+Size 91: 55,91 BAD (HHHHHH)
+Size 92: 55,92 BAD (HHHHHH)
+Size 93: 56,93 BAD (HHHHHH)
+Size 94: 57,94 BAD (HHHHHH)
+Size 95: 57,95 BAD (HHHHHH)
+Size 96: 58,96 BAD (HHHHHH)
+Size 97: 58,97 BAD (HHHHHH)
+Size 98: 59,98 BAD (HHHHHH)
+Size 99: 60,99 BAD (HHHHHH)
+Size 100: 60,100 BAD (HHHHHH)
+
+Windows 8.1
+-----------
+
+Size 1: 1,2 BAD (HHHHHH)
+Size 2: 1,2 BAD (HHHHHH)
+Size 3: 2,3 BAD (HHHHHH)
+Size 4: 2,4 BAD (HHHHHH)
+Size 5: 3,5 BAD (HHHHHH)
+Size 6: 4,6 BAD (HHHHHH)
+Size 7: 4,7 BAD (HHHHHH)
+Size 8: 5,8 BAD (HHHHHH)
+Size 9: 5,9 BAD (HHHHHH)
+Size 10: 6,10 BAD (HHHHHH)
+Size 11: 7,11 BAD (HHHHHH)
+Size 12: 7,12 BAD (HHHHHH)
+Size 13: 8,13 BAD (HHHHHH)
+Size 14: 8,14 BAD (HHHHHH)
+Size 15: 9,15 BAD (HHHHHH)
+Size 16: 10,16 BAD (HHHHHH)
+Size 17: 10,17 BAD (HHHHHH)
+Size 18: 11,18 BAD (HHHHHH)
+Size 19: 11,19 BAD (HHHHHH)
+Size 20: 12,20 BAD (HHHHHH)
+Size 21: 13,21 BAD (HHHHHH)
+Size 22: 13,22 BAD (HHHHHH)
+Size 23: 14,23 BAD (HHHHHH)
+Size 24: 14,24 BAD (HHHHHH)
+Size 25: 15,25 BAD (HHHHHH)
+Size 26: 16,26 BAD (HHHHHH)
+Size 27: 16,27 BAD (HHHHHH)
+Size 28: 17,28 BAD (HHHHHH)
+Size 29: 17,29 BAD (HHHHHH)
+Size 30: 18,30 BAD (HHHHHH)
+Size 31: 19,31 BAD (HHHHHH)
+Size 32: 19,32 BAD (HHHHHH)
+Size 33: 20,33 BAD (HHHHHH)
+Size 34: 20,34 BAD (HHHHHH)
+Size 35: 21,35 BAD (HHHHHH)
+Size 36: 22,36 BAD (HHHHHH)
+Size 37: 22,37 BAD (HHHHHH)
+Size 38: 23,38 BAD (HHHHHH)
+Size 39: 23,39 BAD (HHHHHH)
+Size 40: 24,40 BAD (HHHHHH)
+Size 41: 25,41 BAD (HHHHHH)
+Size 42: 25,42 BAD (HHHHHH)
+Size 43: 26,43 BAD (HHHHHH)
+Size 44: 27,44 BAD (HHHHHH)
+Size 45: 27,45 BAD (HHHHHH)
+Size 46: 28,46 BAD (HHHHHH)
+Size 47: 28,47 BAD (HHHHHH)
+Size 48: 29,48 BAD (HHHHHH)
+Size 49: 30,49 BAD (HHHHHH)
+Size 50: 30,50 BAD (HHHHHH)
+Size 51: 31,51 BAD (HHHHHH)
+Size 52: 31,52 BAD (HHHHHH)
+Size 53: 32,53 BAD (HHHHHH)
+Size 54: 33,54 BAD (HHHHHH)
+Size 55: 33,55 BAD (HHHHHH)
+Size 56: 34,56 BAD (HHHHHH)
+Size 57: 34,57 BAD (HHHHHH)
+Size 58: 35,58 BAD (HHHHHH)
+Size 59: 36,59 BAD (HHHHHH)
+Size 60: 36,60 BAD (HHHHHH)
+Size 61: 37,61 BAD (HHHHHH)
+Size 62: 37,62 BAD (HHHHHH)
+Size 63: 38,63 BAD (HHHHHH)
+Size 64: 39,65 BAD (HHHHHH)
+Size 65: 39,65 BAD (HHHHHH)
+Size 66: 40,66 BAD (HHHHHH)
+Size 67: 40,67 BAD (HHHHHH)
+Size 68: 41,68 BAD (HHHHHH)
+Size 69: 42,69 BAD (HHHHHH)
+Size 70: 42,70 BAD (HHHHHH)
+Size 71: 43,71 BAD (HHHHHH)
+Size 72: 43,72 BAD (HHHHHH)
+Size 73: 44,73 BAD (HHHHHH)
+Size 74: 45,74 BAD (HHHHHH)
+Size 75: 45,75 BAD (HHHHHH)
+Size 76: 46,76 BAD (HHHHHH)
+Size 77: 46,77 BAD (HHHHHH)
+Size 78: 47,78 BAD (HHHHHH)
+Size 79: 48,79 BAD (HHHHHH)
+Size 80: 48,80 BAD (HHHHHH)
+Size 81: 49,81 BAD (HHHHHH)
+Size 82: 49,82 BAD (HHHHHH)
+Size 83: 50,83 BAD (HHHHHH)
+Size 84: 51,84 BAD (HHHHHH)
+Size 85: 51,85 BAD (HHHHHH)
+Size 86: 52,86 BAD (HHHHHH)
+Size 87: 52,87 BAD (HHHHHH)
+Size 88: 53,88 BAD (HHHHHH)
+Size 89: 54,89 BAD (HHHHHH)
+Size 90: 54,90 BAD (HHHHHH)
+Size 91: 55,91 BAD (HHHHHH)
+Size 92: 55,92 BAD (HHHHHH)
+Size 93: 56,93 BAD (HHHHHH)
+Size 94: 57,94 BAD (HHHHHH)
+Size 95: 57,95 BAD (HHHHHH)
+Size 96: 58,96 BAD (HHHHHH)
+Size 97: 58,97 BAD (HHHHHH)
+Size 98: 59,98 BAD (HHHHHH)
+Size 99: 60,99 BAD (HHHHHH)
+Size 100: 60,100 BAD (HHHHHH)
+
+Windows 10 14342 Old Console
+----------------------------
+
+Size 1: 1,2 BAD (HHHHHH)
+Size 2: 1,2 BAD (HHHHHH)
+Size 3: 2,3 BAD (HHHHHH)
+Size 4: 2,4 BAD (HHHHHH)
+Size 5: 3,5 BAD (HHHHHH)
+Size 6: 4,6 BAD (HHHHHH)
+Size 7: 4,7 BAD (HHHHHH)
+Size 8: 5,8 BAD (HHHHHH)
+Size 9: 5,9 BAD (HHHHHH)
+Size 10: 6,10 BAD (HHHHHH)
+Size 11: 7,11 BAD (HHHHHH)
+Size 12: 7,12 BAD (HHHHHH)
+Size 13: 8,13 BAD (HHHHHH)
+Size 14: 8,14 BAD (HHHHHH)
+Size 15: 9,15 BAD (HHHHHH)
+Size 16: 10,16 BAD (HHHHHH)
+Size 17: 10,17 BAD (HHHHHH)
+Size 18: 11,18 BAD (HHHHHH)
+Size 19: 11,19 BAD (HHHHHH)
+Size 20: 12,20 BAD (HHHHHH)
+Size 21: 13,21 BAD (HHHHHH)
+Size 22: 13,22 BAD (HHHHHH)
+Size 23: 14,23 BAD (HHHHHH)
+Size 24: 14,24 BAD (HHHHHH)
+Size 25: 15,25 BAD (HHHHHH)
+Size 26: 16,26 BAD (HHHHHH)
+Size 27: 16,27 BAD (HHHHHH)
+Size 28: 17,28 BAD (HHHHHH)
+Size 29: 17,29 BAD (HHHHHH)
+Size 30: 18,30 BAD (HHHHHH)
+Size 31: 19,31 BAD (HHHHHH)
+Size 32: 19,32 BAD (HHHHHH)
+Size 33: 20,33 BAD (HHHHHH)
+Size 34: 20,34 BAD (HHHHHH)
+Size 35: 21,35 BAD (HHHHHH)
+Size 36: 22,36 BAD (HHHHHH)
+Size 37: 22,37 BAD (HHHHHH)
+Size 38: 23,38 BAD (HHHHHH)
+Size 39: 23,39 BAD (HHHHHH)
+Size 40: 24,40 BAD (HHHHHH)
+Size 41: 25,41 BAD (HHHHHH)
+Size 42: 25,42 BAD (HHHHHH)
+Size 43: 26,43 BAD (HHHHHH)
+Size 44: 27,44 BAD (HHHHHH)
+Size 45: 27,45 BAD (HHHHHH)
+Size 46: 28,46 BAD (HHHHHH)
+Size 47: 28,47 BAD (HHHHHH)
+Size 48: 29,48 BAD (HHHHHH)
+Size 49: 30,49 BAD (HHHHHH)
+Size 50: 30,50 BAD (HHHHHH)
+Size 51: 31,51 BAD (HHHHHH)
+Size 52: 31,52 BAD (HHHHHH)
+Size 53: 32,53 BAD (HHHHHH)
+Size 54: 33,54 BAD (HHHHHH)
+Size 55: 33,55 BAD (HHHHHH)
+Size 56: 34,56 BAD (HHHHHH)
+Size 57: 34,57 BAD (HHHHHH)
+Size 58: 35,58 BAD (HHHHHH)
+Size 59: 36,59 BAD (HHHHHH)
+Size 60: 36,60 BAD (HHHHHH)
+Size 61: 37,61 BAD (HHHHHH)
+Size 62: 37,62 BAD (HHHHHH)
+Size 63: 38,63 BAD (HHHHHH)
+Size 64: 39,65 BAD (HHHHHH)
+Size 65: 39,65 BAD (HHHHHH)
+Size 66: 40,66 BAD (HHHHHH)
+Size 67: 40,67 BAD (HHHHHH)
+Size 68: 41,68 BAD (HHHHHH)
+Size 69: 42,69 BAD (HHHHHH)
+Size 70: 42,70 BAD (HHHHHH)
+Size 71: 43,71 BAD (HHHHHH)
+Size 72: 43,72 BAD (HHHHHH)
+Size 73: 44,73 BAD (HHHHHH)
+Size 74: 45,74 BAD (HHHHHH)
+Size 75: 45,75 BAD (HHHHHH)
+Size 76: 46,76 BAD (HHHHHH)
+Size 77: 46,77 BAD (HHHHHH)
+Size 78: 47,78 BAD (HHHHHH)
+Size 79: 48,79 BAD (HHHHHH)
+Size 80: 48,80 BAD (HHHHHH)
+Size 81: 49,81 BAD (HHHHHH)
+Size 82: 49,82 BAD (HHHHHH)
+Size 83: 50,83 BAD (HHHHHH)
+Size 84: 51,84 BAD (HHHHHH)
+Size 85: 51,85 BAD (HHHHHH)
+Size 86: 52,86 BAD (HHHHHH)
+Size 87: 52,87 BAD (HHHHHH)
+Size 88: 53,88 BAD (HHHHHH)
+Size 89: 54,89 BAD (HHHHHH)
+Size 90: 54,90 BAD (HHHHHH)
+Size 91: 55,91 BAD (HHHHHH)
+Size 92: 55,92 BAD (HHHHHH)
+Size 93: 56,93 BAD (HHHHHH)
+Size 94: 57,94 BAD (HHHHHH)
+Size 95: 57,95 BAD (HHHHHH)
+Size 96: 58,96 BAD (HHHHHH)
+Size 97: 58,97 BAD (HHHHHH)
+Size 98: 59,98 BAD (HHHHHH)
+Size 99: 60,99 BAD (HHHHHH)
+Size 100: 60,100 BAD (HHHHHH)
+
+Windows 10 14342 New Console
+----------------------------
+
+Size 1: 1,1 BAD (HHHHHH)
+Size 2: 1,2 BAD (HHHHHH)
+Size 3: 2,3 BAD (HHHHHH)
+Size 4: 2,4 BAD (HHHHHH)
+Size 5: 3,5 BAD (HHHHHH)
+Size 6: 4,6 BAD (HHHHHH)
+Size 7: 4,7 BAD (HHHHHH)
+Size 8: 5,8 BAD (HHHHHH)
+Size 9: 5,9 BAD (HHHHHH)
+Size 10: 6,10 BAD (HHHHHH)
+Size 11: 7,11 BAD (HHHHHH)
+Size 12: 7,12 BAD (HHHHHH)
+Size 13: 8,13 BAD (HHHHHH)
+Size 14: 8,14 BAD (HHHHHH)
+Size 15: 9,15 BAD (HHHHHH)
+Size 16: 10,16 BAD (HHHHHH)
+Size 17: 10,17 BAD (HHHHHH)
+Size 18: 11,18 BAD (HHHHHH)
+Size 19: 11,19 BAD (HHHHHH)
+Size 20: 12,20 BAD (HHHHHH)
+Size 21: 13,21 BAD (HHHHHH)
+Size 22: 13,22 BAD (HHHHHH)
+Size 23: 14,23 BAD (HHHHHH)
+Size 24: 14,24 BAD (HHHHHH)
+Size 25: 15,25 BAD (HHHHHH)
+Size 26: 16,26 BAD (HHHHHH)
+Size 27: 16,27 BAD (HHHHHH)
+Size 28: 17,28 BAD (HHHHHH)
+Size 29: 17,29 BAD (HHHHHH)
+Size 30: 18,30 BAD (HHHHHH)
+Size 31: 19,31 BAD (HHHHHH)
+Size 32: 19,32 BAD (HHHHHH)
+Size 33: 20,33 BAD (HHHHHH)
+Size 34: 20,34 BAD (HHHHHH)
+Size 35: 21,35 BAD (HHHHHH)
+Size 36: 22,36 BAD (HHHHHH)
+Size 37: 22,37 BAD (HHHHHH)
+Size 38: 23,38 BAD (HHHHHH)
+Size 39: 23,39 BAD (HHHHHH)
+Size 40: 24,40 BAD (HHHHHH)
+Size 41: 25,41 BAD (HHHHHH)
+Size 42: 25,42 BAD (HHHHHH)
+Size 43: 26,43 BAD (HHHHHH)
+Size 44: 27,44 BAD (HHHHHH)
+Size 45: 27,45 BAD (HHHHHH)
+Size 46: 28,46 BAD (HHHHHH)
+Size 47: 28,47 BAD (HHHHHH)
+Size 48: 29,48 BAD (HHHHHH)
+Size 49: 30,49 BAD (HHHHHH)
+Size 50: 30,50 BAD (HHHHHH)
+Size 51: 31,51 BAD (HHHHHH)
+Size 52: 31,52 BAD (HHHHHH)
+Size 53: 32,53 BAD (HHHHHH)
+Size 54: 33,54 BAD (HHHHHH)
+Size 55: 33,55 BAD (HHHHHH)
+Size 56: 34,56 BAD (HHHHHH)
+Size 57: 34,57 BAD (HHHHHH)
+Size 58: 35,58 BAD (HHHHHH)
+Size 59: 36,59 BAD (HHHHHH)
+Size 60: 36,60 BAD (HHHHHH)
+Size 61: 37,61 BAD (HHHHHH)
+Size 62: 37,62 BAD (HHHHHH)
+Size 63: 38,63 BAD (HHHHHH)
+Size 64: 39,64 BAD (HHHHHH)
+Size 65: 39,65 BAD (HHHHHH)
+Size 66: 40,66 BAD (HHHHHH)
+Size 67: 40,67 BAD (HHHHHH)
+Size 68: 41,68 BAD (HHHHHH)
+Size 69: 42,69 BAD (HHHHHH)
+Size 70: 42,70 BAD (HHHHHH)
+Size 71: 43,71 BAD (HHHHHH)
+Size 72: 43,72 BAD (HHHHHH)
+Size 73: 44,73 BAD (HHHHHH)
+Size 74: 45,74 BAD (HHHHHH)
+Size 75: 45,75 BAD (HHHHHH)
+Size 76: 46,76 BAD (HHHHHH)
+Size 77: 46,77 BAD (HHHHHH)
+Size 78: 47,78 BAD (HHHHHH)
+Size 79: 48,79 BAD (HHHHHH)
+Size 80: 48,80 BAD (HHHHHH)
+Size 81: 49,81 BAD (HHHHHH)
+Size 82: 49,82 BAD (HHHHHH)
+Size 83: 50,83 BAD (HHHHHH)
+Size 84: 51,84 BAD (HHHHHH)
+Size 85: 51,85 BAD (HHHHHH)
+Size 86: 52,86 BAD (HHHHHH)
+Size 87: 52,87 BAD (HHHHHH)
+Size 88: 53,88 BAD (HHHHHH)
+Size 89: 54,89 BAD (HHHHHH)
+Size 90: 54,90 BAD (HHHHHH)
+Size 91: 55,91 BAD (HHHHHH)
+Size 92: 55,92 BAD (HHHHHH)
+Size 93: 56,93 BAD (HHHHHH)
+Size 94: 57,94 BAD (HHHHHH)
+Size 95: 57,95 BAD (HHHHHH)
+Size 96: 58,96 BAD (HHHHHH)
+Size 97: 58,97 BAD (HHHHHH)
+Size 98: 59,98 BAD (HHHHHH)
+Size 99: 60,99 BAD (HHHHHH)
+Size 100: 60,100 BAD (HHHHHH)
diff --git a/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP932.txt b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP932.txt
new file mode 100644
index 00000000000..ed3637eac1f
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP932.txt
@@ -0,0 +1,630 @@
+=======================================
+Code Page 932, Japanese, MS Gothic font
+=======================================
+
+Options: -face-gothic -family 0x36
+Chars: A2 A3 2014 3044 30FC 4000
+
+Vista
+-----
+
+Size 1: 1,2 OK (HHHFFF)
+Size 2: 1,2 OK (HHHFFF)
+Size 3: 2,3 BAD (FFFFHH)
+Size 4: 2,4 OK (HHHFFF)
+Size 5: 3,5 OK (HHHFFF)
+Size 6: 3,6 OK (HHHFFF)
+Size 7: 4,7 OK (HHHFFF)
+Size 8: 4,8 OK (HHHFFF)
+Size 9: 5,9 OK (HHHFFF)
+Size 10: 5,10 OK (HHHFFF)
+Size 11: 6,11 OK (HHHFFF)
+Size 12: 6,12 OK (HHHFFF)
+Size 13: 7,13 OK (HHHFFF)
+Size 14: 7,14 BAD (HHHFHH)
+Size 15: 8,15 OK (HHHFFF)
+Size 16: 8,16 BAD (HHHFHH)
+Size 17: 9,17 OK (HHHFFF)
+Size 18: 9,18 BAD (HHHFHH)
+Size 19: 10,19 OK (HHHFFF)
+Size 20: 10,20 BAD (HHHFHH)
+Size 21: 11,21 OK (HHHFFF)
+Size 22: 11,22 BAD (HHHFHH)
+Size 23: 12,23 BAD (HHHFHH)
+Size 24: 12,24 BAD (HHHFHH)
+Size 25: 13,25 BAD (HHHFHH)
+Size 26: 13,26 BAD (HHHFHH)
+Size 27: 14,27 BAD (HHHFHH)
+Size 28: 14,28 BAD (HHHFHH)
+Size 29: 15,29 BAD (HHHFHH)
+Size 30: 15,30 BAD (HHHFHH)
+Size 31: 16,31 BAD (HHHFHH)
+Size 32: 16,33 BAD (HHHFHH)
+Size 33: 17,33 BAD (HHHFHH)
+Size 34: 17,34 BAD (HHHFHH)
+Size 35: 18,35 BAD (HHHFHH)
+Size 36: 18,36 BAD (HHHFHH)
+Size 37: 19,37 BAD (HHHFHH)
+Size 38: 19,38 BAD (HHHFHH)
+Size 39: 20,39 BAD (HHHFHH)
+Size 40: 20,40 BAD (HHHFHH)
+Size 41: 21,41 BAD (HHHFHH)
+Size 42: 21,42 BAD (HHHFHH)
+Size 43: 22,43 BAD (HHHFHH)
+Size 44: 22,44 BAD (HHHFHH)
+Size 45: 23,45 BAD (HHHFHH)
+Size 46: 23,46 BAD (HHHFHH)
+Size 47: 24,47 BAD (HHHFHH)
+Size 48: 24,48 BAD (HHHFHH)
+Size 49: 25,49 BAD (HHHFHH)
+Size 50: 25,50 BAD (HHHFHH)
+Size 51: 26,51 BAD (HHHFHH)
+Size 52: 26,52 BAD (HHHFHH)
+Size 53: 27,53 BAD (HHHFHH)
+Size 54: 27,54 BAD (HHHFHH)
+Size 55: 28,55 BAD (HHHFHH)
+Size 56: 28,56 BAD (HHHFHH)
+Size 57: 29,57 BAD (HHHFHH)
+Size 58: 29,58 BAD (HHHFHH)
+Size 59: 30,59 BAD (HHHFHH)
+Size 60: 30,60 BAD (HHHFHH)
+Size 61: 31,61 BAD (HHHFHH)
+Size 62: 31,62 BAD (HHHFHH)
+Size 63: 32,63 BAD (HHHFHH)
+Size 64: 32,64 BAD (HHHFHH)
+Size 65: 33,65 BAD (HHHFHH)
+Size 66: 33,66 BAD (HHHFHH)
+Size 67: 34,67 BAD (HHHFHH)
+Size 68: 34,68 BAD (HHHFHH)
+Size 69: 35,69 BAD (HHHFHH)
+Size 70: 35,70 BAD (HHHFHH)
+Size 71: 36,71 BAD (HHHFHH)
+Size 72: 36,72 BAD (HHHFHH)
+Size 73: 37,73 BAD (HHHFHH)
+Size 74: 37,74 BAD (HHHFHH)
+Size 75: 38,75 BAD (HHHFHH)
+Size 76: 38,76 BAD (HHHFHH)
+Size 77: 39,77 BAD (HHHFHH)
+Size 78: 39,78 BAD (HHHFHH)
+Size 79: 40,79 BAD (HHHFHH)
+Size 80: 40,80 BAD (HHHFHH)
+Size 81: 41,81 BAD (HHHFHH)
+Size 82: 41,82 BAD (HHHFHH)
+Size 83: 42,83 BAD (HHHFHH)
+Size 84: 42,84 BAD (HHHFHH)
+Size 85: 43,85 BAD (HHHFHH)
+Size 86: 43,86 BAD (HHHFHH)
+Size 87: 44,87 BAD (HHHFHH)
+Size 88: 44,88 BAD (HHHFHH)
+Size 89: 45,89 BAD (HHHFHH)
+Size 90: 45,90 BAD (HHHFHH)
+Size 91: 46,91 BAD (HHHFHH)
+Size 92: 46,92 BAD (HHHFHH)
+Size 93: 47,93 BAD (HHHFHH)
+Size 94: 47,94 BAD (HHHFHH)
+Size 95: 48,95 BAD (HHHFHH)
+Size 96: 48,97 BAD (HHHFHH)
+Size 97: 49,97 BAD (HHHFHH)
+Size 98: 49,98 BAD (HHHFHH)
+Size 99: 50,99 BAD (HHHFHH)
+Size 100: 50,100 BAD (HHHFHH)
+
+Windows 7
+---------
+
+Size 1: 1,2 OK (HHHFFF)
+Size 2: 1,2 OK (HHHFFF)
+Size 3: 2,3 BAD (FFFFHH)
+Size 4: 2,4 OK (HHHFFF)
+Size 5: 3,5 OK (HHHFFF)
+Size 6: 3,6 OK (HHHFFF)
+Size 7: 4,7 OK (HHHFFF)
+Size 8: 4,8 OK (HHHFFF)
+Size 9: 5,9 OK (HHHFFF)
+Size 10: 5,10 OK (HHHFFF)
+Size 11: 6,11 OK (HHHFFF)
+Size 12: 6,12 OK (HHHFFF)
+Size 13: 7,13 OK (HHHFFF)
+Size 14: 7,14 BAD (FFFFFF)
+Size 15: 8,15 OK (HHHFFF)
+Size 16: 8,16 BAD (FFFFFF)
+Size 17: 9,17 OK (HHHFFF)
+Size 18: 9,18 BAD (FFFFFF)
+Size 19: 10,19 OK (HHHFFF)
+Size 20: 10,20 BAD (FFFFFF)
+Size 21: 11,21 OK (HHHFFF)
+Size 22: 11,22 BAD (FFFFFF)
+Size 23: 12,23 BAD (FFFFFF)
+Size 24: 12,24 BAD (FFFFFF)
+Size 25: 13,25 BAD (FFFFFF)
+Size 26: 13,26 BAD (FFFFFF)
+Size 27: 14,27 BAD (FFFFFF)
+Size 28: 14,28 BAD (FFFFFF)
+Size 29: 15,29 BAD (FFFFFF)
+Size 30: 15,30 BAD (FFFFFF)
+Size 31: 16,31 BAD (FFFFFF)
+Size 32: 16,33 BAD (FFFFFF)
+Size 33: 17,33 BAD (FFFFFF)
+Size 34: 17,34 BAD (FFFFFF)
+Size 35: 18,35 BAD (FFFFFF)
+Size 36: 18,36 BAD (FFFFFF)
+Size 37: 19,37 BAD (FFFFFF)
+Size 38: 19,38 BAD (FFFFFF)
+Size 39: 20,39 BAD (FFFFFF)
+Size 40: 20,40 BAD (FFFFFF)
+Size 41: 21,41 BAD (FFFFFF)
+Size 42: 21,42 BAD (FFFFFF)
+Size 43: 22,43 BAD (FFFFFF)
+Size 44: 22,44 BAD (FFFFFF)
+Size 45: 23,45 BAD (FFFFFF)
+Size 46: 23,46 BAD (FFFFFF)
+Size 47: 24,47 BAD (FFFFFF)
+Size 48: 24,48 BAD (FFFFFF)
+Size 49: 25,49 BAD (FFFFFF)
+Size 50: 25,50 BAD (FFFFFF)
+Size 51: 26,51 BAD (FFFFFF)
+Size 52: 26,52 BAD (FFFFFF)
+Size 53: 27,53 BAD (FFFFFF)
+Size 54: 27,54 BAD (FFFFFF)
+Size 55: 28,55 BAD (FFFFFF)
+Size 56: 28,56 BAD (FFFFFF)
+Size 57: 29,57 BAD (FFFFFF)
+Size 58: 29,58 BAD (FFFFFF)
+Size 59: 30,59 BAD (FFFFFF)
+Size 60: 30,60 BAD (FFFFFF)
+Size 61: 31,61 BAD (FFFFFF)
+Size 62: 31,62 BAD (FFFFFF)
+Size 63: 32,63 BAD (FFFFFF)
+Size 64: 32,64 BAD (FFFFFF)
+Size 65: 33,65 BAD (FFFFFF)
+Size 66: 33,66 BAD (FFFFFF)
+Size 67: 34,67 BAD (FFFFFF)
+Size 68: 34,68 BAD (FFFFFF)
+Size 69: 35,69 BAD (FFFFFF)
+Size 70: 35,70 BAD (FFFFFF)
+Size 71: 36,71 BAD (FFFFFF)
+Size 72: 36,72 BAD (FFFFFF)
+Size 73: 37,73 BAD (FFFFFF)
+Size 74: 37,74 BAD (FFFFFF)
+Size 75: 38,75 BAD (FFFFFF)
+Size 76: 38,76 BAD (FFFFFF)
+Size 77: 39,77 BAD (FFFFFF)
+Size 78: 39,78 BAD (FFFFFF)
+Size 79: 40,79 BAD (FFFFFF)
+Size 80: 40,80 BAD (FFFFFF)
+Size 81: 41,81 BAD (FFFFFF)
+Size 82: 41,82 BAD (FFFFFF)
+Size 83: 42,83 BAD (FFFFFF)
+Size 84: 42,84 BAD (FFFFFF)
+Size 85: 43,85 BAD (FFFFFF)
+Size 86: 43,86 BAD (FFFFFF)
+Size 87: 44,87 BAD (FFFFFF)
+Size 88: 44,88 BAD (FFFFFF)
+Size 89: 45,89 BAD (FFFFFF)
+Size 90: 45,90 BAD (FFFFFF)
+Size 91: 46,91 BAD (FFFFFF)
+Size 92: 46,92 BAD (FFFFFF)
+Size 93: 47,93 BAD (FFFFFF)
+Size 94: 47,94 BAD (FFFFFF)
+Size 95: 48,95 BAD (FFFFFF)
+Size 96: 48,97 BAD (FFFFFF)
+Size 97: 49,97 BAD (FFFFFF)
+Size 98: 49,98 BAD (FFFFFF)
+Size 99: 50,99 BAD (FFFFFF)
+Size 100: 50,100 BAD (FFFFFF)
+
+Windows 8
+---------
+
+Size 1: 1,2 BAD (FFFFHH)
+Size 2: 1,2 BAD (FFFFHH)
+Size 3: 2,3 BAD (FFFFFF)
+Size 4: 2,4 BAD (FFFFHH)
+Size 5: 3,5 BAD (FFFFFF)
+Size 6: 3,6 BAD (FFFFHH)
+Size 7: 4,7 BAD (FFFFFF)
+Size 8: 4,8 BAD (FFFFHH)
+Size 9: 5,9 BAD (FFFFFF)
+Size 10: 5,10 BAD (FFFFHH)
+Size 11: 6,11 BAD (FFFFFF)
+Size 12: 6,12 BAD (FFFFHH)
+Size 13: 7,13 BAD (FFFFFF)
+Size 14: 7,14 BAD (FFFFHH)
+Size 15: 8,15 BAD (FFFFFF)
+Size 16: 8,16 BAD (FFFFHH)
+Size 17: 9,17 BAD (FFFFFF)
+Size 18: 9,18 BAD (FFFFHH)
+Size 19: 10,19 BAD (FFFFFF)
+Size 20: 10,20 BAD (FFFFFF)
+Size 21: 11,21 BAD (FFFFFF)
+Size 22: 11,22 BAD (FFFFFF)
+Size 23: 12,23 BAD (FFFFFF)
+Size 24: 12,24 BAD (FFFFFF)
+Size 25: 13,25 BAD (FFFFFF)
+Size 26: 13,26 BAD (FFFFFF)
+Size 27: 14,27 BAD (FFFFFF)
+Size 28: 14,28 BAD (FFFFFF)
+Size 29: 15,29 BAD (FFFFFF)
+Size 30: 15,30 BAD (FFFFFF)
+Size 31: 16,31 BAD (FFFFFF)
+Size 32: 16,33 BAD (FFFFFF)
+Size 33: 17,33 BAD (FFFFFF)
+Size 34: 17,34 BAD (FFFFFF)
+Size 35: 18,35 BAD (FFFFFF)
+Size 36: 18,36 BAD (FFFFFF)
+Size 37: 19,37 BAD (FFFFFF)
+Size 38: 19,38 BAD (FFFFFF)
+Size 39: 20,39 BAD (FFFFFF)
+Size 40: 20,40 BAD (FFFFFF)
+Size 41: 21,41 BAD (FFFFFF)
+Size 42: 21,42 BAD (FFFFFF)
+Size 43: 22,43 BAD (FFFFFF)
+Size 44: 22,44 BAD (FFFFFF)
+Size 45: 23,45 BAD (FFFFFF)
+Size 46: 23,46 BAD (FFFFFF)
+Size 47: 24,47 BAD (FFFFFF)
+Size 48: 24,48 BAD (FFFFFF)
+Size 49: 25,49 BAD (FFFFFF)
+Size 50: 25,50 BAD (FFFFFF)
+Size 51: 26,51 BAD (FFFFFF)
+Size 52: 26,52 BAD (FFFFFF)
+Size 53: 27,53 BAD (FFFFFF)
+Size 54: 27,54 BAD (FFFFFF)
+Size 55: 28,55 BAD (FFFFFF)
+Size 56: 28,56 BAD (FFFFFF)
+Size 57: 29,57 BAD (FFFFFF)
+Size 58: 29,58 BAD (FFFFFF)
+Size 59: 30,59 BAD (FFFFFF)
+Size 60: 30,60 BAD (FFFFFF)
+Size 61: 31,61 BAD (FFFFFF)
+Size 62: 31,62 BAD (FFFFFF)
+Size 63: 32,63 BAD (FFFFFF)
+Size 64: 32,64 BAD (FFFFFF)
+Size 65: 33,65 BAD (FFFFFF)
+Size 66: 33,66 BAD (FFFFFF)
+Size 67: 34,67 BAD (FFFFFF)
+Size 68: 34,68 BAD (FFFFFF)
+Size 69: 35,69 BAD (FFFFFF)
+Size 70: 35,70 BAD (FFFFFF)
+Size 71: 36,71 BAD (FFFFFF)
+Size 72: 36,72 BAD (FFFFFF)
+Size 73: 37,73 BAD (FFFFFF)
+Size 74: 37,74 BAD (FFFFFF)
+Size 75: 38,75 BAD (FFFFFF)
+Size 76: 38,76 BAD (FFFFFF)
+Size 77: 39,77 BAD (FFFFFF)
+Size 78: 39,78 BAD (FFFFFF)
+Size 79: 40,79 BAD (FFFFFF)
+Size 80: 40,80 BAD (FFFFFF)
+Size 81: 41,81 BAD (FFFFFF)
+Size 82: 41,82 BAD (FFFFFF)
+Size 83: 42,83 BAD (FFFFFF)
+Size 84: 42,84 BAD (FFFFFF)
+Size 85: 43,85 BAD (FFFFFF)
+Size 86: 43,86 BAD (FFFFFF)
+Size 87: 44,87 BAD (FFFFFF)
+Size 88: 44,88 BAD (FFFFFF)
+Size 89: 45,89 BAD (FFFFFF)
+Size 90: 45,90 BAD (FFFFFF)
+Size 91: 46,91 BAD (FFFFFF)
+Size 92: 46,92 BAD (FFFFFF)
+Size 93: 47,93 BAD (FFFFFF)
+Size 94: 47,94 BAD (FFFFFF)
+Size 95: 48,95 BAD (FFFFFF)
+Size 96: 48,97 BAD (FFFFFF)
+Size 97: 49,97 BAD (FFFFFF)
+Size 98: 49,98 BAD (FFFFFF)
+Size 99: 50,99 BAD (FFFFFF)
+Size 100: 50,100 BAD (FFFFFF)
+
+Windows 8.1
+-----------
+
+Size 1: 1,2 BAD (FFFFHH)
+Size 2: 1,2 BAD (FFFFHH)
+Size 3: 2,3 BAD (FFFFFF)
+Size 4: 2,4 BAD (FFFFHH)
+Size 5: 3,5 BAD (FFFFFF)
+Size 6: 3,6 BAD (FFFFHH)
+Size 7: 4,7 BAD (FFFFFF)
+Size 8: 4,8 BAD (FFFFHH)
+Size 9: 5,9 BAD (FFFFFF)
+Size 10: 5,10 BAD (FFFFHH)
+Size 11: 6,11 BAD (FFFFFF)
+Size 12: 6,12 BAD (FFFFHH)
+Size 13: 7,13 BAD (FFFFFF)
+Size 14: 7,14 BAD (FFFFHH)
+Size 15: 8,15 BAD (FFFFFF)
+Size 16: 8,16 BAD (FFFFHH)
+Size 17: 9,17 BAD (FFFFFF)
+Size 18: 9,18 BAD (FFFFHH)
+Size 19: 10,19 BAD (FFFFFF)
+Size 20: 10,20 BAD (FFFFFF)
+Size 21: 11,21 BAD (FFFFFF)
+Size 22: 11,22 BAD (FFFFFF)
+Size 23: 12,23 BAD (FFFFFF)
+Size 24: 12,24 BAD (FFFFFF)
+Size 25: 13,25 BAD (FFFFFF)
+Size 26: 13,26 BAD (FFFFFF)
+Size 27: 14,27 BAD (FFFFFF)
+Size 28: 14,28 BAD (FFFFFF)
+Size 29: 15,29 BAD (FFFFFF)
+Size 30: 15,30 BAD (FFFFFF)
+Size 31: 16,31 BAD (FFFFFF)
+Size 32: 16,33 BAD (FFFFFF)
+Size 33: 17,33 BAD (FFFFFF)
+Size 34: 17,34 BAD (FFFFFF)
+Size 35: 18,35 BAD (FFFFFF)
+Size 36: 18,36 BAD (FFFFFF)
+Size 37: 19,37 BAD (FFFFFF)
+Size 38: 19,38 BAD (FFFFFF)
+Size 39: 20,39 BAD (FFFFFF)
+Size 40: 20,40 BAD (FFFFFF)
+Size 41: 21,41 BAD (FFFFFF)
+Size 42: 21,42 BAD (FFFFFF)
+Size 43: 22,43 BAD (FFFFFF)
+Size 44: 22,44 BAD (FFFFFF)
+Size 45: 23,45 BAD (FFFFFF)
+Size 46: 23,46 BAD (FFFFFF)
+Size 47: 24,47 BAD (FFFFFF)
+Size 48: 24,48 BAD (FFFFFF)
+Size 49: 25,49 BAD (FFFFFF)
+Size 50: 25,50 BAD (FFFFFF)
+Size 51: 26,51 BAD (FFFFFF)
+Size 52: 26,52 BAD (FFFFFF)
+Size 53: 27,53 BAD (FFFFFF)
+Size 54: 27,54 BAD (FFFFFF)
+Size 55: 28,55 BAD (FFFFFF)
+Size 56: 28,56 BAD (FFFFFF)
+Size 57: 29,57 BAD (FFFFFF)
+Size 58: 29,58 BAD (FFFFFF)
+Size 59: 30,59 BAD (FFFFFF)
+Size 60: 30,60 BAD (FFFFFF)
+Size 61: 31,61 BAD (FFFFFF)
+Size 62: 31,62 BAD (FFFFFF)
+Size 63: 32,63 BAD (FFFFFF)
+Size 64: 32,64 BAD (FFFFFF)
+Size 65: 33,65 BAD (FFFFFF)
+Size 66: 33,66 BAD (FFFFFF)
+Size 67: 34,67 BAD (FFFFFF)
+Size 68: 34,68 BAD (FFFFFF)
+Size 69: 35,69 BAD (FFFFFF)
+Size 70: 35,70 BAD (FFFFFF)
+Size 71: 36,71 BAD (FFFFFF)
+Size 72: 36,72 BAD (FFFFFF)
+Size 73: 37,73 BAD (FFFFFF)
+Size 74: 37,74 BAD (FFFFFF)
+Size 75: 38,75 BAD (FFFFFF)
+Size 76: 38,76 BAD (FFFFFF)
+Size 77: 39,77 BAD (FFFFFF)
+Size 78: 39,78 BAD (FFFFFF)
+Size 79: 40,79 BAD (FFFFFF)
+Size 80: 40,80 BAD (FFFFFF)
+Size 81: 41,81 BAD (FFFFFF)
+Size 82: 41,82 BAD (FFFFFF)
+Size 83: 42,83 BAD (FFFFFF)
+Size 84: 42,84 BAD (FFFFFF)
+Size 85: 43,85 BAD (FFFFFF)
+Size 86: 43,86 BAD (FFFFFF)
+Size 87: 44,87 BAD (FFFFFF)
+Size 88: 44,88 BAD (FFFFFF)
+Size 89: 45,89 BAD (FFFFFF)
+Size 90: 45,90 BAD (FFFFFF)
+Size 91: 46,91 BAD (FFFFFF)
+Size 92: 46,92 BAD (FFFFFF)
+Size 93: 47,93 BAD (FFFFFF)
+Size 94: 47,94 BAD (FFFFFF)
+Size 95: 48,95 BAD (FFFFFF)
+Size 96: 48,97 BAD (FFFFFF)
+Size 97: 49,97 BAD (FFFFFF)
+Size 98: 49,98 BAD (FFFFFF)
+Size 99: 50,99 BAD (FFFFFF)
+Size 100: 50,100 BAD (FFFFFF)
+
+Windows 10 14342 Old Console
+----------------------------
+
+Size 1: 1,2 BAD (FFFFHH)
+Size 2: 1,2 BAD (FFFFHH)
+Size 3: 2,3 BAD (FFFFFF)
+Size 4: 2,4 BAD (FFFFHH)
+Size 5: 3,5 BAD (FFFFFF)
+Size 6: 3,6 BAD (FFFFHH)
+Size 7: 4,7 BAD (FFFFFF)
+Size 8: 4,8 BAD (FFFFHH)
+Size 9: 5,9 BAD (FFFFFF)
+Size 10: 5,10 BAD (FFFFHH)
+Size 11: 6,11 BAD (FFFFFF)
+Size 12: 6,12 BAD (FFFFHH)
+Size 13: 7,13 BAD (FFFFFF)
+Size 14: 7,14 BAD (FFFFHH)
+Size 15: 8,15 BAD (FFFFFF)
+Size 16: 8,16 BAD (FFFFHH)
+Size 17: 9,17 BAD (FFFFFF)
+Size 18: 9,18 BAD (FFFFHH)
+Size 19: 10,19 BAD (FFFFFF)
+Size 20: 10,20 BAD (FFFFFF)
+Size 21: 11,21 BAD (FFFFFF)
+Size 22: 11,22 BAD (FFFFFF)
+Size 23: 12,23 BAD (FFFFFF)
+Size 24: 12,24 BAD (FFFFFF)
+Size 25: 13,25 BAD (FFFFFF)
+Size 26: 13,26 BAD (FFFFFF)
+Size 27: 14,27 BAD (FFFFFF)
+Size 28: 14,28 BAD (FFFFFF)
+Size 29: 15,29 BAD (FFFFFF)
+Size 30: 15,30 BAD (FFFFFF)
+Size 31: 16,31 BAD (FFFFFF)
+Size 32: 16,33 BAD (FFFFFF)
+Size 33: 17,33 BAD (FFFFFF)
+Size 34: 17,34 BAD (FFFFFF)
+Size 35: 18,35 BAD (FFFFFF)
+Size 36: 18,36 BAD (FFFFFF)
+Size 37: 19,37 BAD (FFFFFF)
+Size 38: 19,38 BAD (FFFFFF)
+Size 39: 20,39 BAD (FFFFFF)
+Size 40: 20,40 BAD (FFFFFF)
+Size 41: 21,41 BAD (FFFFFF)
+Size 42: 21,42 BAD (FFFFFF)
+Size 43: 22,43 BAD (FFFFFF)
+Size 44: 22,44 BAD (FFFFFF)
+Size 45: 23,45 BAD (FFFFFF)
+Size 46: 23,46 BAD (FFFFFF)
+Size 47: 24,47 BAD (FFFFFF)
+Size 48: 24,48 BAD (FFFFFF)
+Size 49: 25,49 BAD (FFFFFF)
+Size 50: 25,50 BAD (FFFFFF)
+Size 51: 26,51 BAD (FFFFFF)
+Size 52: 26,52 BAD (FFFFFF)
+Size 53: 27,53 BAD (FFFFFF)
+Size 54: 27,54 BAD (FFFFFF)
+Size 55: 28,55 BAD (FFFFFF)
+Size 56: 28,56 BAD (FFFFFF)
+Size 57: 29,57 BAD (FFFFFF)
+Size 58: 29,58 BAD (FFFFFF)
+Size 59: 30,59 BAD (FFFFFF)
+Size 60: 30,60 BAD (FFFFFF)
+Size 61: 31,61 BAD (FFFFFF)
+Size 62: 31,62 BAD (FFFFFF)
+Size 63: 32,63 BAD (FFFFFF)
+Size 64: 32,64 BAD (FFFFFF)
+Size 65: 33,65 BAD (FFFFFF)
+Size 66: 33,66 BAD (FFFFFF)
+Size 67: 34,67 BAD (FFFFFF)
+Size 68: 34,68 BAD (FFFFFF)
+Size 69: 35,69 BAD (FFFFFF)
+Size 70: 35,70 BAD (FFFFFF)
+Size 71: 36,71 BAD (FFFFFF)
+Size 72: 36,72 BAD (FFFFFF)
+Size 73: 37,73 BAD (FFFFFF)
+Size 74: 37,74 BAD (FFFFFF)
+Size 75: 38,75 BAD (FFFFFF)
+Size 76: 38,76 BAD (FFFFFF)
+Size 77: 39,77 BAD (FFFFFF)
+Size 78: 39,78 BAD (FFFFFF)
+Size 79: 40,79 BAD (FFFFFF)
+Size 80: 40,80 BAD (FFFFFF)
+Size 81: 41,81 BAD (FFFFFF)
+Size 82: 41,82 BAD (FFFFFF)
+Size 83: 42,83 BAD (FFFFFF)
+Size 84: 42,84 BAD (FFFFFF)
+Size 85: 43,85 BAD (FFFFFF)
+Size 86: 43,86 BAD (FFFFFF)
+Size 87: 44,87 BAD (FFFFFF)
+Size 88: 44,88 BAD (FFFFFF)
+Size 89: 45,89 BAD (FFFFFF)
+Size 90: 45,90 BAD (FFFFFF)
+Size 91: 46,91 BAD (FFFFFF)
+Size 92: 46,92 BAD (FFFFFF)
+Size 93: 47,93 BAD (FFFFFF)
+Size 94: 47,94 BAD (FFFFFF)
+Size 95: 48,95 BAD (FFFFFF)
+Size 96: 48,97 BAD (FFFFFF)
+Size 97: 49,97 BAD (FFFFFF)
+Size 98: 49,98 BAD (FFFFFF)
+Size 99: 50,99 BAD (FFFFFF)
+Size 100: 50,100 BAD (FFFFFF)
+
+Windows 10 14342 New Console
+----------------------------
+
+Size 1: 1,1 OK (HHHFFF)
+Size 2: 1,2 OK (HHHFFF)
+Size 3: 2,3 OK (HHHFFF)
+Size 4: 2,4 OK (HHHFFF)
+Size 5: 3,5 OK (HHHFFF)
+Size 6: 3,6 OK (HHHFFF)
+Size 7: 4,7 OK (HHHFFF)
+Size 8: 4,8 OK (HHHFFF)
+Size 9: 5,9 OK (HHHFFF)
+Size 10: 5,10 OK (HHHFFF)
+Size 11: 6,11 OK (HHHFFF)
+Size 12: 6,12 OK (HHHFFF)
+Size 13: 7,13 OK (HHHFFF)
+Size 14: 7,14 OK (HHHFFF)
+Size 15: 8,15 OK (HHHFFF)
+Size 16: 8,16 OK (HHHFFF)
+Size 17: 9,17 OK (HHHFFF)
+Size 18: 9,18 OK (HHHFFF)
+Size 19: 10,19 OK (HHHFFF)
+Size 20: 10,20 OK (HHHFFF)
+Size 21: 11,21 OK (HHHFFF)
+Size 22: 11,22 OK (HHHFFF)
+Size 23: 12,23 OK (HHHFFF)
+Size 24: 12,24 OK (HHHFFF)
+Size 25: 13,25 OK (HHHFFF)
+Size 26: 13,26 OK (HHHFFF)
+Size 27: 14,27 OK (HHHFFF)
+Size 28: 14,28 OK (HHHFFF)
+Size 29: 15,29 OK (HHHFFF)
+Size 30: 15,30 OK (HHHFFF)
+Size 31: 16,31 OK (HHHFFF)
+Size 32: 16,32 OK (HHHFFF)
+Size 33: 17,33 OK (HHHFFF)
+Size 34: 17,34 OK (HHHFFF)
+Size 35: 18,35 OK (HHHFFF)
+Size 36: 18,36 OK (HHHFFF)
+Size 37: 19,37 OK (HHHFFF)
+Size 38: 19,38 OK (HHHFFF)
+Size 39: 20,39 OK (HHHFFF)
+Size 40: 20,40 OK (HHHFFF)
+Size 41: 21,41 OK (HHHFFF)
+Size 42: 21,42 OK (HHHFFF)
+Size 43: 22,43 OK (HHHFFF)
+Size 44: 22,44 OK (HHHFFF)
+Size 45: 23,45 OK (HHHFFF)
+Size 46: 23,46 OK (HHHFFF)
+Size 47: 24,47 OK (HHHFFF)
+Size 48: 24,48 OK (HHHFFF)
+Size 49: 25,49 OK (HHHFFF)
+Size 50: 25,50 OK (HHHFFF)
+Size 51: 26,51 OK (HHHFFF)
+Size 52: 26,52 OK (HHHFFF)
+Size 53: 27,53 OK (HHHFFF)
+Size 54: 27,54 OK (HHHFFF)
+Size 55: 28,55 OK (HHHFFF)
+Size 56: 28,56 OK (HHHFFF)
+Size 57: 29,57 OK (HHHFFF)
+Size 58: 29,58 OK (HHHFFF)
+Size 59: 30,59 OK (HHHFFF)
+Size 60: 30,60 OK (HHHFFF)
+Size 61: 31,61 OK (HHHFFF)
+Size 62: 31,62 OK (HHHFFF)
+Size 63: 32,63 OK (HHHFFF)
+Size 64: 32,64 OK (HHHFFF)
+Size 65: 33,65 OK (HHHFFF)
+Size 66: 33,66 OK (HHHFFF)
+Size 67: 34,67 OK (HHHFFF)
+Size 68: 34,68 OK (HHHFFF)
+Size 69: 35,69 OK (HHHFFF)
+Size 70: 35,70 OK (HHHFFF)
+Size 71: 36,71 OK (HHHFFF)
+Size 72: 36,72 OK (HHHFFF)
+Size 73: 37,73 OK (HHHFFF)
+Size 74: 37,74 OK (HHHFFF)
+Size 75: 38,75 OK (HHHFFF)
+Size 76: 38,76 OK (HHHFFF)
+Size 77: 39,77 OK (HHHFFF)
+Size 78: 39,78 OK (HHHFFF)
+Size 79: 40,79 OK (HHHFFF)
+Size 80: 40,80 OK (HHHFFF)
+Size 81: 41,81 OK (HHHFFF)
+Size 82: 41,82 OK (HHHFFF)
+Size 83: 42,83 OK (HHHFFF)
+Size 84: 42,84 OK (HHHFFF)
+Size 85: 43,85 OK (HHHFFF)
+Size 86: 43,86 OK (HHHFFF)
+Size 87: 44,87 OK (HHHFFF)
+Size 88: 44,88 OK (HHHFFF)
+Size 89: 45,89 OK (HHHFFF)
+Size 90: 45,90 OK (HHHFFF)
+Size 91: 46,91 OK (HHHFFF)
+Size 92: 46,92 OK (HHHFFF)
+Size 93: 47,93 OK (HHHFFF)
+Size 94: 47,94 OK (HHHFFF)
+Size 95: 48,95 OK (HHHFFF)
+Size 96: 48,96 OK (HHHFFF)
+Size 97: 49,97 OK (HHHFFF)
+Size 98: 49,98 OK (HHHFFF)
+Size 99: 50,99 OK (HHHFFF)
+Size 100: 50,100 OK (HHHFFF)
diff --git a/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP936.txt b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP936.txt
new file mode 100644
index 00000000000..43210dac518
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP936.txt
@@ -0,0 +1,630 @@
+==========================================================
+Code Page 936, Chinese Simplified (China/PRC), SimSun font
+==========================================================
+
+Options: -face-simsun -family 0x36
+Chars: A2 A3 2014 3044 30FC 4000
+
+Vista
+-----
+
+Size 1: 1,2 GOOD (HHFFFF)
+Size 2: 1,2 GOOD (HHFFFF)
+Size 3: 2,3 BAD (FFHFHH)
+Size 4: 2,5 GOOD (HHFFFF)
+Size 5: 3,6 BAD (HHHFHH)
+Size 6: 3,7 GOOD (HHFFFF)
+Size 7: 4,8 BAD (HHHFHH)
+Size 8: 4,9 GOOD (HHFFFF)
+Size 9: 5,10 BAD (HHHFHH)
+Size 10: 5,11 GOOD (HHFFFF)
+Size 11: 6,13 BAD (HHHFHH)
+Size 12: 6,14 GOOD (HHFFFF)
+Size 13: 7,15 BAD (HHHFHH)
+Size 14: 7,16 GOOD (HHFFFF)
+Size 15: 8,17 BAD (HHHFHH)
+Size 16: 8,18 GOOD (HHFFFF)
+Size 17: 9,19 BAD (HHHFHH)
+Size 18: 9,21 GOOD (HHFFFF)
+Size 19: 10,22 BAD (HHHFHH)
+Size 20: 10,23 GOOD (HHFFFF)
+Size 21: 11,24 BAD (HHHFHH)
+Size 22: 11,25 GOOD (HHFFFF)
+Size 23: 12,26 BAD (HHHFHH)
+Size 24: 12,27 GOOD (HHFFFF)
+Size 25: 13,29 BAD (HHHFHH)
+Size 26: 13,30 GOOD (HHFFFF)
+Size 27: 14,31 BAD (HHHFHH)
+Size 28: 14,32 GOOD (HHFFFF)
+Size 29: 15,33 BAD (HHHFHH)
+Size 30: 15,34 GOOD (HHFFFF)
+Size 31: 16,35 BAD (HHHFHH)
+Size 32: 16,38 GOOD (HHFFFF)
+Size 33: 17,38 BAD (HHHFHH)
+Size 34: 17,39 GOOD (HHFFFF)
+Size 35: 18,40 BAD (HHHFHH)
+Size 36: 18,41 GOOD (HHFFFF)
+Size 37: 19,42 BAD (HHHFHH)
+Size 38: 19,43 GOOD (HHFFFF)
+Size 39: 20,44 BAD (HHHFHH)
+Size 40: 20,46 GOOD (HHFFFF)
+Size 41: 21,47 BAD (HHHFHH)
+Size 42: 21,48 GOOD (HHFFFF)
+Size 43: 22,49 BAD (HHHFHH)
+Size 44: 22,50 GOOD (HHFFFF)
+Size 45: 23,51 BAD (HHHFHH)
+Size 46: 23,52 GOOD (HHFFFF)
+Size 47: 24,54 BAD (HHHFHH)
+Size 48: 24,55 GOOD (HHFFFF)
+Size 49: 25,56 BAD (HHHFHH)
+Size 50: 25,57 GOOD (HHFFFF)
+Size 51: 26,58 BAD (HHHFHH)
+Size 52: 26,59 GOOD (HHFFFF)
+Size 53: 27,60 BAD (HHHFHH)
+Size 54: 27,62 GOOD (HHFFFF)
+Size 55: 28,63 BAD (HHHFHH)
+Size 56: 28,64 GOOD (HHFFFF)
+Size 57: 29,65 BAD (HHHFHH)
+Size 58: 29,66 GOOD (HHFFFF)
+Size 59: 30,67 BAD (HHHFHH)
+Size 60: 30,68 GOOD (HHFFFF)
+Size 61: 31,70 BAD (HHHFHH)
+Size 62: 31,71 GOOD (HHFFFF)
+Size 63: 32,72 BAD (HHHFHH)
+Size 64: 32,73 GOOD (HHFFFF)
+Size 65: 33,74 GOOD (HHFFFF)
+Size 66: 33,75 GOOD (HHFFFF)
+Size 67: 34,76 GOOD (HHFFFF)
+Size 68: 34,78 GOOD (HHFFFF)
+Size 69: 35,79 GOOD (HHFFFF)
+Size 70: 35,80 GOOD (HHFFFF)
+Size 71: 36,81 GOOD (HHFFFF)
+Size 72: 36,82 GOOD (HHFFFF)
+Size 73: 37,83 GOOD (HHFFFF)
+Size 74: 37,84 GOOD (HHFFFF)
+Size 75: 38,86 GOOD (HHFFFF)
+Size 76: 38,87 GOOD (HHFFFF)
+Size 77: 39,88 GOOD (HHFFFF)
+Size 78: 39,89 GOOD (HHFFFF)
+Size 79: 40,90 GOOD (HHFFFF)
+Size 80: 40,91 GOOD (HHFFFF)
+Size 81: 41,92 GOOD (HHFFFF)
+Size 82: 41,94 GOOD (HHFFFF)
+Size 83: 42,95 GOOD (HHFFFF)
+Size 84: 42,96 GOOD (HHFFFF)
+Size 85: 43,97 GOOD (HHFFFF)
+Size 86: 43,98 GOOD (HHFFFF)
+Size 87: 44,99 GOOD (HHFFFF)
+Size 88: 44,100 GOOD (HHFFFF)
+Size 89: 45,102 GOOD (HHFFFF)
+Size 90: 45,103 GOOD (HHFFFF)
+Size 91: 46,104 GOOD (HHFFFF)
+Size 92: 46,105 GOOD (HHFFFF)
+Size 93: 47,106 GOOD (HHFFFF)
+Size 94: 47,107 GOOD (HHFFFF)
+Size 95: 48,108 GOOD (HHFFFF)
+Size 96: 48,111 GOOD (HHFFFF)
+Size 97: 49,111 GOOD (HHFFFF)
+Size 98: 49,112 GOOD (HHFFFF)
+Size 99: 50,113 GOOD (HHFFFF)
+Size 100: 50,114 GOOD (HHFFFF)
+
+Windows 7
+---------
+
+Size 1: 1,2 GOOD (HHFFFF)
+Size 2: 1,2 GOOD (HHFFFF)
+Size 3: 2,3 BAD (FFHFHH)
+Size 4: 2,5 GOOD (HHFFFF)
+Size 5: 3,6 BAD (FFHFHH)
+Size 6: 3,7 GOOD (HHFFFF)
+Size 7: 4,8 BAD (FFHFHH)
+Size 8: 4,9 GOOD (HHFFFF)
+Size 9: 5,10 BAD (FFHFHH)
+Size 10: 5,11 GOOD (HHFFFF)
+Size 11: 6,13 BAD (FFHFHH)
+Size 12: 6,14 GOOD (HHFFFF)
+Size 13: 7,15 BAD (FFHFHH)
+Size 14: 7,16 GOOD (HHFFFF)
+Size 15: 8,17 BAD (FFHFHH)
+Size 16: 8,18 GOOD (HHFFFF)
+Size 17: 9,19 BAD (FFHFHH)
+Size 18: 9,21 GOOD (HHFFFF)
+Size 19: 10,22 BAD (FFHFHH)
+Size 20: 10,23 GOOD (HHFFFF)
+Size 21: 11,24 BAD (FFHFHH)
+Size 22: 11,25 GOOD (HHFFFF)
+Size 23: 12,26 BAD (FFHFHH)
+Size 24: 12,27 GOOD (HHFFFF)
+Size 25: 13,29 BAD (FFHFHH)
+Size 26: 13,30 GOOD (HHFFFF)
+Size 27: 14,31 BAD (FFHFHH)
+Size 28: 14,32 GOOD (HHFFFF)
+Size 29: 15,33 BAD (FFHFHH)
+Size 30: 15,34 GOOD (HHFFFF)
+Size 31: 16,35 BAD (FFHFHH)
+Size 32: 16,38 GOOD (HHFFFF)
+Size 33: 17,38 BAD (FFHFHH)
+Size 34: 17,39 GOOD (HHFFFF)
+Size 35: 18,40 BAD (FFHFHH)
+Size 36: 18,41 GOOD (HHFFFF)
+Size 37: 19,42 BAD (FFHFHH)
+Size 38: 19,43 GOOD (HHFFFF)
+Size 39: 20,44 BAD (FFHFHH)
+Size 40: 20,46 GOOD (HHFFFF)
+Size 41: 21,47 BAD (FFHFHH)
+Size 42: 21,48 GOOD (HHFFFF)
+Size 43: 22,49 BAD (FFHFHH)
+Size 44: 22,50 GOOD (HHFFFF)
+Size 45: 23,51 BAD (FFHFHH)
+Size 46: 23,52 GOOD (HHFFFF)
+Size 47: 24,54 BAD (FFHFHH)
+Size 48: 24,55 GOOD (HHFFFF)
+Size 49: 25,56 BAD (FFHFHH)
+Size 50: 25,57 GOOD (HHFFFF)
+Size 51: 26,58 BAD (FFHFHH)
+Size 52: 26,59 GOOD (HHFFFF)
+Size 53: 27,60 BAD (FFHFHH)
+Size 54: 27,62 GOOD (HHFFFF)
+Size 55: 28,63 BAD (FFHFHH)
+Size 56: 28,64 GOOD (HHFFFF)
+Size 57: 29,65 BAD (FFHFHH)
+Size 58: 29,66 GOOD (HHFFFF)
+Size 59: 30,67 BAD (FFHFHH)
+Size 60: 30,68 GOOD (HHFFFF)
+Size 61: 31,70 BAD (FFHFHH)
+Size 62: 31,71 GOOD (HHFFFF)
+Size 63: 32,72 BAD (FFHFHH)
+Size 64: 32,73 GOOD (HHFFFF)
+Size 65: 33,74 GOOD (HHFFFF)
+Size 66: 33,75 GOOD (HHFFFF)
+Size 67: 34,76 GOOD (HHFFFF)
+Size 68: 34,78 GOOD (HHFFFF)
+Size 69: 35,79 GOOD (HHFFFF)
+Size 70: 35,80 GOOD (HHFFFF)
+Size 71: 36,81 GOOD (HHFFFF)
+Size 72: 36,82 GOOD (HHFFFF)
+Size 73: 37,83 GOOD (HHFFFF)
+Size 74: 37,84 GOOD (HHFFFF)
+Size 75: 38,86 GOOD (HHFFFF)
+Size 76: 38,87 GOOD (HHFFFF)
+Size 77: 39,88 GOOD (HHFFFF)
+Size 78: 39,89 GOOD (HHFFFF)
+Size 79: 40,90 GOOD (HHFFFF)
+Size 80: 40,91 GOOD (HHFFFF)
+Size 81: 41,92 GOOD (HHFFFF)
+Size 82: 41,94 GOOD (HHFFFF)
+Size 83: 42,95 GOOD (HHFFFF)
+Size 84: 42,96 GOOD (HHFFFF)
+Size 85: 43,97 GOOD (HHFFFF)
+Size 86: 43,98 GOOD (HHFFFF)
+Size 87: 44,99 GOOD (HHFFFF)
+Size 88: 44,100 GOOD (HHFFFF)
+Size 89: 45,102 GOOD (HHFFFF)
+Size 90: 45,103 GOOD (HHFFFF)
+Size 91: 46,104 GOOD (HHFFFF)
+Size 92: 46,105 GOOD (HHFFFF)
+Size 93: 47,106 GOOD (HHFFFF)
+Size 94: 47,107 GOOD (HHFFFF)
+Size 95: 48,108 GOOD (HHFFFF)
+Size 96: 48,111 GOOD (HHFFFF)
+Size 97: 49,111 GOOD (HHFFFF)
+Size 98: 49,112 GOOD (HHFFFF)
+Size 99: 50,113 GOOD (HHFFFF)
+Size 100: 50,114 GOOD (HHFFFF)
+
+Windows 8
+---------
+
+Size 1: 1,2 GOOD (HHFFFF)
+Size 2: 1,2 GOOD (HHFFFF)
+Size 3: 2,3 BAD (FFHFHH)
+Size 4: 2,5 GOOD (HHFFFF)
+Size 5: 3,6 BAD (FFHFHH)
+Size 6: 3,7 GOOD (HHFFFF)
+Size 7: 4,8 BAD (FFHFHH)
+Size 8: 4,9 GOOD (HHFFFF)
+Size 9: 5,10 BAD (FFHFHH)
+Size 10: 5,11 GOOD (HHFFFF)
+Size 11: 6,13 BAD (FFHFHH)
+Size 12: 6,14 GOOD (HHFFFF)
+Size 13: 7,15 BAD (FFHFHH)
+Size 14: 7,16 GOOD (HHFFFF)
+Size 15: 8,17 BAD (FFHFHH)
+Size 16: 8,18 GOOD (HHFFFF)
+Size 17: 9,19 BAD (FFHFHH)
+Size 18: 9,21 GOOD (HHFFFF)
+Size 19: 10,22 BAD (FFHFHH)
+Size 20: 10,23 GOOD (HHFFFF)
+Size 21: 11,24 BAD (FFHFHH)
+Size 22: 11,25 GOOD (HHFFFF)
+Size 23: 12,26 BAD (FFHFHH)
+Size 24: 12,27 GOOD (HHFFFF)
+Size 25: 13,29 BAD (FFHFHH)
+Size 26: 13,30 GOOD (HHFFFF)
+Size 27: 14,31 BAD (FFHFHH)
+Size 28: 14,32 GOOD (HHFFFF)
+Size 29: 15,33 BAD (FFHFHH)
+Size 30: 15,34 GOOD (HHFFFF)
+Size 31: 16,35 BAD (FFHFHH)
+Size 32: 16,38 GOOD (HHFFFF)
+Size 33: 17,38 BAD (FFHFHH)
+Size 34: 17,39 GOOD (HHFFFF)
+Size 35: 18,40 BAD (FFHFHH)
+Size 36: 18,41 GOOD (HHFFFF)
+Size 37: 19,42 BAD (FFHFHH)
+Size 38: 19,43 GOOD (HHFFFF)
+Size 39: 20,44 BAD (FFHFHH)
+Size 40: 20,46 GOOD (HHFFFF)
+Size 41: 21,47 BAD (FFHFHH)
+Size 42: 21,48 GOOD (HHFFFF)
+Size 43: 22,49 BAD (FFHFHH)
+Size 44: 22,50 GOOD (HHFFFF)
+Size 45: 23,51 BAD (FFHFHH)
+Size 46: 23,52 GOOD (HHFFFF)
+Size 47: 24,54 BAD (FFHFHH)
+Size 48: 24,55 GOOD (HHFFFF)
+Size 49: 25,56 BAD (FFHFHH)
+Size 50: 25,57 GOOD (HHFFFF)
+Size 51: 26,58 BAD (FFHFHH)
+Size 52: 26,59 GOOD (HHFFFF)
+Size 53: 27,60 BAD (FFHFHH)
+Size 54: 27,62 GOOD (HHFFFF)
+Size 55: 28,63 BAD (FFHFHH)
+Size 56: 28,64 GOOD (HHFFFF)
+Size 57: 29,65 BAD (FFHFHH)
+Size 58: 29,66 GOOD (HHFFFF)
+Size 59: 30,67 BAD (FFHFHH)
+Size 60: 30,68 GOOD (HHFFFF)
+Size 61: 31,70 BAD (FFHFHH)
+Size 62: 31,71 GOOD (HHFFFF)
+Size 63: 32,72 BAD (FFHFHH)
+Size 64: 32,73 GOOD (HHFFFF)
+Size 65: 33,74 GOOD (HHFFFF)
+Size 66: 33,75 GOOD (HHFFFF)
+Size 67: 34,76 GOOD (HHFFFF)
+Size 68: 34,78 GOOD (HHFFFF)
+Size 69: 35,79 GOOD (HHFFFF)
+Size 70: 35,80 GOOD (HHFFFF)
+Size 71: 36,81 GOOD (HHFFFF)
+Size 72: 36,82 GOOD (HHFFFF)
+Size 73: 37,83 GOOD (HHFFFF)
+Size 74: 37,84 GOOD (HHFFFF)
+Size 75: 38,86 GOOD (HHFFFF)
+Size 76: 38,87 GOOD (HHFFFF)
+Size 77: 39,88 GOOD (HHFFFF)
+Size 78: 39,89 GOOD (HHFFFF)
+Size 79: 40,90 GOOD (HHFFFF)
+Size 80: 40,91 GOOD (HHFFFF)
+Size 81: 41,92 GOOD (HHFFFF)
+Size 82: 41,94 GOOD (HHFFFF)
+Size 83: 42,95 GOOD (HHFFFF)
+Size 84: 42,96 GOOD (HHFFFF)
+Size 85: 43,97 GOOD (HHFFFF)
+Size 86: 43,98 GOOD (HHFFFF)
+Size 87: 44,99 GOOD (HHFFFF)
+Size 88: 44,100 GOOD (HHFFFF)
+Size 89: 45,102 GOOD (HHFFFF)
+Size 90: 45,103 GOOD (HHFFFF)
+Size 91: 46,104 GOOD (HHFFFF)
+Size 92: 46,105 GOOD (HHFFFF)
+Size 93: 47,106 GOOD (HHFFFF)
+Size 94: 47,107 GOOD (HHFFFF)
+Size 95: 48,108 GOOD (HHFFFF)
+Size 96: 48,111 GOOD (HHFFFF)
+Size 97: 49,111 GOOD (HHFFFF)
+Size 98: 49,112 GOOD (HHFFFF)
+Size 99: 50,113 GOOD (HHFFFF)
+Size 100: 50,114 GOOD (HHFFFF)
+
+Windows 8.1
+-----------
+
+Size 1: 1,2 GOOD (HHFFFF)
+Size 2: 1,2 GOOD (HHFFFF)
+Size 3: 2,3 BAD (FFHFHH)
+Size 4: 2,5 GOOD (HHFFFF)
+Size 5: 3,6 BAD (FFHFHH)
+Size 6: 3,7 GOOD (HHFFFF)
+Size 7: 4,8 BAD (FFHFHH)
+Size 8: 4,9 GOOD (HHFFFF)
+Size 9: 5,10 BAD (FFHFHH)
+Size 10: 5,11 GOOD (HHFFFF)
+Size 11: 6,13 BAD (FFHFHH)
+Size 12: 6,14 GOOD (HHFFFF)
+Size 13: 7,15 BAD (FFHFHH)
+Size 14: 7,16 GOOD (HHFFFF)
+Size 15: 8,17 BAD (FFHFHH)
+Size 16: 8,18 GOOD (HHFFFF)
+Size 17: 9,19 BAD (FFHFHH)
+Size 18: 9,21 GOOD (HHFFFF)
+Size 19: 10,22 BAD (FFHFHH)
+Size 20: 10,23 GOOD (HHFFFF)
+Size 21: 11,24 BAD (FFHFHH)
+Size 22: 11,25 GOOD (HHFFFF)
+Size 23: 12,26 BAD (FFHFHH)
+Size 24: 12,27 GOOD (HHFFFF)
+Size 25: 13,29 BAD (FFHFHH)
+Size 26: 13,30 GOOD (HHFFFF)
+Size 27: 14,31 BAD (FFHFHH)
+Size 28: 14,32 GOOD (HHFFFF)
+Size 29: 15,33 BAD (FFHFHH)
+Size 30: 15,34 GOOD (HHFFFF)
+Size 31: 16,35 BAD (FFHFHH)
+Size 32: 16,38 GOOD (HHFFFF)
+Size 33: 17,38 BAD (FFHFHH)
+Size 34: 17,39 GOOD (HHFFFF)
+Size 35: 18,40 BAD (FFHFHH)
+Size 36: 18,41 GOOD (HHFFFF)
+Size 37: 19,42 BAD (FFHFHH)
+Size 38: 19,43 GOOD (HHFFFF)
+Size 39: 20,44 BAD (FFHFHH)
+Size 40: 20,46 GOOD (HHFFFF)
+Size 41: 21,47 BAD (FFHFHH)
+Size 42: 21,48 GOOD (HHFFFF)
+Size 43: 22,49 BAD (FFHFHH)
+Size 44: 22,50 GOOD (HHFFFF)
+Size 45: 23,51 BAD (FFHFHH)
+Size 46: 23,52 GOOD (HHFFFF)
+Size 47: 24,54 BAD (FFHFHH)
+Size 48: 24,55 GOOD (HHFFFF)
+Size 49: 25,56 BAD (FFHFHH)
+Size 50: 25,57 GOOD (HHFFFF)
+Size 51: 26,58 BAD (FFHFHH)
+Size 52: 26,59 GOOD (HHFFFF)
+Size 53: 27,60 BAD (FFHFHH)
+Size 54: 27,62 GOOD (HHFFFF)
+Size 55: 28,63 BAD (FFHFHH)
+Size 56: 28,64 GOOD (HHFFFF)
+Size 57: 29,65 BAD (FFHFHH)
+Size 58: 29,66 GOOD (HHFFFF)
+Size 59: 30,67 BAD (FFHFHH)
+Size 60: 30,68 GOOD (HHFFFF)
+Size 61: 31,70 BAD (FFHFHH)
+Size 62: 31,71 GOOD (HHFFFF)
+Size 63: 32,72 BAD (FFHFHH)
+Size 64: 32,73 GOOD (HHFFFF)
+Size 65: 33,74 GOOD (HHFFFF)
+Size 66: 33,75 GOOD (HHFFFF)
+Size 67: 34,76 GOOD (HHFFFF)
+Size 68: 34,78 GOOD (HHFFFF)
+Size 69: 35,79 GOOD (HHFFFF)
+Size 70: 35,80 GOOD (HHFFFF)
+Size 71: 36,81 GOOD (HHFFFF)
+Size 72: 36,82 GOOD (HHFFFF)
+Size 73: 37,83 GOOD (HHFFFF)
+Size 74: 37,84 GOOD (HHFFFF)
+Size 75: 38,86 GOOD (HHFFFF)
+Size 76: 38,87 GOOD (HHFFFF)
+Size 77: 39,88 GOOD (HHFFFF)
+Size 78: 39,89 GOOD (HHFFFF)
+Size 79: 40,90 GOOD (HHFFFF)
+Size 80: 40,91 GOOD (HHFFFF)
+Size 81: 41,92 GOOD (HHFFFF)
+Size 82: 41,94 GOOD (HHFFFF)
+Size 83: 42,95 GOOD (HHFFFF)
+Size 84: 42,96 GOOD (HHFFFF)
+Size 85: 43,97 GOOD (HHFFFF)
+Size 86: 43,98 GOOD (HHFFFF)
+Size 87: 44,99 GOOD (HHFFFF)
+Size 88: 44,100 GOOD (HHFFFF)
+Size 89: 45,102 GOOD (HHFFFF)
+Size 90: 45,103 GOOD (HHFFFF)
+Size 91: 46,104 GOOD (HHFFFF)
+Size 92: 46,105 GOOD (HHFFFF)
+Size 93: 47,106 GOOD (HHFFFF)
+Size 94: 47,107 GOOD (HHFFFF)
+Size 95: 48,108 GOOD (HHFFFF)
+Size 96: 48,111 GOOD (HHFFFF)
+Size 97: 49,111 GOOD (HHFFFF)
+Size 98: 49,112 GOOD (HHFFFF)
+Size 99: 50,113 GOOD (HHFFFF)
+Size 100: 50,114 GOOD (HHFFFF)
+
+Windows 10 14342 Old Console
+----------------------------
+
+Size 1: 1,2 GOOD (HHFFFF)
+Size 2: 1,2 GOOD (HHFFFF)
+Size 3: 2,3 BAD (FFHFHH)
+Size 4: 2,5 GOOD (HHFFFF)
+Size 5: 3,6 BAD (FFHFHH)
+Size 6: 3,7 GOOD (HHFFFF)
+Size 7: 4,8 BAD (FFHFHH)
+Size 8: 4,9 GOOD (HHFFFF)
+Size 9: 5,10 BAD (FFHFHH)
+Size 10: 5,11 GOOD (HHFFFF)
+Size 11: 6,13 BAD (FFHFHH)
+Size 12: 6,14 GOOD (HHFFFF)
+Size 13: 7,15 BAD (FFHFHH)
+Size 14: 7,16 GOOD (HHFFFF)
+Size 15: 8,17 BAD (FFHFHH)
+Size 16: 8,18 GOOD (HHFFFF)
+Size 17: 9,19 BAD (FFHFHH)
+Size 18: 9,21 GOOD (HHFFFF)
+Size 19: 10,22 BAD (FFHFHH)
+Size 20: 10,23 GOOD (HHFFFF)
+Size 21: 11,24 BAD (FFHFHH)
+Size 22: 11,25 GOOD (HHFFFF)
+Size 23: 12,26 BAD (FFHFHH)
+Size 24: 12,27 GOOD (HHFFFF)
+Size 25: 13,29 BAD (FFHFHH)
+Size 26: 13,30 GOOD (HHFFFF)
+Size 27: 14,31 BAD (FFHFHH)
+Size 28: 14,32 GOOD (HHFFFF)
+Size 29: 15,33 BAD (FFHFHH)
+Size 30: 15,34 GOOD (HHFFFF)
+Size 31: 16,35 BAD (FFHFHH)
+Size 32: 16,38 GOOD (HHFFFF)
+Size 33: 17,38 BAD (FFHFHH)
+Size 34: 17,39 GOOD (HHFFFF)
+Size 35: 18,40 BAD (FFHFHH)
+Size 36: 18,41 GOOD (HHFFFF)
+Size 37: 19,42 BAD (FFHFHH)
+Size 38: 19,43 GOOD (HHFFFF)
+Size 39: 20,44 BAD (FFHFHH)
+Size 40: 20,46 GOOD (HHFFFF)
+Size 41: 21,47 BAD (FFHFHH)
+Size 42: 21,48 GOOD (HHFFFF)
+Size 43: 22,49 BAD (FFHFHH)
+Size 44: 22,50 GOOD (HHFFFF)
+Size 45: 23,51 BAD (FFHFHH)
+Size 46: 23,52 GOOD (HHFFFF)
+Size 47: 24,54 BAD (FFHFHH)
+Size 48: 24,55 GOOD (HHFFFF)
+Size 49: 25,56 BAD (FFHFHH)
+Size 50: 25,57 GOOD (HHFFFF)
+Size 51: 26,58 BAD (FFHFHH)
+Size 52: 26,59 GOOD (HHFFFF)
+Size 53: 27,60 BAD (FFHFHH)
+Size 54: 27,62 GOOD (HHFFFF)
+Size 55: 28,63 BAD (FFHFHH)
+Size 56: 28,64 GOOD (HHFFFF)
+Size 57: 29,65 BAD (FFHFHH)
+Size 58: 29,66 GOOD (HHFFFF)
+Size 59: 30,67 BAD (FFHFHH)
+Size 60: 30,68 GOOD (HHFFFF)
+Size 61: 31,70 BAD (FFHFHH)
+Size 62: 31,71 GOOD (HHFFFF)
+Size 63: 32,72 BAD (FFHFHH)
+Size 64: 32,73 GOOD (HHFFFF)
+Size 65: 33,74 GOOD (HHFFFF)
+Size 66: 33,75 GOOD (HHFFFF)
+Size 67: 34,76 GOOD (HHFFFF)
+Size 68: 34,78 GOOD (HHFFFF)
+Size 69: 35,79 GOOD (HHFFFF)
+Size 70: 35,80 GOOD (HHFFFF)
+Size 71: 36,81 GOOD (HHFFFF)
+Size 72: 36,82 GOOD (HHFFFF)
+Size 73: 37,83 GOOD (HHFFFF)
+Size 74: 37,84 GOOD (HHFFFF)
+Size 75: 38,86 GOOD (HHFFFF)
+Size 76: 38,87 GOOD (HHFFFF)
+Size 77: 39,88 GOOD (HHFFFF)
+Size 78: 39,89 GOOD (HHFFFF)
+Size 79: 40,90 GOOD (HHFFFF)
+Size 80: 40,91 GOOD (HHFFFF)
+Size 81: 41,92 GOOD (HHFFFF)
+Size 82: 41,94 GOOD (HHFFFF)
+Size 83: 42,95 GOOD (HHFFFF)
+Size 84: 42,96 GOOD (HHFFFF)
+Size 85: 43,97 GOOD (HHFFFF)
+Size 86: 43,98 GOOD (HHFFFF)
+Size 87: 44,99 GOOD (HHFFFF)
+Size 88: 44,100 GOOD (HHFFFF)
+Size 89: 45,102 GOOD (HHFFFF)
+Size 90: 45,103 GOOD (HHFFFF)
+Size 91: 46,104 GOOD (HHFFFF)
+Size 92: 46,105 GOOD (HHFFFF)
+Size 93: 47,106 GOOD (HHFFFF)
+Size 94: 47,107 GOOD (HHFFFF)
+Size 95: 48,108 GOOD (HHFFFF)
+Size 96: 48,111 GOOD (HHFFFF)
+Size 97: 49,111 GOOD (HHFFFF)
+Size 98: 49,112 GOOD (HHFFFF)
+Size 99: 50,113 GOOD (HHFFFF)
+Size 100: 50,114 GOOD (HHFFFF)
+
+Windows 10 14342 New Console
+----------------------------
+
+Size 1: 1,1 GOOD (HHFFFF)
+Size 2: 1,2 GOOD (HHFFFF)
+Size 3: 2,3 GOOD (HHFFFF)
+Size 4: 2,4 GOOD (HHFFFF)
+Size 5: 3,5 GOOD (HHFFFF)
+Size 6: 3,6 GOOD (HHFFFF)
+Size 7: 4,7 GOOD (HHFFFF)
+Size 8: 4,8 GOOD (HHFFFF)
+Size 9: 5,9 GOOD (HHFFFF)
+Size 10: 5,10 GOOD (HHFFFF)
+Size 11: 6,11 GOOD (HHFFFF)
+Size 12: 6,12 GOOD (HHFFFF)
+Size 13: 7,13 GOOD (HHFFFF)
+Size 14: 7,14 GOOD (HHFFFF)
+Size 15: 8,15 GOOD (HHFFFF)
+Size 16: 8,16 GOOD (HHFFFF)
+Size 17: 9,17 GOOD (HHFFFF)
+Size 18: 9,18 GOOD (HHFFFF)
+Size 19: 10,19 GOOD (HHFFFF)
+Size 20: 10,20 GOOD (HHFFFF)
+Size 21: 11,21 GOOD (HHFFFF)
+Size 22: 11,22 GOOD (HHFFFF)
+Size 23: 12,23 GOOD (HHFFFF)
+Size 24: 12,24 GOOD (HHFFFF)
+Size 25: 13,25 GOOD (HHFFFF)
+Size 26: 13,26 GOOD (HHFFFF)
+Size 27: 14,27 GOOD (HHFFFF)
+Size 28: 14,28 GOOD (HHFFFF)
+Size 29: 15,29 GOOD (HHFFFF)
+Size 30: 15,30 GOOD (HHFFFF)
+Size 31: 16,31 GOOD (HHFFFF)
+Size 32: 16,32 GOOD (HHFFFF)
+Size 33: 17,33 GOOD (HHFFFF)
+Size 34: 17,34 GOOD (HHFFFF)
+Size 35: 18,35 GOOD (HHFFFF)
+Size 36: 18,36 GOOD (HHFFFF)
+Size 37: 19,37 GOOD (HHFFFF)
+Size 38: 19,38 GOOD (HHFFFF)
+Size 39: 20,39 GOOD (HHFFFF)
+Size 40: 20,40 GOOD (HHFFFF)
+Size 41: 21,41 GOOD (HHFFFF)
+Size 42: 21,42 GOOD (HHFFFF)
+Size 43: 22,43 GOOD (HHFFFF)
+Size 44: 22,44 GOOD (HHFFFF)
+Size 45: 23,45 GOOD (HHFFFF)
+Size 46: 23,46 GOOD (HHFFFF)
+Size 47: 24,47 GOOD (HHFFFF)
+Size 48: 24,48 GOOD (HHFFFF)
+Size 49: 25,49 GOOD (HHFFFF)
+Size 50: 25,50 GOOD (HHFFFF)
+Size 51: 26,51 GOOD (HHFFFF)
+Size 52: 26,52 GOOD (HHFFFF)
+Size 53: 27,53 GOOD (HHFFFF)
+Size 54: 27,54 GOOD (HHFFFF)
+Size 55: 28,55 GOOD (HHFFFF)
+Size 56: 28,56 GOOD (HHFFFF)
+Size 57: 29,57 GOOD (HHFFFF)
+Size 58: 29,58 GOOD (HHFFFF)
+Size 59: 30,59 GOOD (HHFFFF)
+Size 60: 30,60 GOOD (HHFFFF)
+Size 61: 31,61 GOOD (HHFFFF)
+Size 62: 31,62 GOOD (HHFFFF)
+Size 63: 32,63 GOOD (HHFFFF)
+Size 64: 32,64 GOOD (HHFFFF)
+Size 65: 33,65 GOOD (HHFFFF)
+Size 66: 33,66 GOOD (HHFFFF)
+Size 67: 34,67 GOOD (HHFFFF)
+Size 68: 34,68 GOOD (HHFFFF)
+Size 69: 35,69 GOOD (HHFFFF)
+Size 70: 35,70 GOOD (HHFFFF)
+Size 71: 36,71 GOOD (HHFFFF)
+Size 72: 36,72 GOOD (HHFFFF)
+Size 73: 37,73 GOOD (HHFFFF)
+Size 74: 37,74 GOOD (HHFFFF)
+Size 75: 38,75 GOOD (HHFFFF)
+Size 76: 38,76 GOOD (HHFFFF)
+Size 77: 39,77 GOOD (HHFFFF)
+Size 78: 39,78 GOOD (HHFFFF)
+Size 79: 40,79 GOOD (HHFFFF)
+Size 80: 40,80 GOOD (HHFFFF)
+Size 81: 41,81 GOOD (HHFFFF)
+Size 82: 41,82 GOOD (HHFFFF)
+Size 83: 42,83 GOOD (HHFFFF)
+Size 84: 42,84 GOOD (HHFFFF)
+Size 85: 43,85 GOOD (HHFFFF)
+Size 86: 43,86 GOOD (HHFFFF)
+Size 87: 44,87 GOOD (HHFFFF)
+Size 88: 44,88 GOOD (HHFFFF)
+Size 89: 45,89 GOOD (HHFFFF)
+Size 90: 45,90 GOOD (HHFFFF)
+Size 91: 46,91 GOOD (HHFFFF)
+Size 92: 46,92 GOOD (HHFFFF)
+Size 93: 47,93 GOOD (HHFFFF)
+Size 94: 47,94 GOOD (HHFFFF)
+Size 95: 48,95 GOOD (HHFFFF)
+Size 96: 48,96 GOOD (HHFFFF)
+Size 97: 49,97 GOOD (HHFFFF)
+Size 98: 49,98 GOOD (HHFFFF)
+Size 99: 50,99 GOOD (HHFFFF)
+Size 100: 50,100 GOOD (HHFFFF)
diff --git a/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP949.txt b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP949.txt
new file mode 100644
index 00000000000..2f0ea1e7c2e
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP949.txt
@@ -0,0 +1,630 @@
+=====================================
+Code Page 949, Korean, GulimChe font
+=====================================
+
+Options: -face-gulimche -family 0x36
+Chars: A2 A3 2014 3044 30FC 4000
+
+Vista
+-----
+
+Size 1: 1,2 OK (HHHFFF)
+Size 2: 1,2 OK (HHHFFF)
+Size 3: 2,3 BAD (FFFFHH)
+Size 4: 2,5 OK (HHHFFF)
+Size 5: 3,6 BAD (HHHFHH)
+Size 6: 3,7 OK (HHHFFF)
+Size 7: 4,8 BAD (HHHFHH)
+Size 8: 4,9 OK (HHHFFF)
+Size 9: 5,10 BAD (HHHFHH)
+Size 10: 5,11 OK (HHHFFF)
+Size 11: 6,13 BAD (HHHFHH)
+Size 12: 6,14 OK (HHHFFF)
+Size 13: 7,15 BAD (HHHFHH)
+Size 14: 7,16 OK (HHHFFF)
+Size 15: 8,17 BAD (HHHFHH)
+Size 16: 8,18 OK (HHHFFF)
+Size 17: 9,20 BAD (HHHFHH)
+Size 18: 9,21 OK (HHHFFF)
+Size 19: 10,22 BAD (HHHFHH)
+Size 20: 10,23 OK (HHHFFF)
+Size 21: 11,24 BAD (HHHFHH)
+Size 22: 11,25 OK (HHHFFF)
+Size 23: 12,26 BAD (HHHFHH)
+Size 24: 12,28 OK (HHHFFF)
+Size 25: 13,29 BAD (HHHFHH)
+Size 26: 13,30 OK (HHHFFF)
+Size 27: 14,31 BAD (HHHFHH)
+Size 28: 14,32 OK (HHHFFF)
+Size 29: 15,33 BAD (HHHFHH)
+Size 30: 15,34 OK (HHHFFF)
+Size 31: 16,36 BAD (HHHFHH)
+Size 32: 16,37 OK (HHHFFF)
+Size 33: 17,38 BAD (HHHFHH)
+Size 34: 17,39 OK (HHHFFF)
+Size 35: 18,40 BAD (HHHFHH)
+Size 36: 18,41 OK (HHHFFF)
+Size 37: 19,42 BAD (HHHFHH)
+Size 38: 19,44 OK (HHHFFF)
+Size 39: 20,45 BAD (HHHFHH)
+Size 40: 20,46 OK (HHHFFF)
+Size 41: 21,47 BAD (HHHFHH)
+Size 42: 21,48 OK (HHHFFF)
+Size 43: 22,49 BAD (HHHFHH)
+Size 44: 22,51 OK (HHHFFF)
+Size 45: 23,52 BAD (HHHFHH)
+Size 46: 23,53 OK (HHHFFF)
+Size 47: 24,54 BAD (HHHFHH)
+Size 48: 24,55 OK (HHHFFF)
+Size 49: 25,56 BAD (HHHFHH)
+Size 50: 25,57 OK (HHHFFF)
+Size 51: 26,59 BAD (HHHFHH)
+Size 52: 26,60 OK (HHHFFF)
+Size 53: 27,61 BAD (HHHFHH)
+Size 54: 27,62 OK (HHHFFF)
+Size 55: 28,63 BAD (HHHFHH)
+Size 56: 28,64 OK (HHHFFF)
+Size 57: 29,65 BAD (HHHFHH)
+Size 58: 29,67 OK (HHHFFF)
+Size 59: 30,68 BAD (HHHFHH)
+Size 60: 30,69 OK (HHHFFF)
+Size 61: 31,70 BAD (HHHFHH)
+Size 62: 31,71 OK (HHHFFF)
+Size 63: 32,72 BAD (HHHFHH)
+Size 64: 32,74 OK (HHHFFF)
+Size 65: 33,75 BAD (HHHFHH)
+Size 66: 33,76 OK (HHHFFF)
+Size 67: 34,77 BAD (HHHFHH)
+Size 68: 34,78 OK (HHHFFF)
+Size 69: 35,79 BAD (HHHFHH)
+Size 70: 35,80 OK (HHHFFF)
+Size 71: 36,82 BAD (HHHFHH)
+Size 72: 36,83 OK (HHHFFF)
+Size 73: 37,84 BAD (HHHFHH)
+Size 74: 37,85 OK (HHHFFF)
+Size 75: 38,86 BAD (HHHFHH)
+Size 76: 38,87 OK (HHHFFF)
+Size 77: 39,88 BAD (HHHFHH)
+Size 78: 39,90 OK (HHHFFF)
+Size 79: 40,91 BAD (HHHFHH)
+Size 80: 40,92 OK (HHHFFF)
+Size 81: 41,93 BAD (HHHFHH)
+Size 82: 41,94 OK (HHHFFF)
+Size 83: 42,95 BAD (HHHFHH)
+Size 84: 42,96 OK (HHHFFF)
+Size 85: 43,98 BAD (HHHFHH)
+Size 86: 43,99 OK (HHHFFF)
+Size 87: 44,100 BAD (HHHFHH)
+Size 88: 44,101 OK (HHHFFF)
+Size 89: 45,102 BAD (HHHFHH)
+Size 90: 45,103 OK (HHHFFF)
+Size 91: 46,105 BAD (HHHFHH)
+Size 92: 46,106 OK (HHHFFF)
+Size 93: 47,107 BAD (HHHFHH)
+Size 94: 47,108 OK (HHHFFF)
+Size 95: 48,109 BAD (HHHFHH)
+Size 96: 48,110 OK (HHHFFF)
+Size 97: 49,111 BAD (HHHFHH)
+Size 98: 49,113 OK (HHHFFF)
+Size 99: 50,114 BAD (HHHFHH)
+Size 100: 50,115 OK (HHHFFF)
+
+Windows 7
+---------
+
+Size 1: 1,2 OK (HHHFFF)
+Size 2: 1,2 OK (HHHFFF)
+Size 3: 2,3 BAD (FFFFHH)
+Size 4: 2,5 OK (HHHFFF)
+Size 5: 3,6 BAD (FFFFHH)
+Size 6: 3,7 OK (HHHFFF)
+Size 7: 4,8 BAD (FFFFHH)
+Size 8: 4,9 OK (HHHFFF)
+Size 9: 5,10 BAD (FFFFHH)
+Size 10: 5,11 OK (HHHFFF)
+Size 11: 6,13 BAD (FFFFHH)
+Size 12: 6,14 OK (HHHFFF)
+Size 13: 7,15 BAD (FFFFHH)
+Size 14: 7,16 OK (HHHFFF)
+Size 15: 8,17 BAD (FFFFHH)
+Size 16: 8,18 OK (HHHFFF)
+Size 17: 9,20 BAD (FFFFHH)
+Size 18: 9,21 OK (HHHFFF)
+Size 19: 10,22 BAD (FFFFHH)
+Size 20: 10,23 OK (HHHFFF)
+Size 21: 11,24 BAD (FFFFHH)
+Size 22: 11,25 OK (HHHFFF)
+Size 23: 12,26 BAD (FFFFHH)
+Size 24: 12,28 OK (HHHFFF)
+Size 25: 13,29 BAD (FFFFHH)
+Size 26: 13,30 OK (HHHFFF)
+Size 27: 14,31 BAD (FFFFHH)
+Size 28: 14,32 OK (HHHFFF)
+Size 29: 15,33 BAD (FFFFHH)
+Size 30: 15,34 OK (HHHFFF)
+Size 31: 16,36 BAD (FFFFHH)
+Size 32: 16,37 OK (HHHFFF)
+Size 33: 17,38 BAD (FFFFHH)
+Size 34: 17,39 OK (HHHFFF)
+Size 35: 18,40 BAD (FFFFHH)
+Size 36: 18,41 OK (HHHFFF)
+Size 37: 19,42 BAD (FFFFHH)
+Size 38: 19,44 OK (HHHFFF)
+Size 39: 20,45 BAD (FFFFHH)
+Size 40: 20,46 OK (HHHFFF)
+Size 41: 21,47 BAD (FFFFHH)
+Size 42: 21,48 OK (HHHFFF)
+Size 43: 22,49 BAD (FFFFHH)
+Size 44: 22,51 OK (HHHFFF)
+Size 45: 23,52 BAD (FFFFHH)
+Size 46: 23,53 OK (HHHFFF)
+Size 47: 24,54 BAD (FFFFHH)
+Size 48: 24,55 OK (HHHFFF)
+Size 49: 25,56 BAD (FFFFHH)
+Size 50: 25,57 OK (HHHFFF)
+Size 51: 26,59 BAD (FFFFHH)
+Size 52: 26,60 OK (HHHFFF)
+Size 53: 27,61 BAD (FFFFHH)
+Size 54: 27,62 OK (HHHFFF)
+Size 55: 28,63 BAD (FFFFHH)
+Size 56: 28,64 OK (HHHFFF)
+Size 57: 29,65 BAD (FFFFHH)
+Size 58: 29,67 OK (HHHFFF)
+Size 59: 30,68 BAD (FFFFHH)
+Size 60: 30,69 OK (HHHFFF)
+Size 61: 31,70 BAD (FFFFHH)
+Size 62: 31,71 OK (HHHFFF)
+Size 63: 32,72 BAD (FFFFHH)
+Size 64: 32,74 OK (HHHFFF)
+Size 65: 33,75 BAD (FFFFHH)
+Size 66: 33,76 OK (HHHFFF)
+Size 67: 34,77 BAD (FFFFHH)
+Size 68: 34,78 OK (HHHFFF)
+Size 69: 35,79 BAD (FFFFHH)
+Size 70: 35,80 OK (HHHFFF)
+Size 71: 36,82 BAD (FFFFHH)
+Size 72: 36,83 OK (HHHFFF)
+Size 73: 37,84 BAD (FFFFHH)
+Size 74: 37,85 OK (HHHFFF)
+Size 75: 38,86 BAD (FFFFHH)
+Size 76: 38,87 OK (HHHFFF)
+Size 77: 39,88 BAD (FFFFHH)
+Size 78: 39,90 OK (HHHFFF)
+Size 79: 40,91 BAD (FFFFHH)
+Size 80: 40,92 OK (HHHFFF)
+Size 81: 41,93 BAD (FFFFHH)
+Size 82: 41,94 OK (HHHFFF)
+Size 83: 42,95 BAD (FFFFHH)
+Size 84: 42,96 OK (HHHFFF)
+Size 85: 43,98 BAD (FFFFHH)
+Size 86: 43,99 OK (HHHFFF)
+Size 87: 44,100 BAD (FFFFHH)
+Size 88: 44,101 OK (HHHFFF)
+Size 89: 45,102 BAD (FFFFHH)
+Size 90: 45,103 OK (HHHFFF)
+Size 91: 46,105 BAD (FFFFHH)
+Size 92: 46,106 OK (HHHFFF)
+Size 93: 47,107 BAD (FFFFHH)
+Size 94: 47,108 OK (HHHFFF)
+Size 95: 48,109 BAD (FFFFHH)
+Size 96: 48,110 OK (HHHFFF)
+Size 97: 49,111 BAD (FFFFHH)
+Size 98: 49,113 OK (HHHFFF)
+Size 99: 50,114 BAD (FFFFHH)
+Size 100: 50,115 OK (HHHFFF)
+
+Windows 8
+---------
+
+Size 1: 1,2 OK (HHHFFF)
+Size 2: 1,2 OK (HHHFFF)
+Size 3: 2,3 BAD (FFFFHH)
+Size 4: 2,5 OK (HHHFFF)
+Size 5: 3,6 BAD (FFFFHH)
+Size 6: 3,7 OK (HHHFFF)
+Size 7: 4,8 BAD (FFFFHH)
+Size 8: 4,9 OK (HHHFFF)
+Size 9: 5,10 BAD (FFFFHH)
+Size 10: 5,11 OK (HHHFFF)
+Size 11: 6,13 BAD (FFFFHH)
+Size 12: 6,14 OK (HHHFFF)
+Size 13: 7,15 BAD (FFFFHH)
+Size 14: 7,16 OK (HHHFFF)
+Size 15: 8,17 BAD (FFFFHH)
+Size 16: 8,18 OK (HHHFFF)
+Size 17: 9,20 BAD (FFFFHH)
+Size 18: 9,21 OK (HHHFFF)
+Size 19: 10,22 BAD (FFFFHH)
+Size 20: 10,23 OK (HHHFFF)
+Size 21: 11,24 BAD (FFFFHH)
+Size 22: 11,25 OK (HHHFFF)
+Size 23: 12,26 BAD (FFFFHH)
+Size 24: 12,28 OK (HHHFFF)
+Size 25: 13,29 BAD (FFFFHH)
+Size 26: 13,30 OK (HHHFFF)
+Size 27: 14,31 BAD (FFFFHH)
+Size 28: 14,32 OK (HHHFFF)
+Size 29: 15,33 BAD (FFFFHH)
+Size 30: 15,34 OK (HHHFFF)
+Size 31: 16,36 BAD (FFFFHH)
+Size 32: 16,37 OK (HHHFFF)
+Size 33: 17,38 BAD (FFFFHH)
+Size 34: 17,39 OK (HHHFFF)
+Size 35: 18,40 BAD (FFFFHH)
+Size 36: 18,41 OK (HHHFFF)
+Size 37: 19,42 BAD (FFFFHH)
+Size 38: 19,44 OK (HHHFFF)
+Size 39: 20,45 BAD (FFFFHH)
+Size 40: 20,46 OK (HHHFFF)
+Size 41: 21,47 BAD (FFFFHH)
+Size 42: 21,48 OK (HHHFFF)
+Size 43: 22,49 BAD (FFFFHH)
+Size 44: 22,51 OK (HHHFFF)
+Size 45: 23,52 BAD (FFFFHH)
+Size 46: 23,53 OK (HHHFFF)
+Size 47: 24,54 BAD (FFFFHH)
+Size 48: 24,55 OK (HHHFFF)
+Size 49: 25,56 BAD (FFFFHH)
+Size 50: 25,57 OK (HHHFFF)
+Size 51: 26,59 BAD (FFFFHH)
+Size 52: 26,60 OK (HHHFFF)
+Size 53: 27,61 BAD (FFFFHH)
+Size 54: 27,62 OK (HHHFFF)
+Size 55: 28,63 BAD (FFFFHH)
+Size 56: 28,64 OK (HHHFFF)
+Size 57: 29,65 BAD (FFFFHH)
+Size 58: 29,67 OK (HHHFFF)
+Size 59: 30,68 BAD (FFFFHH)
+Size 60: 30,69 OK (HHHFFF)
+Size 61: 31,70 BAD (FFFFHH)
+Size 62: 31,71 OK (HHHFFF)
+Size 63: 32,72 BAD (FFFFHH)
+Size 64: 32,74 OK (HHHFFF)
+Size 65: 33,75 BAD (FFFFHH)
+Size 66: 33,76 OK (HHHFFF)
+Size 67: 34,77 BAD (FFFFHH)
+Size 68: 34,78 OK (HHHFFF)
+Size 69: 35,79 BAD (FFFFHH)
+Size 70: 35,80 OK (HHHFFF)
+Size 71: 36,82 BAD (FFFFHH)
+Size 72: 36,83 OK (HHHFFF)
+Size 73: 37,84 BAD (FFFFHH)
+Size 74: 37,85 OK (HHHFFF)
+Size 75: 38,86 BAD (FFFFHH)
+Size 76: 38,87 OK (HHHFFF)
+Size 77: 39,88 BAD (FFFFHH)
+Size 78: 39,90 OK (HHHFFF)
+Size 79: 40,91 BAD (FFFFHH)
+Size 80: 40,92 OK (HHHFFF)
+Size 81: 41,93 BAD (FFFFHH)
+Size 82: 41,94 OK (HHHFFF)
+Size 83: 42,95 BAD (FFFFHH)
+Size 84: 42,96 OK (HHHFFF)
+Size 85: 43,98 BAD (FFFFHH)
+Size 86: 43,99 OK (HHHFFF)
+Size 87: 44,100 BAD (FFFFHH)
+Size 88: 44,101 OK (HHHFFF)
+Size 89: 45,102 BAD (FFFFHH)
+Size 90: 45,103 OK (HHHFFF)
+Size 91: 46,105 BAD (FFFFHH)
+Size 92: 46,106 OK (HHHFFF)
+Size 93: 47,107 BAD (FFFFHH)
+Size 94: 47,108 OK (HHHFFF)
+Size 95: 48,109 BAD (FFFFHH)
+Size 96: 48,110 OK (HHHFFF)
+Size 97: 49,111 BAD (FFFFHH)
+Size 98: 49,113 OK (HHHFFF)
+Size 99: 50,114 BAD (FFFFHH)
+Size 100: 50,115 OK (HHHFFF)
+
+Windows 8.1
+-----------
+
+Size 1: 1,2 OK (HHHFFF)
+Size 2: 1,2 OK (HHHFFF)
+Size 3: 2,3 BAD (FFFFHH)
+Size 4: 2,5 OK (HHHFFF)
+Size 5: 3,6 BAD (FFFFHH)
+Size 6: 3,7 OK (HHHFFF)
+Size 7: 4,8 BAD (FFFFHH)
+Size 8: 4,9 OK (HHHFFF)
+Size 9: 5,10 BAD (FFFFHH)
+Size 10: 5,11 OK (HHHFFF)
+Size 11: 6,13 BAD (FFFFHH)
+Size 12: 6,14 OK (HHHFFF)
+Size 13: 7,15 BAD (FFFFHH)
+Size 14: 7,16 OK (HHHFFF)
+Size 15: 8,17 BAD (FFFFHH)
+Size 16: 8,18 OK (HHHFFF)
+Size 17: 9,20 BAD (FFFFHH)
+Size 18: 9,21 OK (HHHFFF)
+Size 19: 10,22 BAD (FFFFHH)
+Size 20: 10,23 OK (HHHFFF)
+Size 21: 11,24 BAD (FFFFHH)
+Size 22: 11,25 OK (HHHFFF)
+Size 23: 12,26 BAD (FFFFHH)
+Size 24: 12,28 OK (HHHFFF)
+Size 25: 13,29 BAD (FFFFHH)
+Size 26: 13,30 OK (HHHFFF)
+Size 27: 14,31 BAD (FFFFHH)
+Size 28: 14,32 OK (HHHFFF)
+Size 29: 15,33 BAD (FFFFHH)
+Size 30: 15,34 OK (HHHFFF)
+Size 31: 16,36 BAD (FFFFHH)
+Size 32: 16,37 OK (HHHFFF)
+Size 33: 17,38 BAD (FFFFHH)
+Size 34: 17,39 OK (HHHFFF)
+Size 35: 18,40 BAD (FFFFHH)
+Size 36: 18,41 OK (HHHFFF)
+Size 37: 19,42 BAD (FFFFHH)
+Size 38: 19,44 OK (HHHFFF)
+Size 39: 20,45 BAD (FFFFHH)
+Size 40: 20,46 OK (HHHFFF)
+Size 41: 21,47 BAD (FFFFHH)
+Size 42: 21,48 OK (HHHFFF)
+Size 43: 22,49 BAD (FFFFHH)
+Size 44: 22,51 OK (HHHFFF)
+Size 45: 23,52 BAD (FFFFHH)
+Size 46: 23,53 OK (HHHFFF)
+Size 47: 24,54 BAD (FFFFHH)
+Size 48: 24,55 OK (HHHFFF)
+Size 49: 25,56 BAD (FFFFHH)
+Size 50: 25,57 OK (HHHFFF)
+Size 51: 26,59 BAD (FFFFHH)
+Size 52: 26,60 OK (HHHFFF)
+Size 53: 27,61 BAD (FFFFHH)
+Size 54: 27,62 OK (HHHFFF)
+Size 55: 28,63 BAD (FFFFHH)
+Size 56: 28,64 OK (HHHFFF)
+Size 57: 29,65 BAD (FFFFHH)
+Size 58: 29,67 OK (HHHFFF)
+Size 59: 30,68 BAD (FFFFHH)
+Size 60: 30,69 OK (HHHFFF)
+Size 61: 31,70 BAD (FFFFHH)
+Size 62: 31,71 OK (HHHFFF)
+Size 63: 32,72 BAD (FFFFHH)
+Size 64: 32,74 OK (HHHFFF)
+Size 65: 33,75 BAD (FFFFHH)
+Size 66: 33,76 OK (HHHFFF)
+Size 67: 34,77 BAD (FFFFHH)
+Size 68: 34,78 OK (HHHFFF)
+Size 69: 35,79 BAD (FFFFHH)
+Size 70: 35,80 OK (HHHFFF)
+Size 71: 36,82 BAD (FFFFHH)
+Size 72: 36,83 OK (HHHFFF)
+Size 73: 37,84 BAD (FFFFHH)
+Size 74: 37,85 OK (HHHFFF)
+Size 75: 38,86 BAD (FFFFHH)
+Size 76: 38,87 OK (HHHFFF)
+Size 77: 39,88 BAD (FFFFHH)
+Size 78: 39,90 OK (HHHFFF)
+Size 79: 40,91 BAD (FFFFHH)
+Size 80: 40,92 OK (HHHFFF)
+Size 81: 41,93 BAD (FFFFHH)
+Size 82: 41,94 OK (HHHFFF)
+Size 83: 42,95 BAD (FFFFHH)
+Size 84: 42,96 OK (HHHFFF)
+Size 85: 43,98 BAD (FFFFHH)
+Size 86: 43,99 OK (HHHFFF)
+Size 87: 44,100 BAD (FFFFHH)
+Size 88: 44,101 OK (HHHFFF)
+Size 89: 45,102 BAD (FFFFHH)
+Size 90: 45,103 OK (HHHFFF)
+Size 91: 46,105 BAD (FFFFHH)
+Size 92: 46,106 OK (HHHFFF)
+Size 93: 47,107 BAD (FFFFHH)
+Size 94: 47,108 OK (HHHFFF)
+Size 95: 48,109 BAD (FFFFHH)
+Size 96: 48,110 OK (HHHFFF)
+Size 97: 49,111 BAD (FFFFHH)
+Size 98: 49,113 OK (HHHFFF)
+Size 99: 50,114 BAD (FFFFHH)
+Size 100: 50,115 OK (HHHFFF)
+
+Windows 10 14342 Old Console
+----------------------------
+
+Size 1: 1,2 OK (HHHFFF)
+Size 2: 1,2 OK (HHHFFF)
+Size 3: 2,3 BAD (FFFFHH)
+Size 4: 2,5 OK (HHHFFF)
+Size 5: 3,6 BAD (FFFFHH)
+Size 6: 3,7 OK (HHHFFF)
+Size 7: 4,8 BAD (FFFFHH)
+Size 8: 4,9 OK (HHHFFF)
+Size 9: 5,10 BAD (FFFFHH)
+Size 10: 5,11 OK (HHHFFF)
+Size 11: 6,13 BAD (FFFFHH)
+Size 12: 6,14 OK (HHHFFF)
+Size 13: 7,15 BAD (FFFFHH)
+Size 14: 7,16 OK (HHHFFF)
+Size 15: 8,17 BAD (FFFFHH)
+Size 16: 8,18 OK (HHHFFF)
+Size 17: 9,20 BAD (FFFFHH)
+Size 18: 9,21 OK (HHHFFF)
+Size 19: 10,22 BAD (FFFFHH)
+Size 20: 10,23 OK (HHHFFF)
+Size 21: 11,24 BAD (FFFFHH)
+Size 22: 11,25 OK (HHHFFF)
+Size 23: 12,26 BAD (FFFFHH)
+Size 24: 12,28 OK (HHHFFF)
+Size 25: 13,29 BAD (FFFFHH)
+Size 26: 13,30 OK (HHHFFF)
+Size 27: 14,31 BAD (FFFFHH)
+Size 28: 14,32 OK (HHHFFF)
+Size 29: 15,33 BAD (FFFFHH)
+Size 30: 15,34 OK (HHHFFF)
+Size 31: 16,36 BAD (FFFFHH)
+Size 32: 16,37 OK (HHHFFF)
+Size 33: 17,38 BAD (FFFFHH)
+Size 34: 17,39 OK (HHHFFF)
+Size 35: 18,40 BAD (FFFFHH)
+Size 36: 18,41 OK (HHHFFF)
+Size 37: 19,42 BAD (FFFFHH)
+Size 38: 19,44 OK (HHHFFF)
+Size 39: 20,45 BAD (FFFFHH)
+Size 40: 20,46 OK (HHHFFF)
+Size 41: 21,47 BAD (FFFFHH)
+Size 42: 21,48 OK (HHHFFF)
+Size 43: 22,49 BAD (FFFFHH)
+Size 44: 22,51 OK (HHHFFF)
+Size 45: 23,52 BAD (FFFFHH)
+Size 46: 23,53 OK (HHHFFF)
+Size 47: 24,54 BAD (FFFFHH)
+Size 48: 24,55 OK (HHHFFF)
+Size 49: 25,56 BAD (FFFFHH)
+Size 50: 25,57 OK (HHHFFF)
+Size 51: 26,59 BAD (FFFFHH)
+Size 52: 26,60 OK (HHHFFF)
+Size 53: 27,61 BAD (FFFFHH)
+Size 54: 27,62 OK (HHHFFF)
+Size 55: 28,63 BAD (FFFFHH)
+Size 56: 28,64 OK (HHHFFF)
+Size 57: 29,65 BAD (FFFFHH)
+Size 58: 29,67 OK (HHHFFF)
+Size 59: 30,68 BAD (FFFFHH)
+Size 60: 30,69 OK (HHHFFF)
+Size 61: 31,70 BAD (FFFFHH)
+Size 62: 31,71 OK (HHHFFF)
+Size 63: 32,72 BAD (FFFFHH)
+Size 64: 32,74 OK (HHHFFF)
+Size 65: 33,75 BAD (FFFFHH)
+Size 66: 33,76 OK (HHHFFF)
+Size 67: 34,77 BAD (FFFFHH)
+Size 68: 34,78 OK (HHHFFF)
+Size 69: 35,79 BAD (FFFFHH)
+Size 70: 35,80 OK (HHHFFF)
+Size 71: 36,82 BAD (FFFFHH)
+Size 72: 36,83 OK (HHHFFF)
+Size 73: 37,84 BAD (FFFFHH)
+Size 74: 37,85 OK (HHHFFF)
+Size 75: 38,86 BAD (FFFFHH)
+Size 76: 38,87 OK (HHHFFF)
+Size 77: 39,88 BAD (FFFFHH)
+Size 78: 39,90 OK (HHHFFF)
+Size 79: 40,91 BAD (FFFFHH)
+Size 80: 40,92 OK (HHHFFF)
+Size 81: 41,93 BAD (FFFFHH)
+Size 82: 41,94 OK (HHHFFF)
+Size 83: 42,95 BAD (FFFFHH)
+Size 84: 42,96 OK (HHHFFF)
+Size 85: 43,98 BAD (FFFFHH)
+Size 86: 43,99 OK (HHHFFF)
+Size 87: 44,100 BAD (FFFFHH)
+Size 88: 44,101 OK (HHHFFF)
+Size 89: 45,102 BAD (FFFFHH)
+Size 90: 45,103 OK (HHHFFF)
+Size 91: 46,105 BAD (FFFFHH)
+Size 92: 46,106 OK (HHHFFF)
+Size 93: 47,107 BAD (FFFFHH)
+Size 94: 47,108 OK (HHHFFF)
+Size 95: 48,109 BAD (FFFFHH)
+Size 96: 48,110 OK (HHHFFF)
+Size 97: 49,111 BAD (FFFFHH)
+Size 98: 49,113 OK (HHHFFF)
+Size 99: 50,114 BAD (FFFFHH)
+Size 100: 50,115 OK (HHHFFF)
+
+Windows 10 14342 New Console
+----------------------------
+
+Size 1: 1,1 OK (HHHFFF)
+Size 2: 1,2 OK (HHHFFF)
+Size 3: 2,3 OK (HHHFFF)
+Size 4: 2,4 OK (HHHFFF)
+Size 5: 3,5 OK (HHHFFF)
+Size 6: 3,6 OK (HHHFFF)
+Size 7: 4,7 OK (HHHFFF)
+Size 8: 4,8 OK (HHHFFF)
+Size 9: 5,9 OK (HHHFFF)
+Size 10: 5,10 OK (HHHFFF)
+Size 11: 6,11 OK (HHHFFF)
+Size 12: 6,12 OK (HHHFFF)
+Size 13: 7,13 OK (HHHFFF)
+Size 14: 7,14 OK (HHHFFF)
+Size 15: 8,15 OK (HHHFFF)
+Size 16: 8,16 OK (HHHFFF)
+Size 17: 9,17 OK (HHHFFF)
+Size 18: 9,18 OK (HHHFFF)
+Size 19: 10,19 OK (HHHFFF)
+Size 20: 10,20 OK (HHHFFF)
+Size 21: 11,21 OK (HHHFFF)
+Size 22: 11,22 OK (HHHFFF)
+Size 23: 12,23 OK (HHHFFF)
+Size 24: 12,24 OK (HHHFFF)
+Size 25: 13,25 OK (HHHFFF)
+Size 26: 13,26 OK (HHHFFF)
+Size 27: 14,27 OK (HHHFFF)
+Size 28: 14,28 OK (HHHFFF)
+Size 29: 15,29 OK (HHHFFF)
+Size 30: 15,30 OK (HHHFFF)
+Size 31: 16,31 OK (HHHFFF)
+Size 32: 16,32 OK (HHHFFF)
+Size 33: 17,33 OK (HHHFFF)
+Size 34: 17,34 OK (HHHFFF)
+Size 35: 18,35 OK (HHHFFF)
+Size 36: 18,36 OK (HHHFFF)
+Size 37: 19,37 OK (HHHFFF)
+Size 38: 19,38 OK (HHHFFF)
+Size 39: 20,39 OK (HHHFFF)
+Size 40: 20,40 OK (HHHFFF)
+Size 41: 21,41 OK (HHHFFF)
+Size 42: 21,42 OK (HHHFFF)
+Size 43: 22,43 OK (HHHFFF)
+Size 44: 22,44 OK (HHHFFF)
+Size 45: 23,45 OK (HHHFFF)
+Size 46: 23,46 OK (HHHFFF)
+Size 47: 24,47 OK (HHHFFF)
+Size 48: 24,48 OK (HHHFFF)
+Size 49: 25,49 OK (HHHFFF)
+Size 50: 25,50 OK (HHHFFF)
+Size 51: 26,51 OK (HHHFFF)
+Size 52: 26,52 OK (HHHFFF)
+Size 53: 27,53 OK (HHHFFF)
+Size 54: 27,54 OK (HHHFFF)
+Size 55: 28,55 OK (HHHFFF)
+Size 56: 28,56 OK (HHHFFF)
+Size 57: 29,57 OK (HHHFFF)
+Size 58: 29,58 OK (HHHFFF)
+Size 59: 30,59 OK (HHHFFF)
+Size 60: 30,60 OK (HHHFFF)
+Size 61: 31,61 OK (HHHFFF)
+Size 62: 31,62 OK (HHHFFF)
+Size 63: 32,63 OK (HHHFFF)
+Size 64: 32,64 OK (HHHFFF)
+Size 65: 33,65 OK (HHHFFF)
+Size 66: 33,66 OK (HHHFFF)
+Size 67: 34,67 OK (HHHFFF)
+Size 68: 34,68 OK (HHHFFF)
+Size 69: 35,69 OK (HHHFFF)
+Size 70: 35,70 OK (HHHFFF)
+Size 71: 36,71 OK (HHHFFF)
+Size 72: 36,72 OK (HHHFFF)
+Size 73: 37,73 OK (HHHFFF)
+Size 74: 37,74 OK (HHHFFF)
+Size 75: 38,75 OK (HHHFFF)
+Size 76: 38,76 OK (HHHFFF)
+Size 77: 39,77 OK (HHHFFF)
+Size 78: 39,78 OK (HHHFFF)
+Size 79: 40,79 OK (HHHFFF)
+Size 80: 40,80 OK (HHHFFF)
+Size 81: 41,81 OK (HHHFFF)
+Size 82: 41,82 OK (HHHFFF)
+Size 83: 42,83 OK (HHHFFF)
+Size 84: 42,84 OK (HHHFFF)
+Size 85: 43,85 OK (HHHFFF)
+Size 86: 43,86 OK (HHHFFF)
+Size 87: 44,87 OK (HHHFFF)
+Size 88: 44,88 OK (HHHFFF)
+Size 89: 45,89 OK (HHHFFF)
+Size 90: 45,90 OK (HHHFFF)
+Size 91: 46,91 OK (HHHFFF)
+Size 92: 46,92 OK (HHHFFF)
+Size 93: 47,93 OK (HHHFFF)
+Size 94: 47,94 OK (HHHFFF)
+Size 95: 48,95 OK (HHHFFF)
+Size 96: 48,96 OK (HHHFFF)
+Size 97: 49,97 OK (HHHFFF)
+Size 98: 49,98 OK (HHHFFF)
+Size 99: 50,99 OK (HHHFFF)
+Size 100: 50,100 OK (HHHFFF)
diff --git a/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP950.txt b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP950.txt
new file mode 100644
index 00000000000..0dbade504db
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/CP950.txt
@@ -0,0 +1,630 @@
+===========================================================
+Code Page 950, Chinese Traditional (Taiwan), MingLight font
+===========================================================
+
+Options: -face-minglight -family 0x36
+Chars: A2 A3 2014 3044 30FC 4000
+
+Vista
+-----
+
+Size 1: 1,2 GOOD (HHFFFF)
+Size 2: 1,2 GOOD (HHFFFF)
+Size 3: 2,4 BAD (FFHFHH)
+Size 4: 2,5 GOOD (HHFFFF)
+Size 5: 3,6 BAD (HHHFHH)
+Size 6: 3,7 GOOD (HHFFFF)
+Size 7: 4,8 BAD (HHHFHH)
+Size 8: 4,10 GOOD (HHFFFF)
+Size 9: 5,11 BAD (HHHFHH)
+Size 10: 5,12 GOOD (HHFFFF)
+Size 11: 6,13 BAD (HHHFHH)
+Size 12: 6,14 GOOD (HHFFFF)
+Size 13: 7,16 BAD (HHHFHH)
+Size 14: 7,17 GOOD (HHFFFF)
+Size 15: 8,18 BAD (HHHFHH)
+Size 16: 8,19 GOOD (HHFFFF)
+Size 17: 9,20 BAD (HHHFHH)
+Size 18: 9,22 GOOD (HHFFFF)
+Size 19: 10,23 BAD (HHHFHH)
+Size 20: 10,24 GOOD (HHFFFF)
+Size 21: 11,25 BAD (HHHFHH)
+Size 22: 11,26 GOOD (HHFFFF)
+Size 23: 12,28 BAD (HHHFHH)
+Size 24: 12,29 GOOD (HHFFFF)
+Size 25: 13,30 BAD (HHHFHH)
+Size 26: 13,31 GOOD (HHFFFF)
+Size 27: 14,32 BAD (HHHFHH)
+Size 28: 14,34 GOOD (HHFFFF)
+Size 29: 15,35 BAD (HHHFHH)
+Size 30: 15,36 GOOD (HHFFFF)
+Size 31: 16,37 BAD (HHHFHH)
+Size 32: 16,38 GOOD (HHFFFF)
+Size 33: 17,40 BAD (HHHFHH)
+Size 34: 17,41 GOOD (HHFFFF)
+Size 35: 18,42 BAD (HHHFHH)
+Size 36: 18,43 GOOD (HHFFFF)
+Size 37: 19,44 BAD (HHHFHH)
+Size 38: 19,46 GOOD (HHFFFF)
+Size 39: 20,47 BAD (HHHFHH)
+Size 40: 20,48 GOOD (HHFFFF)
+Size 41: 21,49 BAD (HHHFHH)
+Size 42: 21,50 GOOD (HHFFFF)
+Size 43: 22,52 BAD (HHHFHH)
+Size 44: 22,53 GOOD (HHFFFF)
+Size 45: 23,54 BAD (HHHFHH)
+Size 46: 23,55 GOOD (HHFFFF)
+Size 47: 24,56 BAD (HHHFHH)
+Size 48: 24,58 GOOD (HHFFFF)
+Size 49: 25,59 BAD (HHHFHH)
+Size 50: 25,60 GOOD (HHFFFF)
+Size 51: 26,61 BAD (HHHFHH)
+Size 52: 26,62 GOOD (HHFFFF)
+Size 53: 27,64 BAD (HHHFHH)
+Size 54: 27,65 GOOD (HHFFFF)
+Size 55: 28,66 BAD (HHHFHH)
+Size 56: 28,67 GOOD (HHFFFF)
+Size 57: 29,68 BAD (HHHFHH)
+Size 58: 29,70 GOOD (HHFFFF)
+Size 59: 30,71 BAD (HHHFHH)
+Size 60: 30,72 GOOD (HHFFFF)
+Size 61: 31,73 BAD (HHHFHH)
+Size 62: 31,74 GOOD (HHFFFF)
+Size 63: 32,76 BAD (HHHFHH)
+Size 64: 32,77 GOOD (HHFFFF)
+Size 65: 33,78 BAD (HHHFHH)
+Size 66: 33,79 GOOD (HHFFFF)
+Size 67: 34,80 BAD (HHHFHH)
+Size 68: 34,82 GOOD (HHFFFF)
+Size 69: 35,83 BAD (HHHFHH)
+Size 70: 35,84 GOOD (HHFFFF)
+Size 71: 36,85 BAD (HHHFHH)
+Size 72: 36,86 GOOD (HHFFFF)
+Size 73: 37,88 BAD (HHHFHH)
+Size 74: 37,89 GOOD (HHFFFF)
+Size 75: 38,90 BAD (HHHFHH)
+Size 76: 38,91 GOOD (HHFFFF)
+Size 77: 39,92 BAD (HHHFHH)
+Size 78: 39,94 GOOD (HHFFFF)
+Size 79: 40,95 BAD (HHHFHH)
+Size 80: 40,96 GOOD (HHFFFF)
+Size 81: 41,97 BAD (HHHFHH)
+Size 82: 41,98 GOOD (HHFFFF)
+Size 83: 42,100 BAD (HHHFHH)
+Size 84: 42,101 GOOD (HHFFFF)
+Size 85: 43,102 BAD (HHHFHH)
+Size 86: 43,103 GOOD (HHFFFF)
+Size 87: 44,104 BAD (HHHFHH)
+Size 88: 44,106 GOOD (HHFFFF)
+Size 89: 45,107 BAD (HHHFHH)
+Size 90: 45,108 GOOD (HHFFFF)
+Size 91: 46,109 BAD (HHHFHH)
+Size 92: 46,110 GOOD (HHFFFF)
+Size 93: 47,112 BAD (HHHFHH)
+Size 94: 47,113 GOOD (HHFFFF)
+Size 95: 48,114 BAD (HHHFHH)
+Size 96: 48,115 GOOD (HHFFFF)
+Size 97: 49,116 BAD (HHHFHH)
+Size 98: 49,118 GOOD (HHFFFF)
+Size 99: 50,119 BAD (HHHFHH)
+Size 100: 50,120 GOOD (HHFFFF)
+
+Windows 7
+---------
+
+Size 1: 1,2 GOOD (HHFFFF)
+Size 2: 1,2 GOOD (HHFFFF)
+Size 3: 2,4 BAD (FFHFHH)
+Size 4: 2,5 GOOD (HHFFFF)
+Size 5: 3,6 BAD (FFHFHH)
+Size 6: 3,7 GOOD (HHFFFF)
+Size 7: 4,8 BAD (FFHFHH)
+Size 8: 4,10 GOOD (HHFFFF)
+Size 9: 5,11 BAD (FFHFHH)
+Size 10: 5,12 GOOD (HHFFFF)
+Size 11: 6,13 BAD (FFHFHH)
+Size 12: 6,14 GOOD (HHFFFF)
+Size 13: 7,16 BAD (FFHFHH)
+Size 14: 7,17 GOOD (HHFFFF)
+Size 15: 8,18 BAD (FFHFHH)
+Size 16: 8,19 GOOD (HHFFFF)
+Size 17: 9,20 BAD (FFHFHH)
+Size 18: 9,22 GOOD (HHFFFF)
+Size 19: 10,23 BAD (FFHFHH)
+Size 20: 10,24 GOOD (HHFFFF)
+Size 21: 11,25 BAD (FFHFHH)
+Size 22: 11,26 GOOD (HHFFFF)
+Size 23: 12,28 BAD (FFHFHH)
+Size 24: 12,29 GOOD (HHFFFF)
+Size 25: 13,30 BAD (FFHFHH)
+Size 26: 13,31 GOOD (HHFFFF)
+Size 27: 14,32 BAD (FFHFHH)
+Size 28: 14,34 GOOD (HHFFFF)
+Size 29: 15,35 BAD (FFHFHH)
+Size 30: 15,36 GOOD (HHFFFF)
+Size 31: 16,37 BAD (FFHFHH)
+Size 32: 16,38 GOOD (HHFFFF)
+Size 33: 17,40 BAD (FFHFHH)
+Size 34: 17,41 GOOD (HHFFFF)
+Size 35: 18,42 BAD (FFHFHH)
+Size 36: 18,43 GOOD (HHFFFF)
+Size 37: 19,44 BAD (FFHFHH)
+Size 38: 19,46 GOOD (HHFFFF)
+Size 39: 20,47 BAD (FFHFHH)
+Size 40: 20,48 GOOD (HHFFFF)
+Size 41: 21,49 BAD (FFHFHH)
+Size 42: 21,50 GOOD (HHFFFF)
+Size 43: 22,52 BAD (FFHFHH)
+Size 44: 22,53 GOOD (HHFFFF)
+Size 45: 23,54 BAD (FFHFHH)
+Size 46: 23,55 GOOD (HHFFFF)
+Size 47: 24,56 BAD (FFHFHH)
+Size 48: 24,58 GOOD (HHFFFF)
+Size 49: 25,59 BAD (FFHFHH)
+Size 50: 25,60 GOOD (HHFFFF)
+Size 51: 26,61 BAD (FFHFHH)
+Size 52: 26,62 GOOD (HHFFFF)
+Size 53: 27,64 BAD (FFHFHH)
+Size 54: 27,65 GOOD (HHFFFF)
+Size 55: 28,66 BAD (FFHFHH)
+Size 56: 28,67 GOOD (HHFFFF)
+Size 57: 29,68 BAD (FFHFHH)
+Size 58: 29,70 GOOD (HHFFFF)
+Size 59: 30,71 BAD (FFHFHH)
+Size 60: 30,72 GOOD (HHFFFF)
+Size 61: 31,73 BAD (FFHFHH)
+Size 62: 31,74 GOOD (HHFFFF)
+Size 63: 32,76 BAD (FFHFHH)
+Size 64: 32,77 GOOD (HHFFFF)
+Size 65: 33,78 BAD (FFHFHH)
+Size 66: 33,79 GOOD (HHFFFF)
+Size 67: 34,80 BAD (FFHFHH)
+Size 68: 34,82 GOOD (HHFFFF)
+Size 69: 35,83 BAD (FFHFHH)
+Size 70: 35,84 GOOD (HHFFFF)
+Size 71: 36,85 BAD (FFHFHH)
+Size 72: 36,86 GOOD (HHFFFF)
+Size 73: 37,88 BAD (FFHFHH)
+Size 74: 37,89 GOOD (HHFFFF)
+Size 75: 38,90 BAD (FFHFHH)
+Size 76: 38,91 GOOD (HHFFFF)
+Size 77: 39,92 BAD (FFHFHH)
+Size 78: 39,94 GOOD (HHFFFF)
+Size 79: 40,95 BAD (FFHFHH)
+Size 80: 40,96 GOOD (HHFFFF)
+Size 81: 41,97 BAD (FFHFHH)
+Size 82: 41,98 GOOD (HHFFFF)
+Size 83: 42,100 BAD (FFHFHH)
+Size 84: 42,101 GOOD (HHFFFF)
+Size 85: 43,102 BAD (FFHFHH)
+Size 86: 43,103 GOOD (HHFFFF)
+Size 87: 44,104 BAD (FFHFHH)
+Size 88: 44,106 GOOD (HHFFFF)
+Size 89: 45,107 BAD (FFHFHH)
+Size 90: 45,108 GOOD (HHFFFF)
+Size 91: 46,109 BAD (FFHFHH)
+Size 92: 46,110 GOOD (HHFFFF)
+Size 93: 47,112 BAD (FFHFHH)
+Size 94: 47,113 GOOD (HHFFFF)
+Size 95: 48,114 BAD (FFHFHH)
+Size 96: 48,115 GOOD (HHFFFF)
+Size 97: 49,116 BAD (FFHFHH)
+Size 98: 49,118 GOOD (HHFFFF)
+Size 99: 50,119 BAD (FFHFHH)
+Size 100: 50,120 GOOD (HHFFFF)
+
+Windows 8
+---------
+
+Size 1: 1,2 GOOD (HHFFFF)
+Size 2: 1,2 GOOD (HHFFFF)
+Size 3: 2,4 BAD (FFHFHH)
+Size 4: 2,5 GOOD (HHFFFF)
+Size 5: 3,6 BAD (FFHFHH)
+Size 6: 3,7 GOOD (HHFFFF)
+Size 7: 4,8 BAD (FFHFHH)
+Size 8: 4,10 GOOD (HHFFFF)
+Size 9: 5,11 BAD (FFHFHH)
+Size 10: 5,12 GOOD (HHFFFF)
+Size 11: 6,13 BAD (FFHFHH)
+Size 12: 6,14 GOOD (HHFFFF)
+Size 13: 7,16 BAD (FFHFHH)
+Size 14: 7,17 GOOD (HHFFFF)
+Size 15: 8,18 BAD (FFHFHH)
+Size 16: 8,19 GOOD (HHFFFF)
+Size 17: 9,20 BAD (FFHFHH)
+Size 18: 9,22 GOOD (HHFFFF)
+Size 19: 10,23 BAD (FFHFHH)
+Size 20: 10,24 GOOD (HHFFFF)
+Size 21: 11,25 BAD (FFHFHH)
+Size 22: 11,26 GOOD (HHFFFF)
+Size 23: 12,28 BAD (FFHFHH)
+Size 24: 12,29 GOOD (HHFFFF)
+Size 25: 13,30 BAD (FFHFHH)
+Size 26: 13,31 GOOD (HHFFFF)
+Size 27: 14,32 BAD (FFHFHH)
+Size 28: 14,34 GOOD (HHFFFF)
+Size 29: 15,35 BAD (FFHFHH)
+Size 30: 15,36 GOOD (HHFFFF)
+Size 31: 16,37 BAD (FFHFHH)
+Size 32: 16,38 GOOD (HHFFFF)
+Size 33: 17,40 BAD (FFHFHH)
+Size 34: 17,41 GOOD (HHFFFF)
+Size 35: 18,42 BAD (FFHFHH)
+Size 36: 18,43 GOOD (HHFFFF)
+Size 37: 19,44 BAD (FFHFHH)
+Size 38: 19,46 GOOD (HHFFFF)
+Size 39: 20,47 BAD (FFHFHH)
+Size 40: 20,48 GOOD (HHFFFF)
+Size 41: 21,49 BAD (FFHFHH)
+Size 42: 21,50 GOOD (HHFFFF)
+Size 43: 22,52 BAD (FFHFHH)
+Size 44: 22,53 GOOD (HHFFFF)
+Size 45: 23,54 BAD (FFHFHH)
+Size 46: 23,55 GOOD (HHFFFF)
+Size 47: 24,56 BAD (FFHFHH)
+Size 48: 24,58 GOOD (HHFFFF)
+Size 49: 25,59 BAD (FFHFHH)
+Size 50: 25,60 GOOD (HHFFFF)
+Size 51: 26,61 BAD (FFHFHH)
+Size 52: 26,62 GOOD (HHFFFF)
+Size 53: 27,64 BAD (FFHFHH)
+Size 54: 27,65 GOOD (HHFFFF)
+Size 55: 28,66 BAD (FFHFHH)
+Size 56: 28,67 GOOD (HHFFFF)
+Size 57: 29,68 BAD (FFHFHH)
+Size 58: 29,70 GOOD (HHFFFF)
+Size 59: 30,71 BAD (FFHFHH)
+Size 60: 30,72 GOOD (HHFFFF)
+Size 61: 31,73 BAD (FFHFHH)
+Size 62: 31,74 GOOD (HHFFFF)
+Size 63: 32,76 BAD (FFHFHH)
+Size 64: 32,77 GOOD (HHFFFF)
+Size 65: 33,78 BAD (FFHFHH)
+Size 66: 33,79 GOOD (HHFFFF)
+Size 67: 34,80 BAD (FFHFHH)
+Size 68: 34,82 GOOD (HHFFFF)
+Size 69: 35,83 BAD (FFHFHH)
+Size 70: 35,84 GOOD (HHFFFF)
+Size 71: 36,85 BAD (FFHFHH)
+Size 72: 36,86 GOOD (HHFFFF)
+Size 73: 37,88 BAD (FFHFHH)
+Size 74: 37,89 GOOD (HHFFFF)
+Size 75: 38,90 BAD (FFHFHH)
+Size 76: 38,91 GOOD (HHFFFF)
+Size 77: 39,92 BAD (FFHFHH)
+Size 78: 39,94 GOOD (HHFFFF)
+Size 79: 40,95 BAD (FFHFHH)
+Size 80: 40,96 GOOD (HHFFFF)
+Size 81: 41,97 BAD (FFHFHH)
+Size 82: 41,98 GOOD (HHFFFF)
+Size 83: 42,100 BAD (FFHFHH)
+Size 84: 42,101 GOOD (HHFFFF)
+Size 85: 43,102 BAD (FFHFHH)
+Size 86: 43,103 GOOD (HHFFFF)
+Size 87: 44,104 BAD (FFHFHH)
+Size 88: 44,106 GOOD (HHFFFF)
+Size 89: 45,107 BAD (FFHFHH)
+Size 90: 45,108 GOOD (HHFFFF)
+Size 91: 46,109 BAD (FFHFHH)
+Size 92: 46,110 GOOD (HHFFFF)
+Size 93: 47,112 BAD (FFHFHH)
+Size 94: 47,113 GOOD (HHFFFF)
+Size 95: 48,114 BAD (FFHFHH)
+Size 96: 48,115 GOOD (HHFFFF)
+Size 97: 49,116 BAD (FFHFHH)
+Size 98: 49,118 GOOD (HHFFFF)
+Size 99: 50,119 BAD (FFHFHH)
+Size 100: 50,120 GOOD (HHFFFF)
+
+Windows 8.1
+-----------
+
+Size 1: 1,2 GOOD (HHFFFF)
+Size 2: 1,2 GOOD (HHFFFF)
+Size 3: 2,4 BAD (FFHFHH)
+Size 4: 2,5 GOOD (HHFFFF)
+Size 5: 3,6 BAD (FFHFHH)
+Size 6: 3,7 GOOD (HHFFFF)
+Size 7: 4,8 BAD (FFHFHH)
+Size 8: 4,10 GOOD (HHFFFF)
+Size 9: 5,11 BAD (FFHFHH)
+Size 10: 5,12 GOOD (HHFFFF)
+Size 11: 6,13 BAD (FFHFHH)
+Size 12: 6,14 GOOD (HHFFFF)
+Size 13: 7,16 BAD (FFHFHH)
+Size 14: 7,17 GOOD (HHFFFF)
+Size 15: 8,18 BAD (FFHFHH)
+Size 16: 8,19 GOOD (HHFFFF)
+Size 17: 9,20 BAD (FFHFHH)
+Size 18: 9,22 GOOD (HHFFFF)
+Size 19: 10,23 BAD (FFHFHH)
+Size 20: 10,24 GOOD (HHFFFF)
+Size 21: 11,25 BAD (FFHFHH)
+Size 22: 11,26 GOOD (HHFFFF)
+Size 23: 12,28 BAD (FFHFHH)
+Size 24: 12,29 GOOD (HHFFFF)
+Size 25: 13,30 BAD (FFHFHH)
+Size 26: 13,31 GOOD (HHFFFF)
+Size 27: 14,32 BAD (FFHFHH)
+Size 28: 14,34 GOOD (HHFFFF)
+Size 29: 15,35 BAD (FFHFHH)
+Size 30: 15,36 GOOD (HHFFFF)
+Size 31: 16,37 BAD (FFHFHH)
+Size 32: 16,38 GOOD (HHFFFF)
+Size 33: 17,40 BAD (FFHFHH)
+Size 34: 17,41 GOOD (HHFFFF)
+Size 35: 18,42 BAD (FFHFHH)
+Size 36: 18,43 GOOD (HHFFFF)
+Size 37: 19,44 BAD (FFHFHH)
+Size 38: 19,46 GOOD (HHFFFF)
+Size 39: 20,47 BAD (FFHFHH)
+Size 40: 20,48 GOOD (HHFFFF)
+Size 41: 21,49 BAD (FFHFHH)
+Size 42: 21,50 GOOD (HHFFFF)
+Size 43: 22,52 BAD (FFHFHH)
+Size 44: 22,53 GOOD (HHFFFF)
+Size 45: 23,54 BAD (FFHFHH)
+Size 46: 23,55 GOOD (HHFFFF)
+Size 47: 24,56 BAD (FFHFHH)
+Size 48: 24,58 GOOD (HHFFFF)
+Size 49: 25,59 BAD (FFHFHH)
+Size 50: 25,60 GOOD (HHFFFF)
+Size 51: 26,61 BAD (FFHFHH)
+Size 52: 26,62 GOOD (HHFFFF)
+Size 53: 27,64 BAD (FFHFHH)
+Size 54: 27,65 GOOD (HHFFFF)
+Size 55: 28,66 BAD (FFHFHH)
+Size 56: 28,67 GOOD (HHFFFF)
+Size 57: 29,68 BAD (FFHFHH)
+Size 58: 29,70 GOOD (HHFFFF)
+Size 59: 30,71 BAD (FFHFHH)
+Size 60: 30,72 GOOD (HHFFFF)
+Size 61: 31,73 BAD (FFHFHH)
+Size 62: 31,74 GOOD (HHFFFF)
+Size 63: 32,76 BAD (FFHFHH)
+Size 64: 32,77 GOOD (HHFFFF)
+Size 65: 33,78 BAD (FFHFHH)
+Size 66: 33,79 GOOD (HHFFFF)
+Size 67: 34,80 BAD (FFHFHH)
+Size 68: 34,82 GOOD (HHFFFF)
+Size 69: 35,83 BAD (FFHFHH)
+Size 70: 35,84 GOOD (HHFFFF)
+Size 71: 36,85 BAD (FFHFHH)
+Size 72: 36,86 GOOD (HHFFFF)
+Size 73: 37,88 BAD (FFHFHH)
+Size 74: 37,89 GOOD (HHFFFF)
+Size 75: 38,90 BAD (FFHFHH)
+Size 76: 38,91 GOOD (HHFFFF)
+Size 77: 39,92 BAD (FFHFHH)
+Size 78: 39,94 GOOD (HHFFFF)
+Size 79: 40,95 BAD (FFHFHH)
+Size 80: 40,96 GOOD (HHFFFF)
+Size 81: 41,97 BAD (FFHFHH)
+Size 82: 41,98 GOOD (HHFFFF)
+Size 83: 42,100 BAD (FFHFHH)
+Size 84: 42,101 GOOD (HHFFFF)
+Size 85: 43,102 BAD (FFHFHH)
+Size 86: 43,103 GOOD (HHFFFF)
+Size 87: 44,104 BAD (FFHFHH)
+Size 88: 44,106 GOOD (HHFFFF)
+Size 89: 45,107 BAD (FFHFHH)
+Size 90: 45,108 GOOD (HHFFFF)
+Size 91: 46,109 BAD (FFHFHH)
+Size 92: 46,110 GOOD (HHFFFF)
+Size 93: 47,112 BAD (FFHFHH)
+Size 94: 47,113 GOOD (HHFFFF)
+Size 95: 48,114 BAD (FFHFHH)
+Size 96: 48,115 GOOD (HHFFFF)
+Size 97: 49,116 BAD (FFHFHH)
+Size 98: 49,118 GOOD (HHFFFF)
+Size 99: 50,119 BAD (FFHFHH)
+Size 100: 50,120 GOOD (HHFFFF)
+
+Windows 10 14342 Old Console
+----------------------------
+
+Size 1: 1,2 GOOD (HHFFFF)
+Size 2: 1,2 GOOD (HHFFFF)
+Size 3: 2,4 BAD (FFHFHH)
+Size 4: 2,5 GOOD (HHFFFF)
+Size 5: 3,6 BAD (FFHFHH)
+Size 6: 3,7 GOOD (HHFFFF)
+Size 7: 4,8 BAD (FFHFHH)
+Size 8: 4,10 GOOD (HHFFFF)
+Size 9: 5,11 BAD (FFHFHH)
+Size 10: 5,12 GOOD (HHFFFF)
+Size 11: 6,13 BAD (FFHFHH)
+Size 12: 6,14 GOOD (HHFFFF)
+Size 13: 7,16 BAD (FFHFHH)
+Size 14: 7,17 GOOD (HHFFFF)
+Size 15: 8,18 BAD (FFHFHH)
+Size 16: 8,19 GOOD (HHFFFF)
+Size 17: 9,20 BAD (FFHFHH)
+Size 18: 9,22 GOOD (HHFFFF)
+Size 19: 10,23 BAD (FFHFHH)
+Size 20: 10,24 GOOD (HHFFFF)
+Size 21: 11,25 BAD (FFHFHH)
+Size 22: 11,26 GOOD (HHFFFF)
+Size 23: 12,28 BAD (FFHFHH)
+Size 24: 12,29 GOOD (HHFFFF)
+Size 25: 13,30 BAD (FFHFHH)
+Size 26: 13,31 GOOD (HHFFFF)
+Size 27: 14,32 BAD (FFHFHH)
+Size 28: 14,34 GOOD (HHFFFF)
+Size 29: 15,35 BAD (FFHFHH)
+Size 30: 15,36 GOOD (HHFFFF)
+Size 31: 16,37 BAD (FFHFHH)
+Size 32: 16,38 GOOD (HHFFFF)
+Size 33: 17,40 BAD (FFHFHH)
+Size 34: 17,41 GOOD (HHFFFF)
+Size 35: 18,42 BAD (FFHFHH)
+Size 36: 18,43 GOOD (HHFFFF)
+Size 37: 19,44 BAD (FFHFHH)
+Size 38: 19,46 GOOD (HHFFFF)
+Size 39: 20,47 BAD (FFHFHH)
+Size 40: 20,48 GOOD (HHFFFF)
+Size 41: 21,49 BAD (FFHFHH)
+Size 42: 21,50 GOOD (HHFFFF)
+Size 43: 22,52 BAD (FFHFHH)
+Size 44: 22,53 GOOD (HHFFFF)
+Size 45: 23,54 BAD (FFHFHH)
+Size 46: 23,55 GOOD (HHFFFF)
+Size 47: 24,56 BAD (FFHFHH)
+Size 48: 24,58 GOOD (HHFFFF)
+Size 49: 25,59 BAD (FFHFHH)
+Size 50: 25,60 GOOD (HHFFFF)
+Size 51: 26,61 BAD (FFHFHH)
+Size 52: 26,62 GOOD (HHFFFF)
+Size 53: 27,64 BAD (FFHFHH)
+Size 54: 27,65 GOOD (HHFFFF)
+Size 55: 28,66 BAD (FFHFHH)
+Size 56: 28,67 GOOD (HHFFFF)
+Size 57: 29,68 BAD (FFHFHH)
+Size 58: 29,70 GOOD (HHFFFF)
+Size 59: 30,71 BAD (FFHFHH)
+Size 60: 30,72 GOOD (HHFFFF)
+Size 61: 31,73 BAD (FFHFHH)
+Size 62: 31,74 GOOD (HHFFFF)
+Size 63: 32,76 BAD (FFHFHH)
+Size 64: 32,77 GOOD (HHFFFF)
+Size 65: 33,78 BAD (FFHFHH)
+Size 66: 33,79 GOOD (HHFFFF)
+Size 67: 34,80 BAD (FFHFHH)
+Size 68: 34,82 GOOD (HHFFFF)
+Size 69: 35,83 BAD (FFHFHH)
+Size 70: 35,84 GOOD (HHFFFF)
+Size 71: 36,85 BAD (FFHFHH)
+Size 72: 36,86 GOOD (HHFFFF)
+Size 73: 37,88 BAD (FFHFHH)
+Size 74: 37,89 GOOD (HHFFFF)
+Size 75: 38,90 BAD (FFHFHH)
+Size 76: 38,91 GOOD (HHFFFF)
+Size 77: 39,92 BAD (FFHFHH)
+Size 78: 39,94 GOOD (HHFFFF)
+Size 79: 40,95 BAD (FFHFHH)
+Size 80: 40,96 GOOD (HHFFFF)
+Size 81: 41,97 BAD (FFHFHH)
+Size 82: 41,98 GOOD (HHFFFF)
+Size 83: 42,100 BAD (FFHFHH)
+Size 84: 42,101 GOOD (HHFFFF)
+Size 85: 43,102 BAD (FFHFHH)
+Size 86: 43,103 GOOD (HHFFFF)
+Size 87: 44,104 BAD (FFHFHH)
+Size 88: 44,106 GOOD (HHFFFF)
+Size 89: 45,107 BAD (FFHFHH)
+Size 90: 45,108 GOOD (HHFFFF)
+Size 91: 46,109 BAD (FFHFHH)
+Size 92: 46,110 GOOD (HHFFFF)
+Size 93: 47,112 BAD (FFHFHH)
+Size 94: 47,113 GOOD (HHFFFF)
+Size 95: 48,114 BAD (FFHFHH)
+Size 96: 48,115 GOOD (HHFFFF)
+Size 97: 49,116 BAD (FFHFHH)
+Size 98: 49,118 GOOD (HHFFFF)
+Size 99: 50,119 BAD (FFHFHH)
+Size 100: 50,120 GOOD (HHFFFF)
+
+Windows 10 14342 New Console
+----------------------------
+
+Size 1: 1,1 GOOD (HHFFFF)
+Size 2: 1,2 GOOD (HHFFFF)
+Size 3: 2,3 GOOD (HHFFFF)
+Size 4: 2,4 GOOD (HHFFFF)
+Size 5: 3,5 GOOD (HHFFFF)
+Size 6: 3,6 GOOD (HHFFFF)
+Size 7: 4,7 GOOD (HHFFFF)
+Size 8: 4,8 GOOD (HHFFFF)
+Size 9: 5,9 GOOD (HHFFFF)
+Size 10: 5,10 GOOD (HHFFFF)
+Size 11: 6,11 GOOD (HHFFFF)
+Size 12: 6,12 GOOD (HHFFFF)
+Size 13: 7,13 GOOD (HHFFFF)
+Size 14: 7,14 GOOD (HHFFFF)
+Size 15: 8,15 GOOD (HHFFFF)
+Size 16: 8,16 GOOD (HHFFFF)
+Size 17: 9,17 GOOD (HHFFFF)
+Size 18: 9,18 GOOD (HHFFFF)
+Size 19: 10,19 GOOD (HHFFFF)
+Size 20: 10,20 GOOD (HHFFFF)
+Size 21: 11,21 GOOD (HHFFFF)
+Size 22: 11,22 GOOD (HHFFFF)
+Size 23: 12,23 GOOD (HHFFFF)
+Size 24: 12,24 GOOD (HHFFFF)
+Size 25: 13,25 GOOD (HHFFFF)
+Size 26: 13,26 GOOD (HHFFFF)
+Size 27: 14,27 GOOD (HHFFFF)
+Size 28: 14,28 GOOD (HHFFFF)
+Size 29: 15,29 GOOD (HHFFFF)
+Size 30: 15,30 GOOD (HHFFFF)
+Size 31: 16,31 GOOD (HHFFFF)
+Size 32: 16,32 GOOD (HHFFFF)
+Size 33: 17,33 GOOD (HHFFFF)
+Size 34: 17,34 GOOD (HHFFFF)
+Size 35: 18,35 GOOD (HHFFFF)
+Size 36: 18,36 GOOD (HHFFFF)
+Size 37: 19,37 GOOD (HHFFFF)
+Size 38: 19,38 GOOD (HHFFFF)
+Size 39: 20,39 GOOD (HHFFFF)
+Size 40: 20,40 GOOD (HHFFFF)
+Size 41: 21,41 GOOD (HHFFFF)
+Size 42: 21,42 GOOD (HHFFFF)
+Size 43: 22,43 GOOD (HHFFFF)
+Size 44: 22,44 GOOD (HHFFFF)
+Size 45: 23,45 GOOD (HHFFFF)
+Size 46: 23,46 GOOD (HHFFFF)
+Size 47: 24,47 GOOD (HHFFFF)
+Size 48: 24,48 GOOD (HHFFFF)
+Size 49: 25,49 GOOD (HHFFFF)
+Size 50: 25,50 GOOD (HHFFFF)
+Size 51: 26,51 GOOD (HHFFFF)
+Size 52: 26,52 GOOD (HHFFFF)
+Size 53: 27,53 GOOD (HHFFFF)
+Size 54: 27,54 GOOD (HHFFFF)
+Size 55: 28,55 GOOD (HHFFFF)
+Size 56: 28,56 GOOD (HHFFFF)
+Size 57: 29,57 GOOD (HHFFFF)
+Size 58: 29,58 GOOD (HHFFFF)
+Size 59: 30,59 GOOD (HHFFFF)
+Size 60: 30,60 GOOD (HHFFFF)
+Size 61: 31,61 GOOD (HHFFFF)
+Size 62: 31,62 GOOD (HHFFFF)
+Size 63: 32,63 GOOD (HHFFFF)
+Size 64: 32,64 GOOD (HHFFFF)
+Size 65: 33,65 GOOD (HHFFFF)
+Size 66: 33,66 GOOD (HHFFFF)
+Size 67: 34,67 GOOD (HHFFFF)
+Size 68: 34,68 GOOD (HHFFFF)
+Size 69: 35,69 GOOD (HHFFFF)
+Size 70: 35,70 GOOD (HHFFFF)
+Size 71: 36,71 GOOD (HHFFFF)
+Size 72: 36,72 GOOD (HHFFFF)
+Size 73: 37,73 GOOD (HHFFFF)
+Size 74: 37,74 GOOD (HHFFFF)
+Size 75: 38,75 GOOD (HHFFFF)
+Size 76: 38,76 GOOD (HHFFFF)
+Size 77: 39,77 GOOD (HHFFFF)
+Size 78: 39,78 GOOD (HHFFFF)
+Size 79: 40,79 GOOD (HHFFFF)
+Size 80: 40,80 GOOD (HHFFFF)
+Size 81: 41,81 GOOD (HHFFFF)
+Size 82: 41,82 GOOD (HHFFFF)
+Size 83: 42,83 GOOD (HHFFFF)
+Size 84: 42,84 GOOD (HHFFFF)
+Size 85: 43,85 GOOD (HHFFFF)
+Size 86: 43,86 GOOD (HHFFFF)
+Size 87: 44,87 GOOD (HHFFFF)
+Size 88: 44,88 GOOD (HHFFFF)
+Size 89: 45,89 GOOD (HHFFFF)
+Size 90: 45,90 GOOD (HHFFFF)
+Size 91: 46,91 GOOD (HHFFFF)
+Size 92: 46,92 GOOD (HHFFFF)
+Size 93: 47,93 GOOD (HHFFFF)
+Size 94: 47,94 GOOD (HHFFFF)
+Size 95: 48,95 GOOD (HHFFFF)
+Size 96: 48,96 GOOD (HHFFFF)
+Size 97: 49,97 GOOD (HHFFFF)
+Size 98: 49,98 GOOD (HHFFFF)
+Size 99: 50,99 GOOD (HHFFFF)
+Size 100: 50,100 GOOD (HHFFFF)
diff --git a/src/libs/3rdparty/winpty/misc/Font-Report-June2016/MinimumWindowWidths.txt b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/MinimumWindowWidths.txt
new file mode 100644
index 00000000000..d5261d8db39
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/MinimumWindowWidths.txt
@@ -0,0 +1,16 @@
+The narrowest allowed console window, in pixels, on a conventional (~96dpi)
+monitor:
+
+(mode con: cols=40 lines=40) && SetFont.exe -face "Lucida Console" -h 1 && (ping -n 4 127.0.0.1 > NUL) && cls && GetConsolePos.exe && SetFont.exe -face "Lucida Console" -h 12
+
+(mode con: cols=40 lines=40) && SetFont.exe -face "Lucida Console" -h 16 && (ping -n 4 127.0.0.1 > NUL) && cls && GetConsolePos.exe && SetFont.exe -face "Lucida Console" -h 12
+
+ sz1:px sz1:col sz16:px sz16:col
+Vista: 124 104 137 10
+Windows 7: 132 112 147 11
+Windows 8: 140 120 147 11
+Windows 8.1: 140 120 147 11
+Windows 10 OLD: 136 116 147 11
+Windows 10 NEW: 136 103 136 10
+
+I used build 14342 to test Windows 10.
diff --git a/src/libs/3rdparty/winpty/misc/Font-Report-June2016/Results.txt b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/Results.txt
new file mode 100644
index 00000000000..15a825cb51b
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/Results.txt
@@ -0,0 +1,4 @@
+As before, avoid odd sizes in favor of even sizes.
+
+It's curious that the Japanese font is handled so poorly, especially with
+Windows 8 and later.
diff --git a/src/libs/3rdparty/winpty/misc/Font-Report-June2016/Windows10SetFontBugginess.txt b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/Windows10SetFontBugginess.txt
new file mode 100644
index 00000000000..fef397a1e34
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/Font-Report-June2016/Windows10SetFontBugginess.txt
@@ -0,0 +1,144 @@
+Issues:
+
+ - Starting with the 14342 build, changing the font using
+ SetCurrentConsoleFontEx does not affect the window size. e.g. The content
+ itself will resize/redraw, but the window neither shrinks nor expands.
+ Presumably this is an oversight? It's almost a convenience; if a program
+ is going to resize the window anyway, then it's nice that the window size
+ contraints don't get in the way. Ordinarily, changing the font doesn't just
+ change the window size in pixels--it can also change the size as measured in
+ rows and columns.
+
+ - (Aside: in the 14342 build, there is also a bug with wmic.exe. Open a console
+ with more than 300 lines of screen buffer, then fill those lines with, e.g.,
+ dir /s. Then run wmic.exe. You won't be able to see the wmic.exe prompt.
+ If you query the screen buffer info somehow, you'll notice that the srWindow
+ is not contained within the dwSize. This breaks winpty's scraping, because
+ it's invalid.)
+
+ - In build 14316, with the Japanese locale, with the 437 code page, attempting
+ to set the Consolas font instead sets the Terminal (raster) font. It seems
+ to pick an appropriate vertical size.
+
+ - It seems necessary to specify "-family 0x36" for maximum reliability.
+ Setting the family to 0 almost always works, and specifying just -tt rarely
+ works.
+
+Win7
+ English locale / 437 code page:
+ SetFont.exe -face Consolas -h 16 works
+ SetFont.exe -face Consolas -h 16 -tt selects Terminal font instead
+ SetFont.exe -face Consolas -h 16 -family 0x36 works
+ Japanese locale / 932 code page:
+ SetFont.exe -face Consolas -h 16 works
+ SetFont.exe -face Consolas -h 16 -tt selects Terminal font instead
+ SetFont.exe -face Consolas -h 16 -family 0x36 works
+ Japanese locale / 437 code page:
+ SetFont.exe -face Consolas -h 16 works
+ SetFont.exe -face Consolas -h 16 -tt unreliable
+ SetFont.exe -face Consolas -h 16 -family 0x36 works
+
+Win10 Build 10586
+ New console
+ Japanese locale / 437 code page:
+ SetFont.exe -face Consolas -h 16 works
+ SetFont.exe -face Consolas -h 16 -tt selects Terminal instead
+ SetFont.exe -face Consolas -h 16 -family 0x36 works
+
+Win10 Build 14316
+ Old console
+ English locale / 437 code page:
+ SetFont.exe -face Consolas -h 16 works
+ SetFont.exe -face Consolas -h 16 -tt selects Terminal font instead
+ SetFont.exe -face Consolas -h 16 -family 0x36 works
+ Japanese locale / 932 code page:
+ SetFont.exe -face Consolas -h 16 works
+ SetFont.exe -face Consolas -h 16 -tt selects Terminal font instead
+ SetFont.exe -face Consolas -h 16 -family 0x36 works
+ Japanese locale / 437 code page:
+ SetFont.exe -face Consolas -h 16 works
+ SetFont.exe -face Consolas -h 16 -tt selected very small Consolas font
+ SetFont.exe -face Consolas -h 16 -family 0x36 works
+ New console
+ English locale / 437 code page:
+ SetFont.exe -face Consolas -h 16 works
+ SetFont.exe -face Consolas -h 16 -tt works
+ SetFont.exe -face Consolas -h 16 -family 0x36 works
+ Japanese locale / 932 code page:
+ SetFont.exe -face Consolas -h 16 selects gothic instead
+ SetFont.exe -face Consolas -h 16 -tt selects gothic instead
+ SetFont.exe -face Consolas -h 16 -family 0x36 selects gothic instead
+ Japanese locale / 437 code page:
+ SetFont.exe -face Consolas -h 16 selects Terminal font instead
+ SetFont.exe -face Consolas -h 16 -tt selects Terminal font instead
+ SetFont.exe -face Consolas -h 16 -family 0x36(*) selects Terminal font instead
+
+Win10 Build 14342
+ Old Console
+ English locale / 437 code page:
+ SetFont.exe -face Consolas -h 16 works
+ SetFont.exe -face Consolas -h 16 -tt selects Terminal font instead
+ SetFont.exe -face Consolas -h 16 -family 0x36 works
+ Japanese locale / 932 code page:
+ SetFont.exe -face Consolas -h 16 works
+ SetFont.exe -face Consolas -h 16 -tt selects Terminal font instead
+ SetFont.exe -face Consolas -h 16 -family 0x36 works
+ Japanese locale / 437 code page:
+ SetFont.exe -face Consolas -h 16 works
+ SetFont.exe -face Consolas -h 16 -tt selects Terminal font instead
+ SetFont.exe -face Consolas -h 16 -family 0x36 works
+ New console
+ English locale / 437 code page:
+ SetFont.exe -face Consolas -h 16 works
+ SetFont.exe -face Consolas -h 16 -tt works
+ SetFont.exe -face Consolas -h 16 -family 0x36 works
+ Japanese locale / 932 code page:
+ SetFont.exe -face Consolas -h 16 selects gothic instead
+ SetFont.exe -face Consolas -h 16 -tt selects gothic instead
+ SetFont.exe -face Consolas -h 16 -family 0x36 selects gothic instead
+ Japanese locale / 437 code page:
+ SetFont.exe -face Consolas -h 16 selects Terminal font instead
+ SetFont.exe -face Consolas -h 16 -tt works
+ SetFont.exe -face Consolas -h 16 -family 0x36 works
+
+(*) I was trying to figure out whether the inconsistency was at when I stumbled
+onto this completely unexpected bug. Here's more detail:
+
+ F:\>SetFont.exe -face Consolas -h 16 -family 0x36 -weight normal -w 8
+ Setting to: nFont=0 dwFontSize=(8,16) FontFamily=0x36 FontWeight=400 FaceName="Consolas"
+ SetCurrentConsoleFontEx returned 1
+
+ F:\>GetFont.exe
+ largestConsoleWindowSize=(96,50)
+ maxWnd=0: nFont=0 dwFontSize=(12,16) FontFamily=0x30 FontWeight=400 FaceName=Terminal (54 65 72 6D 69 6E 61 6C)
+ maxWnd=1: nFont=0 dwFontSize=(96,25) FontFamily=0x30 FontWeight=400 FaceName=Terminal (54 65 72 6D 69 6E 61 6C)
+ 00-00: 12x16
+ GetNumberOfConsoleFonts returned 0
+ CP=437 OutputCP=437
+
+ F:\>SetFont.exe -face "Lucida Console" -h 16 -family 0x36 -weight normal
+ Setting to: nFont=0 dwFontSize=(0,16) FontFamily=0x36 FontWeight=400 FaceName="Lucida Console"
+ SetCurrentConsoleFontEx returned 1
+
+ F:\>GetFont.exe
+ largestConsoleWindowSize=(96,50)
+ maxWnd=0: nFont=0 dwFontSize=(12,16) FontFamily=0x30 FontWeight=400 FaceName=Terminal (54 65 72 6D 69 6E 61 6C)
+ maxWnd=1: nFont=0 dwFontSize=(96,25) FontFamily=0x30 FontWeight=400 FaceName=Terminal (54 65 72 6D 69 6E 61 6C)
+ 00-00: 12x16
+ GetNumberOfConsoleFonts returned 0
+ CP=437 OutputCP=437
+
+ F:\>SetFont.exe -face "Lucida Console" -h 12 -family 0x36 -weight normal
+ Setting to: nFont=0 dwFontSize=(0,12) FontFamily=0x36 FontWeight=400 FaceName="Lucida Console"
+ SetCurrentConsoleFontEx returned 1
+
+ F:\>GetFont.exe
+ largestConsoleWindowSize=(230,66)
+ maxWnd=0: nFont=0 dwFontSize=(5,12) FontFamily=0x30 FontWeight=400 FaceName=Terminal (54 65 72 6D 69 6E 61 6C)
+ maxWnd=1: nFont=0 dwFontSize=(116,36) FontFamily=0x30 FontWeight=400 FaceName=Terminal (54 65 72 6D 69 6E 61 6C)
+ 00-00: 5x12
+ GetNumberOfConsoleFonts returned 0
+ CP=437 OutputCP=437
+
+Even attempting to set to a Lucida Console / Consolas font from the Console
+properties dialog fails.
diff --git a/src/libs/3rdparty/winpty/misc/FontSurvey.cc b/src/libs/3rdparty/winpty/misc/FontSurvey.cc
new file mode 100644
index 00000000000..254bcc81a60
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/FontSurvey.cc
@@ -0,0 +1,100 @@
+#include <windows.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <vector>
+
+#include "TestUtil.cc"
+
+#define COUNT_OF(array) (sizeof(array) / sizeof((array)[0]))
+
+// See https://en.wikipedia.org/wiki/List_of_CJK_fonts
+const wchar_t kMSGothic[] = { 0xff2d, 0xff33, 0x0020, 0x30b4, 0x30b7, 0x30c3, 0x30af, 0 }; // Japanese
+const wchar_t kNSimSun[] = { 0x65b0, 0x5b8b, 0x4f53, 0 }; // Simplified Chinese
+const wchar_t kMingLight[] = { 0x7d30, 0x660e, 0x9ad4, 0 }; // Traditional Chinese
+const wchar_t kGulimChe[] = { 0xad74, 0xb9bc, 0xccb4, 0 }; // Korean
+
+std::vector<bool> condense(const std::vector<CHAR_INFO> &buf) {
+ std::vector<bool> ret;
+ size_t i = 0;
+ while (i < buf.size()) {
+ if (buf[i].Char.UnicodeChar == L' ' &&
+ ((buf[i].Attributes & 0x300) == 0)) {
+ // end of line
+ break;
+ } else if (i + 1 < buf.size() &&
+ ((buf[i].Attributes & 0x300) == 0x100) &&
+ ((buf[i + 1].Attributes & 0x300) == 0x200) &&
+ buf[i].Char.UnicodeChar != L' ' &&
+ buf[i].Char.UnicodeChar == buf[i + 1].Char.UnicodeChar) {
+ // double-width
+ ret.push_back(true);
+ i += 2;
+ } else if ((buf[i].Attributes & 0x300) == 0) {
+ // single-width
+ ret.push_back(false);
+ i++;
+ } else {
+ ASSERT(false && "unexpected output");
+ }
+ }
+ return ret;
+}
+
+int main(int argc, char *argv[]) {
+ if (argc != 2) {
+ printf("Usage: %s \"arguments for SetFont.exe\"\n", argv[0]);
+ return 1;
+ }
+
+ const char *setFontArgs = argv[1];
+
+ const wchar_t testLine[] = { 0xA2, 0xA3, 0x2014, 0x3044, 0x30FC, 0x4000, 0 };
+ const HANDLE conout = openConout();
+
+ char setFontCmd[1024];
+ for (int h = 1; h <= 100; ++h) {
+ sprintf(setFontCmd, ".\\SetFont.exe %s -h %d && cls", setFontArgs, h);
+ system(setFontCmd);
+
+ CONSOLE_FONT_INFOEX infoex = {};
+ infoex.cbSize = sizeof(infoex);
+ BOOL success = GetCurrentConsoleFontEx(conout, FALSE, &infoex);
+ ASSERT(success && "GetCurrentConsoleFontEx failed");
+
+ DWORD actual = 0;
+ success = WriteConsoleW(conout, testLine, wcslen(testLine), &actual, nullptr);
+ ASSERT(success && actual == wcslen(testLine));
+
+ std::vector<CHAR_INFO> readBuf(14);
+ const SMALL_RECT readRegion = {0, 0, static_cast<short>(readBuf.size() - 1), 0};
+ SMALL_RECT readRegion2 = readRegion;
+ success = ReadConsoleOutputW(
+ conout, readBuf.data(),
+ {static_cast<short>(readBuf.size()), 1},
+ {0, 0},
+ &readRegion2);
+ ASSERT(success && !memcmp(&readRegion, &readRegion2, sizeof(readRegion)));
+
+ const auto widths = condense(readBuf);
+ std::string widthsStr;
+ for (bool width : widths) {
+ widthsStr.append(width ? "F" : "H");
+ }
+ char size[16];
+ sprintf(size, "%d,%d", infoex.dwFontSize.X, infoex.dwFontSize.Y);
+ const char *status = "";
+ if (widthsStr == "HHFFFF") {
+ status = "GOOD";
+ } else if (widthsStr == "HHHFFF") {
+ status = "OK";
+ } else {
+ status = "BAD";
+ }
+ trace("Size %3d: %-7s %-4s (%s)", h, size, status, widthsStr.c_str());
+ }
+ sprintf(setFontCmd, ".\\SetFont.exe %s -h 14", setFontArgs);
+ system(setFontCmd);
+}
diff --git a/src/libs/3rdparty/winpty/misc/FormatChar.h b/src/libs/3rdparty/winpty/misc/FormatChar.h
new file mode 100644
index 00000000000..aade488f9e2
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/FormatChar.h
@@ -0,0 +1,21 @@
+#include <ctype.h>
+#include <stdio.h>
+#include <string.h>
+
+static inline void formatChar(char *str, char ch)
+{
+ // Print some common control codes.
+ switch (ch) {
+ case '\r': strcpy(str, "CR "); break;
+ case '\n': strcpy(str, "LF "); break;
+ case ' ': strcpy(str, "SP "); break;
+ case 27: strcpy(str, "^[ "); break;
+ case 3: strcpy(str, "^C "); break;
+ default:
+ if (isgraph(ch))
+ sprintf(str, "%c ", ch);
+ else
+ sprintf(str, "%02x ", ch);
+ break;
+ }
+}
diff --git a/src/libs/3rdparty/winpty/misc/FreezePerfTest.cc b/src/libs/3rdparty/winpty/misc/FreezePerfTest.cc
new file mode 100644
index 00000000000..2c0b0086a14
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/FreezePerfTest.cc
@@ -0,0 +1,62 @@
+#include <windows.h>
+
+#include "TestUtil.cc"
+
+const int SC_CONSOLE_MARK = 0xFFF2;
+const int SC_CONSOLE_SELECT_ALL = 0xFFF5;
+
+int main(int argc, char *argv[0]) {
+
+ if (argc != 2) {
+ printf("Usage: %s (mark|selectall|read)\n", argv[0]);
+ return 1;
+ }
+
+ enum class Test { Mark, SelectAll, Read } test;
+ if (!strcmp(argv[1], "mark")) {
+ test = Test::Mark;
+ } else if (!strcmp(argv[1], "selectall")) {
+ test = Test::SelectAll;
+ } else if (!strcmp(argv[1], "read")) {
+ test = Test::Read;
+ } else {
+ printf("Invalid test: %s\n", argv[1]);
+ return 1;
+ }
+
+ HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE);
+ TimeMeasurement tm;
+ HWND hwnd = GetConsoleWindow();
+
+ setWindowPos(0, 0, 1, 1);
+ setBufferSize(100, 3000);
+ system("cls");
+ setWindowPos(0, 2975, 100, 25);
+ setCursorPos(0, 2999);
+
+ ShowWindow(hwnd, SW_HIDE);
+
+ for (int i = 0; i < 1000; ++i) {
+ // CONSOLE_SCREEN_BUFFER_INFO info = {};
+ // GetConsoleScreenBufferInfo(conout, &info);
+
+ if (test == Test::Mark) {
+ SendMessage(hwnd, WM_SYSCOMMAND, SC_CONSOLE_MARK, 0);
+ SendMessage(hwnd, WM_CHAR, 27, 0x00010001);
+ } else if (test == Test::SelectAll) {
+ SendMessage(hwnd, WM_SYSCOMMAND, SC_CONSOLE_SELECT_ALL, 0);
+ SendMessage(hwnd, WM_CHAR, 27, 0x00010001);
+ } else if (test == Test::Read) {
+ static CHAR_INFO buffer[100 * 3000];
+ const SMALL_RECT readRegion = {0, 0, 99, 2999};
+ SMALL_RECT tmp = readRegion;
+ BOOL ret = ReadConsoleOutput(conout, buffer, {100, 3000}, {0, 0}, &tmp);
+ ASSERT(ret && !memcmp(&tmp, &readRegion, sizeof(tmp)));
+ }
+ }
+
+ ShowWindow(hwnd, SW_SHOW);
+
+ printf("elapsed: %f\n", tm.elapsed());
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/GetCh.cc b/src/libs/3rdparty/winpty/misc/GetCh.cc
new file mode 100644
index 00000000000..cd6ed1943ad
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/GetCh.cc
@@ -0,0 +1,20 @@
+#include <conio.h>
+#include <ctype.h>
+#include <stdio.h>
+
+int main() {
+ printf("\nPress any keys -- Ctrl-D exits\n\n");
+
+ while (true) {
+ const int ch = getch();
+ printf("0x%x", ch);
+ if (isgraph(ch)) {
+ printf(" '%c'", ch);
+ }
+ printf("\n");
+ if (ch == 0x4) { // Ctrl-D
+ break;
+ }
+ }
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/GetConsolePos.cc b/src/libs/3rdparty/winpty/misc/GetConsolePos.cc
new file mode 100644
index 00000000000..1f3cc5316f1
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/GetConsolePos.cc
@@ -0,0 +1,41 @@
+#include <windows.h>
+
+#include <stdio.h>
+
+#include "TestUtil.cc"
+
+int main() {
+ const HANDLE conout = openConout();
+
+ CONSOLE_SCREEN_BUFFER_INFO info = {};
+ BOOL ret = GetConsoleScreenBufferInfo(conout, &info);
+ ASSERT(ret && "GetConsoleScreenBufferInfo failed");
+
+ trace("cursor=%d,%d", info.dwCursorPosition.X, info.dwCursorPosition.Y);
+ printf("cursor=%d,%d\n", info.dwCursorPosition.X, info.dwCursorPosition.Y);
+
+ trace("srWindow={L=%d,T=%d,R=%d,B=%d}", info.srWindow.Left, info.srWindow.Top, info.srWindow.Right, info.srWindow.Bottom);
+ printf("srWindow={L=%d,T=%d,R=%d,B=%d}\n", info.srWindow.Left, info.srWindow.Top, info.srWindow.Right, info.srWindow.Bottom);
+
+ trace("dwSize=%d,%d", info.dwSize.X, info.dwSize.Y);
+ printf("dwSize=%d,%d\n", info.dwSize.X, info.dwSize.Y);
+
+ const HWND hwnd = GetConsoleWindow();
+ if (hwnd != NULL) {
+ RECT r = {};
+ if (GetWindowRect(hwnd, &r)) {
+ const int w = r.right - r.left;
+ const int h = r.bottom - r.top;
+ trace("hwnd: pos=(%d,%d) size=(%d,%d)", r.left, r.top, w, h);
+ printf("hwnd: pos=(%d,%d) size=(%d,%d)\n", r.left, r.top, w, h);
+ } else {
+ trace("GetWindowRect failed");
+ printf("GetWindowRect failed\n");
+ }
+ } else {
+ trace("GetConsoleWindow returned NULL");
+ printf("GetConsoleWindow returned NULL\n");
+ }
+
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/GetFont.cc b/src/libs/3rdparty/winpty/misc/GetFont.cc
new file mode 100644
index 00000000000..38625317ab2
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/GetFont.cc
@@ -0,0 +1,261 @@
+#include <windows.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <wchar.h>
+
+#include "../src/shared/OsModule.h"
+#include "../src/shared/StringUtil.h"
+
+#include "TestUtil.cc"
+#include "../src/shared/StringUtil.cc"
+
+#define COUNT_OF(x) (sizeof(x) / sizeof((x)[0]))
+
+// Some of these types and functions are missing from the MinGW headers.
+// Others are undocumented.
+
+struct AGENT_CONSOLE_FONT_INFO {
+ DWORD nFont;
+ COORD dwFontSize;
+};
+
+struct AGENT_CONSOLE_FONT_INFOEX {
+ ULONG cbSize;
+ DWORD nFont;
+ COORD dwFontSize;
+ UINT FontFamily;
+ UINT FontWeight;
+ WCHAR FaceName[LF_FACESIZE];
+};
+
+// undocumented XP API
+typedef BOOL WINAPI SetConsoleFont_t(
+ HANDLE hOutput,
+ DWORD dwFontIndex);
+
+// undocumented XP API
+typedef DWORD WINAPI GetNumberOfConsoleFonts_t();
+
+// XP and up
+typedef BOOL WINAPI GetCurrentConsoleFont_t(
+ HANDLE hOutput,
+ BOOL bMaximumWindow,
+ AGENT_CONSOLE_FONT_INFO *lpConsoleCurrentFont);
+
+// XP and up
+typedef COORD WINAPI GetConsoleFontSize_t(
+ HANDLE hConsoleOutput,
+ DWORD nFont);
+
+// Vista and up
+typedef BOOL WINAPI GetCurrentConsoleFontEx_t(
+ HANDLE hConsoleOutput,
+ BOOL bMaximumWindow,
+ AGENT_CONSOLE_FONT_INFOEX *lpConsoleCurrentFontEx);
+
+// Vista and up
+typedef BOOL WINAPI SetCurrentConsoleFontEx_t(
+ HANDLE hConsoleOutput,
+ BOOL bMaximumWindow,
+ AGENT_CONSOLE_FONT_INFOEX *lpConsoleCurrentFontEx);
+
+#define GET_MODULE_PROC(mod, funcName) \
+ m_##funcName = reinterpret_cast<funcName##_t*>((mod).proc(#funcName)); \
+
+#define DEFINE_ACCESSOR(funcName) \
+ funcName##_t &funcName() const { \
+ ASSERT(valid()); \
+ return *m_##funcName; \
+ }
+
+class XPFontAPI {
+public:
+ XPFontAPI() : m_kernel32(L"kernel32.dll") {
+ GET_MODULE_PROC(m_kernel32, GetCurrentConsoleFont);
+ GET_MODULE_PROC(m_kernel32, GetConsoleFontSize);
+ }
+
+ bool valid() const {
+ return m_GetCurrentConsoleFont != NULL &&
+ m_GetConsoleFontSize != NULL;
+ }
+
+ DEFINE_ACCESSOR(GetCurrentConsoleFont)
+ DEFINE_ACCESSOR(GetConsoleFontSize)
+
+private:
+ OsModule m_kernel32;
+ GetCurrentConsoleFont_t *m_GetCurrentConsoleFont;
+ GetConsoleFontSize_t *m_GetConsoleFontSize;
+};
+
+class UndocumentedXPFontAPI : public XPFontAPI {
+public:
+ UndocumentedXPFontAPI() : m_kernel32(L"kernel32.dll") {
+ GET_MODULE_PROC(m_kernel32, SetConsoleFont);
+ GET_MODULE_PROC(m_kernel32, GetNumberOfConsoleFonts);
+ }
+
+ bool valid() const {
+ return this->XPFontAPI::valid() &&
+ m_SetConsoleFont != NULL &&
+ m_GetNumberOfConsoleFonts != NULL;
+ }
+
+ DEFINE_ACCESSOR(SetConsoleFont)
+ DEFINE_ACCESSOR(GetNumberOfConsoleFonts)
+
+private:
+ OsModule m_kernel32;
+ SetConsoleFont_t *m_SetConsoleFont;
+ GetNumberOfConsoleFonts_t *m_GetNumberOfConsoleFonts;
+};
+
+class VistaFontAPI : public XPFontAPI {
+public:
+ VistaFontAPI() : m_kernel32(L"kernel32.dll") {
+ GET_MODULE_PROC(m_kernel32, GetCurrentConsoleFontEx);
+ GET_MODULE_PROC(m_kernel32, SetCurrentConsoleFontEx);
+ }
+
+ bool valid() const {
+ return this->XPFontAPI::valid() &&
+ m_GetCurrentConsoleFontEx != NULL &&
+ m_SetCurrentConsoleFontEx != NULL;
+ }
+
+ DEFINE_ACCESSOR(GetCurrentConsoleFontEx)
+ DEFINE_ACCESSOR(SetCurrentConsoleFontEx)
+
+private:
+ OsModule m_kernel32;
+ GetCurrentConsoleFontEx_t *m_GetCurrentConsoleFontEx;
+ SetCurrentConsoleFontEx_t *m_SetCurrentConsoleFontEx;
+};
+
+static std::vector<std::pair<DWORD, COORD> > readFontTable(
+ XPFontAPI &api, HANDLE conout, DWORD maxCount) {
+ std::vector<std::pair<DWORD, COORD> > ret;
+ for (DWORD i = 0; i < maxCount; ++i) {
+ COORD size = api.GetConsoleFontSize()(conout, i);
+ if (size.X == 0 && size.Y == 0) {
+ break;
+ }
+ ret.push_back(std::make_pair(i, size));
+ }
+ return ret;
+}
+
+static void dumpFontTable(HANDLE conout) {
+ const int kMaxCount = 1000;
+ XPFontAPI api;
+ if (!api.valid()) {
+ printf("dumpFontTable: cannot dump font table -- missing APIs\n");
+ return;
+ }
+ std::vector<std::pair<DWORD, COORD> > table =
+ readFontTable(api, conout, kMaxCount);
+ std::string line;
+ char tmp[128];
+ size_t first = 0;
+ while (first < table.size()) {
+ size_t last = std::min(table.size() - 1, first + 10 - 1);
+ winpty_snprintf(tmp, "%02u-%02u:",
+ static_cast<unsigned>(first), static_cast<unsigned>(last));
+ line = tmp;
+ for (size_t i = first; i <= last; ++i) {
+ if (i % 10 == 5) {
+ line += " - ";
+ }
+ winpty_snprintf(tmp, " %2dx%-2d",
+ table[i].second.X, table[i].second.Y);
+ line += tmp;
+ }
+ printf("%s\n", line.c_str());
+ first = last + 1;
+ }
+ if (table.size() == kMaxCount) {
+ printf("... stopped reading at %d fonts ...\n", kMaxCount);
+ }
+}
+
+static std::string stringToCodePoints(const std::wstring &str) {
+ std::string ret = "(";
+ for (size_t i = 0; i < str.size(); ++i) {
+ char tmp[32];
+ winpty_snprintf(tmp, "%X", str[i]);
+ if (ret.size() > 1) {
+ ret.push_back(' ');
+ }
+ ret += tmp;
+ }
+ ret.push_back(')');
+ return ret;
+}
+
+static void dumpFontInfoEx(
+ const AGENT_CONSOLE_FONT_INFOEX &infoex) {
+ std::wstring faceName(infoex.FaceName,
+ winpty_wcsnlen(infoex.FaceName, COUNT_OF(infoex.FaceName)));
+ cprintf(L"nFont=%u dwFontSize=(%d,%d) "
+ "FontFamily=0x%x FontWeight=%u FaceName=%ls %hs\n",
+ static_cast<unsigned>(infoex.nFont),
+ infoex.dwFontSize.X, infoex.dwFontSize.Y,
+ infoex.FontFamily, infoex.FontWeight, faceName.c_str(),
+ stringToCodePoints(faceName).c_str());
+}
+
+static void dumpVistaFont(VistaFontAPI &api, HANDLE conout, BOOL maxWindow) {
+ AGENT_CONSOLE_FONT_INFOEX infoex = {0};
+ infoex.cbSize = sizeof(infoex);
+ if (!api.GetCurrentConsoleFontEx()(conout, maxWindow, &infoex)) {
+ printf("GetCurrentConsoleFontEx call failed\n");
+ return;
+ }
+ dumpFontInfoEx(infoex);
+}
+
+static void dumpXPFont(XPFontAPI &api, HANDLE conout, BOOL maxWindow) {
+ AGENT_CONSOLE_FONT_INFO info = {0};
+ if (!api.GetCurrentConsoleFont()(conout, maxWindow, &info)) {
+ printf("GetCurrentConsoleFont call failed\n");
+ return;
+ }
+ printf("nFont=%u dwFontSize=(%d,%d)\n",
+ static_cast<unsigned>(info.nFont),
+ info.dwFontSize.X, info.dwFontSize.Y);
+}
+
+static void dumpFontAndTable(HANDLE conout) {
+ VistaFontAPI vista;
+ if (vista.valid()) {
+ printf("maxWnd=0: "); dumpVistaFont(vista, conout, FALSE);
+ printf("maxWnd=1: "); dumpVistaFont(vista, conout, TRUE);
+ dumpFontTable(conout);
+ return;
+ }
+ UndocumentedXPFontAPI xp;
+ if (xp.valid()) {
+ printf("maxWnd=0: "); dumpXPFont(xp, conout, FALSE);
+ printf("maxWnd=1: "); dumpXPFont(xp, conout, TRUE);
+ dumpFontTable(conout);
+ return;
+ }
+ printf("setSmallFont: neither Vista nor XP APIs detected -- giving up\n");
+ dumpFontTable(conout);
+}
+
+int main() {
+ const HANDLE conout = openConout();
+ const COORD largest = GetLargestConsoleWindowSize(conout);
+ printf("largestConsoleWindowSize=(%d,%d)\n", largest.X, largest.Y);
+ dumpFontAndTable(conout);
+ UndocumentedXPFontAPI xp;
+ if (xp.valid()) {
+ printf("GetNumberOfConsoleFonts returned %u\n", xp.GetNumberOfConsoleFonts()());
+ } else {
+ printf("The GetNumberOfConsoleFonts API was missing\n");
+ }
+ printf("CP=%u OutputCP=%u\n", GetConsoleCP(), GetConsoleOutputCP());
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/IdentifyConsoleWindow.ps1 b/src/libs/3rdparty/winpty/misc/IdentifyConsoleWindow.ps1
new file mode 100644
index 00000000000..0c488597bd2
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/IdentifyConsoleWindow.ps1
@@ -0,0 +1,51 @@
+#
+# Usage: powershell <path>\IdentifyConsoleWindow.ps1
+#
+# This script determines whether the process has a console attached, whether
+# that console has a non-NULL window (e.g. HWND), and whether the window is on
+# the current window station.
+#
+
+$signature = @'
+[DllImport("kernel32.dll", SetLastError=true)]
+public static extern IntPtr GetConsoleWindow();
+
+[DllImport("kernel32.dll", CharSet=CharSet.Auto, SetLastError=true)]
+public static extern bool SetConsoleTitle(String title);
+
+[DllImport("user32.dll", CharSet=CharSet.Auto, SetLastError=true)]
+public static extern int GetWindowText(IntPtr hWnd,
+ System.Text.StringBuilder lpString,
+ int nMaxCount);
+'@
+
+$WinAPI = Add-Type -MemberDefinition $signature `
+ -Name WinAPI -Namespace IdentifyConsoleWindow -PassThru
+
+if (!$WinAPI::SetConsoleTitle("ConsoleWindowScript")) {
+ echo "error: could not change console title -- is a console attached?"
+ exit 1
+} else {
+ echo "note: successfully set console title to ""ConsoleWindowScript""."
+}
+
+$hwnd = $WinAPI::GetConsoleWindow()
+if ($hwnd -eq 0) {
+ echo "note: GetConsoleWindow returned NULL."
+} else {
+ echo "note: GetConsoleWindow returned 0x$($hwnd.ToString("X"))."
+ $sb = New-Object System.Text.StringBuilder -ArgumentList 4096
+ if ($WinAPI::GetWindowText($hwnd, $sb, $sb.Capacity)) {
+ $title = $sb.ToString()
+ echo "note: GetWindowText returned ""${title}""."
+ if ($title -eq "ConsoleWindowScript") {
+ echo "success!"
+ } else {
+ echo "error: expected to see ""ConsoleWindowScript""."
+ echo " (Perhaps the console window is on a different window station?)"
+ }
+ } else {
+ echo "error: GetWindowText could not read the window title."
+ echo " (Perhaps the console window is on a different window station?)"
+ }
+}
diff --git a/src/libs/3rdparty/winpty/misc/IsNewConsole.cc b/src/libs/3rdparty/winpty/misc/IsNewConsole.cc
new file mode 100644
index 00000000000..2b554c72c9f
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/IsNewConsole.cc
@@ -0,0 +1,87 @@
+// Determines whether this is a new console by testing whether MARK moves the
+// cursor.
+//
+// WARNING: This test program may behave erratically if run under winpty.
+//
+
+#include <windows.h>
+
+#include <stdio.h>
+#include <string.h>
+
+#include "TestUtil.cc"
+
+const int SC_CONSOLE_MARK = 0xFFF2;
+const int SC_CONSOLE_SELECT_ALL = 0xFFF5;
+
+static COORD getWindowPos(HANDLE conout) {
+ CONSOLE_SCREEN_BUFFER_INFO info = {};
+ BOOL ret = GetConsoleScreenBufferInfo(conout, &info);
+ ASSERT(ret && "GetConsoleScreenBufferInfo failed");
+ return { info.srWindow.Left, info.srWindow.Top };
+}
+
+static COORD getWindowSize(HANDLE conout) {
+ CONSOLE_SCREEN_BUFFER_INFO info = {};
+ BOOL ret = GetConsoleScreenBufferInfo(conout, &info);
+ ASSERT(ret && "GetConsoleScreenBufferInfo failed");
+ return {
+ static_cast<short>(info.srWindow.Right - info.srWindow.Left + 1),
+ static_cast<short>(info.srWindow.Bottom - info.srWindow.Top + 1)
+ };
+}
+
+static COORD getCursorPos(HANDLE conout) {
+ CONSOLE_SCREEN_BUFFER_INFO info = {};
+ BOOL ret = GetConsoleScreenBufferInfo(conout, &info);
+ ASSERT(ret && "GetConsoleScreenBufferInfo failed");
+ return info.dwCursorPosition;
+}
+
+static void setCursorPos(HANDLE conout, COORD pos) {
+ BOOL ret = SetConsoleCursorPosition(conout, pos);
+ ASSERT(ret && "SetConsoleCursorPosition failed");
+}
+
+int main() {
+ const HANDLE conout = openConout();
+ const HWND hwnd = GetConsoleWindow();
+ ASSERT(hwnd != NULL && "GetConsoleWindow() returned NULL");
+
+ // With the legacy console, the Mark command moves the the cursor to the
+ // top-left cell of the visible console window. Determine whether this
+ // is the new console by seeing if the cursor moves.
+
+ const auto windowSize = getWindowSize(conout);
+ if (windowSize.X <= 1) {
+ printf("Error: console window must be at least 2 columns wide\n");
+ trace("Error: console window must be at least 2 columns wide");
+ return 1;
+ }
+
+ bool cursorMoved = false;
+ const auto initialPos = getCursorPos(conout);
+
+ const auto windowPos = getWindowPos(conout);
+ setCursorPos(conout, { static_cast<short>(windowPos.X + 1), windowPos.Y });
+
+ {
+ const auto posA = getCursorPos(conout);
+ SendMessage(hwnd, WM_SYSCOMMAND, SC_CONSOLE_MARK, 0);
+ const auto posB = getCursorPos(conout);
+ cursorMoved = memcmp(&posA, &posB, sizeof(posA)) != 0;
+ SendMessage(hwnd, WM_CHAR, 27, 0x00010001); // Send ESCAPE
+ }
+
+ setCursorPos(conout, initialPos);
+
+ if (cursorMoved) {
+ printf("Legacy console (i.e. MARK moved cursor)\n");
+ trace("Legacy console (i.e. MARK moved cursor)");
+ } else {
+ printf("Windows 10 new console (i.e MARK did not move cursor)\n");
+ trace("Windows 10 new console (i.e MARK did not move cursor)");
+ }
+
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/MouseInputNotes.txt b/src/libs/3rdparty/winpty/misc/MouseInputNotes.txt
new file mode 100644
index 00000000000..18460c6861e
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/MouseInputNotes.txt
@@ -0,0 +1,90 @@
+Introduction
+============
+
+The only specification I could find describing mouse input escape sequences
+was the /usr/share/doc/xterm/ctlseqs.txt.gz file installed on my Ubuntu
+machine.
+
+Here are the relevant escape sequences:
+
+ * [ON] CSI '?' M 'h' Enable mouse input mode M
+ * [OFF] CSI '?' M 'l' Disable mouse input mode M
+ * [EVT] CSI 'M' F X Y Mouse event (default or mode 1005)
+ * [EVT6] CSI '<' F ';' X ';' Y 'M' Mouse event with mode 1006
+ * [EVT6] CSI '<' F ';' X ';' Y 'm' Mouse event with mode 1006 (up)
+ * [EVT15] CSI F ';' X ';' Y 'M' Mouse event with mode 1015
+
+The first batch of modes affect what events are reported:
+
+ * 9: Presses only (not as well-supported as the other modes)
+ * 1000: Presses and releases
+ * 1002: Presses, releases, and moves-while-pressed
+ * 1003: Presses, releases, and all moves
+
+The next batch of modes affect the encoding of the mouse events:
+
+ * 1005: The X and Y coordinates are UTF-8 codepoints rather than bytes.
+ * 1006: Use the EVT6 sequences instead of EVT
+ * 1015: Use the EVT15 sequence instead of EVT (aka URVXT-mode)
+
+Support for modes in existing terminals
+=======================================
+
+ | 9 1000 1002 1003 | 1004 | overflow | defhi | 1005 1006 1015
+---------------------------------+---------------------+------+--------------+-------+----------------
+Eclipse TM Terminal (Neon) | _ _ _ _ | _ | n/a | n/a | _ _ _
+gnome-terminal 3.6.2 | X X X X | _ | suppressed*b | 0x07 | _ X X
+iTerm2 2.1.4 | _ X X X | OI | wrap*z | n/a | X X X
+jediterm/IntelliJ | _ X X X | _ | ch='?' | 0xff | X X X
+Konsole 2.13.2 | _ X X *a | _ | suppressed | 0xff | X X X
+mintty 2.2.2 | X X X X | OI | ch='\0' | 0xff | X X X
+putty 0.66 | _ X X _ | _ | suppressed | 0xff | _ X X
+rxvt 2.7.10 | X X _ _ | _ | wrap*z | n/a | _ _ _
+screen(under xterm) | X X X X | _ | suppressed | 0xff | _ _ _
+urxvt 9.21 | X X X X | _ | wrap*z | n/a | X _ X
+xfce4-terminal 0.6.3 (GTK2 VTE) | X X X X | _ | wrap | n/a | _ _ _
+xterm | X X X X | OI | ch='\0' | 0xff | X X X
+
+*a: Mode 1003 is handled the same way as 1002.
+*b: The coordinate wraps from 0xff to 0x00, then maxs out at 0x07. I'm
+ guessing this behavior is a bug? I'm using the Xubuntu 14.04
+ gnome-terminal.
+*z: These terminals have a bug where column 224 (and row 224, presumably)
+ yields a truncated escape sequence. 224 + 32 is 0, so it would normally
+ yield `CSI 'M' F '\0' Y`, but the '\0' is interpreted as a NUL-terminator.
+
+Problem 1: How do these flags work?
+===================================
+
+Terminals accept the OFF sequence with any of the input modes. This makes
+little sense--there are two multi-value settings, not seven independent flags!
+
+All the terminals handle Granularity the same way. ON-Granularity sets
+Granularity to the specified value, and OFF-Granularity sets Granularity to
+OFF.
+
+Terminals vary in how they handle the Encoding modes. For example:
+
+ * xterm. ON-Encoding sets Encoding. OFF-Encoding with a non-active Encoding
+ has no effect. OFF-Encoding otherwise resets Encoding to Default.
+
+ * mintty (tested 2.2.2), iTerm2 2.1.4, and jediterm. ON-Encoding sets
+ Encoding. OFF-Encoding resets Encoding to Default.
+
+ * Konsole (tested 2.13.2) seems to configure each encoding method
+ independently. The effective Encoding is the first enabled encoding in this
+ list:
+ - Mode 1006
+ - Mode 1015
+ - Mode 1005
+ - Default
+
+ * gnome-terminal (tested 3.6.2) also configures each encoding method
+ independently. The effective Encoding is the first enabled encoding in
+ this list:
+ - Mode 1006
+ - Mode 1015
+ - Default
+ Mode 1005 is not supported.
+
+ * xfce4 terminal 0.6.3 (GTK2 VTE) always outputs the default encoding method.
diff --git a/src/libs/3rdparty/winpty/misc/MoveConsoleWindow.cc b/src/libs/3rdparty/winpty/misc/MoveConsoleWindow.cc
new file mode 100644
index 00000000000..7d9684fe94e
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/MoveConsoleWindow.cc
@@ -0,0 +1,34 @@
+#include <windows.h>
+
+#include "TestUtil.cc"
+
+int main(int argc, char *argv[]) {
+ if (argc != 3 && argc != 5) {
+ printf("Usage: %s x y\n", argv[0]);
+ printf("Usage: %s x y width height\n", argv[0]);
+ return 1;
+ }
+
+ HWND hwnd = GetConsoleWindow();
+
+ const int x = atoi(argv[1]);
+ const int y = atoi(argv[2]);
+
+ int w = 0, h = 0;
+ if (argc == 3) {
+ RECT r = {};
+ BOOL ret = GetWindowRect(hwnd, &r);
+ ASSERT(ret && "GetWindowRect failed on console window");
+ w = r.right - r.left;
+ h = r.bottom - r.top;
+ } else {
+ w = atoi(argv[3]);
+ h = atoi(argv[4]);
+ }
+
+ BOOL ret = MoveWindow(hwnd, x, y, w, h, TRUE);
+ trace("MoveWindow: ret=%d", ret);
+ printf("MoveWindow: ret=%d\n", ret);
+
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/Notes.txt b/src/libs/3rdparty/winpty/misc/Notes.txt
new file mode 100644
index 00000000000..410e1841986
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/Notes.txt
@@ -0,0 +1,219 @@
+Test programs
+-------------
+
+Cygwin
+ emacs
+ vim
+ mc (Midnight Commander)
+ lynx
+ links
+ less
+ more
+ wget
+
+Capturing the console output
+----------------------------
+
+Initial idea:
+
+In the agent, keep track of the remote terminal state for N lines of
+(window+history). Also keep track of the terminal size. Regularly poll for
+changes to the console screen buffer, then use some number of edits to bring
+the remote terminal into sync with the console.
+
+This idea seems to have trouble when a Unix terminal is resized. When the
+server receives a resize notification, it can have a hard time figuring out
+what the terminal did. Race conditions might also be a problem.
+
+The behavior of the terminal can be tricky:
+
+ - When the window is expanded by one line, does the terminal add a blank line
+ to the bottom or move a line from the history into the top?
+
+ - When the window is shrunk by one line, does the terminal delete the topmost
+ or the bottommost line? Can it delete the line with the cursor?
+
+Some popular behaviors for expanding:
+ - [all] If there are no history lines, then add a line at the bottom.
+ - [konsole] Always add a line at the bottom.
+ - [putty,xterm,rxvt] Pull in a history line from the top.
+ - [g-t] I can't tell. It seems to add a blank line, until the program writes
+ to stdout or until I click the scroll bar, then the output "snaps" back down,
+ pulling lines out of the history. I thought I saw different behavior
+ between Ubuntu 10.10 and 11.10, so maybe GNOME 3 changed something. Avoid
+ using "bash" to test this behavior because "bash" apparently always writes
+ the prompt after terminal resize.
+
+Some popular behaviors for shrinking:
+ - [konsole,putty,xterm,rxvt] If the line at the bottom is blank, then delete
+ it. Otherwise, move the topmost line into history.
+ - [g-t] If the line at the bottom has not been touched, then delete it.
+ Otherwise, move the topmost line into history.
+
+(TODO: I need to test my theories about the terminal behavior better still.
+It's interesting to see how g-t handles clear differently than every other
+terminal.)
+
+There is an ANSI escape sequence (DSR) that sends the current cursor location
+to the terminal's input. One idea I had was to use this code to figure out how
+the terminal had handled a resize. I currently think this idea won't work due
+to race conditions.
+
+Newer idea:
+
+Keep track of the last N lines that have been sent to the remote terminal.
+Poll for changes to console output. When the output changes, send just the
+changed content to the terminal. In particular:
+ - Don't send a cursor position (CUP) code. Instead, if the line that's 3
+ steps up from the latest line changes, send a relative cursor up (CUU)
+ code. It's OK to send an absolute column number code (CHA).
+ - At least in general, don't try to send complete screenshots of the current
+ console window.
+
+The idea is that sending just the changes should have good behavior for streams
+of output, even when those streams modify the output (e.g. an archiver, or
+maybe a downloader/packager/wget). I need to think about whether this works
+for full-screen programs (e.g. emacs, less, lynx, the above list of programs).
+
+I noticed that console programs don't typically modify the window or buffer
+coordinates. edit.com is an exception.
+
+I tested the pager in native Python (more?), and I verified that ENTER and SPACE
+both paid no attention to the location of the console window within the screen
+buffer. This makes sense -- why would they care? The Cygwin less, on the other
+hand, does care. If I scroll the window up, then Cygwin less will write to a
+position within the window. I didn't really expect this behavior, but it
+doesn't seem to be a problem.
+
+Setting up a TestNetServer service
+----------------------------------
+
+First run the deploy.sh script to copy files into deploy. Make sure
+TestNetServer.exe will run in a bare environment (no MinGW or Qt in the path).
+
+Install the Windows Server 2003 Resource Kit. It will have two programs in it,
+instsrv and srvany.
+
+Run:
+
+ InstSrv TestNetServer <path-to-srvany>\srvany.exe
+
+This creates a service named "TestNetServer" that uses the Microsoft service
+wrapper. To configure the new service to run TestNetServer, set a registry
+value:
+
+ [HKLM\SYSTEM\CurrentControlSet\Services\TestNetServer\Parameters]
+ Application=<full-path>\TestNetServer.exe
+
+Also see http://www.iopus.com/guides/srvany.htm.
+
+To remove the service, run:
+
+ InstSrv TestNetServer REMOVE
+
+TODO
+----
+
+Agent: When resizing the console, consider whether to add lines to the top
+or bottom. I remember thinking the current behavior was wrong for some
+application, but I forgot which one.
+
+Make the font as small as possible. The console window dimensions are limited by
+the screen size, so making the font small reduces an unnecessary limitation on the
+PseudoConsole size. There's a documented Vista/Win7 API for this
+(SetCurrentConsoleFontEx), and apparently WinXP has an undocumented API
+(SetConsoleFont):
+ http://blogs.microsoft.co.il/blogs/pavely/archive/2009/07/23/changing-console-fonts.aspx
+
+Make the agent work with DOS programs like edit and qbasic.
+ - Detect that the terminal program has resized the window/buffer and enter a
+ simple just-scrape-and-dont-resize mode. Track the client window size and
+ send the intersection of the console and the agent's client.
+ - I also need to generate keyboard scan codes.
+ - Solve the NTVDM.EXE console shutdown problem, probably by ignoring NTVDM.EXE
+ when it appears on the GetConsoleProcessList list.
+
+Rename the agent? Is the term "proxy" more accurate?
+
+Optimize the polling. e.g. Use a longer poll interval when the console is idle.
+Do a minimal poll that checks whether the sync marker or window has moved.
+
+Increase the console buffer size to ~9000 lines. Beware making it so big that
+reading the sync column exhausts the 32KB conhost<->agent heap.
+
+Reduce the memory overhead of the agent. The agent's m_bufferData array can
+be small (a few hundred lines?) relative to the console buffer size.
+
+Try to handle console background color better.
+ Unix terminal emulators have a user-configurable foreground and background
+color, and for best results, the agent really needs to avoid changing the colors,
+especially the background color. It's undesirable/ugly to SSH into a machine
+and see the command prompt change the colors. It's especially ugly that the
+terminal retains its original colors and only drawn cells get the new colors.
+(e.g. Resizing the window to the right uses the local terminal colors rather
+than the remote colors.) It's especially ugly in gnome-terminal, which draws
+user-configurable black as black, but VT100 black as dark-gray.
+ If there were a way to query the terminal emulator's colors, then I could
+match the console's colors to the terminal and everything would just work. As
+far as I know, that's not possible.
+ I thought of a kludge that might work. Instead of translating console white
+and black to VT/100 white and black, I would translate them to "reset" and
+"invert". I'd translate other colors normally. This approach should produce
+ideal results for command-line work and tolerable results for full-screen
+programs without configuration. Configuring the agent for black-on-white or
+white-on-black would produce ideal results in all situations.
+ This kludge only really applies to the SSH application. For a Win32 Konsole
+application, it should be easy to get the colors right all the time.
+
+Try using the screen reader API:
+ - To eliminate polling.
+ - To detect when a line wraps. When a line wraps, it'd be nice not to send a
+ CRLF to the terminal emulator so copy-and-paste works better.
+ - To detect hard tabs with Cygwin.
+
+Implement VT100/ANSI escape sequence recognition for input. Decide where this
+functionality belongs. PseudoConsole.dll? Disambiguating ESC from an escape
+sequence might be tricky. For the SSH server, I was thinking that when a small
+SSH payload ended with an ESC character, I could assume the character was really
+an ESC keypress, on the assumption that if it were an escape sequence, the
+payload would probably contain the whole sequence. I'm not sure this works,
+especially if there's a lot of other traffic multiplexed on the SSH socket.
+
+Support Unicode.
+ - Some DOS programs draw using line/box characters. Can these characters be
+ translated to the Unicode equivalents?
+
+Create automated tests.
+
+Experiment with the Terminator emulator, an emulator that doesn't wrap lines.
+How many columns does it report having? What column does it report the cursor
+in as it's writing past the right end of the window? Will Terminator be a
+problem if I implement line wrapping detection in the agent?
+
+BUG: After the unix-adapter/pconsole.exe program exits, the blinking cursor is
+replaced with a hidden cursor.
+
+Fix assert() in the agent. If it fails, the failure message needs to be
+reported somewhere. Pop up a dialog box? Maybe switch the active desktop,
+then show a dialog box?
+
+TODO: There's already a pconsole project on GitHub. Maybe rename this project
+to something else? winpty?
+
+TODO: Can the DebugServer system be replaced with OutputDebugString? How
+do we decide whose processes' output to collect?
+
+TODO: Three executables:
+ build/winpty-agent.exe
+ build/winpty.dll
+ build/console.exe
+
+BUG: Run the pconsole.exe inside another console. As I type dir, I see this:
+ D:\rprichard\pconsole>
+ D:\rprichard\pconsole>d
+ D:\rprichard\pconsole>di
+ D:\rprichard\pconsole>dir
+ In the output of "dir", every other line is blank.
+ There was a bug in Terminal::sendLine that was causing this to happen
+ frequently. Now that I fixed it, this bug should only manifest on lines
+ whose last column is not a space (i.e. a full line).
diff --git a/src/libs/3rdparty/winpty/misc/OSVersion.cc b/src/libs/3rdparty/winpty/misc/OSVersion.cc
new file mode 100644
index 00000000000..456708f05b1
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/OSVersion.cc
@@ -0,0 +1,27 @@
+#include <windows.h>
+
+#include <assert.h>
+#include <locale.h>
+#include <stdio.h>
+
+#include <iostream>
+
+int main() {
+ setlocale(LC_ALL, "");
+
+ OSVERSIONINFOEXW info = {0};
+ info.dwOSVersionInfoSize = sizeof(info);
+ assert(GetVersionExW((OSVERSIONINFOW*)&info));
+
+ printf("dwMajorVersion = %d\n", (int)info.dwMajorVersion);
+ printf("dwMinorVersion = %d\n", (int)info.dwMinorVersion);
+ printf("dwBuildNumber = %d\n", (int)info.dwBuildNumber);
+ printf("dwPlatformId = %d\n", (int)info.dwPlatformId);
+ printf("szCSDVersion = %ls\n", info.szCSDVersion);
+ printf("wServicePackMajor = %d\n", info.wServicePackMajor);
+ printf("wServicePackMinor = %d\n", info.wServicePackMinor);
+ printf("wSuiteMask = 0x%x\n", (unsigned int)info.wSuiteMask);
+ printf("wProductType = 0x%x\n", (unsigned int)info.wProductType);
+
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/ScreenBufferFreezeInactive.cc b/src/libs/3rdparty/winpty/misc/ScreenBufferFreezeInactive.cc
new file mode 100644
index 00000000000..656d4f126df
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/ScreenBufferFreezeInactive.cc
@@ -0,0 +1,101 @@
+//
+// Verify that console selection blocks writes to an inactive console screen
+// buffer. Writes TEST PASSED or TEST FAILED to the popup console window.
+//
+
+#include <windows.h>
+#include <stdio.h>
+
+#include <string>
+
+#include "TestUtil.cc"
+
+const int SC_CONSOLE_MARK = 0xFFF2;
+const int SC_CONSOLE_SELECT_ALL = 0xFFF5;
+
+bool g_useMark = false;
+
+CALLBACK DWORD pausingThread(LPVOID dummy)
+{
+ HWND hwnd = GetConsoleWindow();
+ trace("Sending selection to freeze");
+ SendMessage(hwnd, WM_SYSCOMMAND,
+ g_useMark ? SC_CONSOLE_MARK :
+ SC_CONSOLE_SELECT_ALL,
+ 0);
+ Sleep(1000);
+ trace("Sending escape WM_CHAR to unfreeze");
+ SendMessage(hwnd, WM_CHAR, 27, 0x00010001);
+ Sleep(1000);
+}
+
+static HANDLE createBuffer() {
+ HANDLE buf = CreateConsoleScreenBuffer(
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL,
+ CONSOLE_TEXTMODE_BUFFER,
+ NULL);
+ ASSERT(buf != INVALID_HANDLE_VALUE);
+ return buf;
+}
+
+static void runTest(bool useMark, bool createEarly) {
+ trace("=======================================");
+ trace("useMark=%d createEarly=%d", useMark, createEarly);
+ g_useMark = useMark;
+ HANDLE buf = INVALID_HANDLE_VALUE;
+
+ if (createEarly) {
+ buf = createBuffer();
+ }
+
+ CreateThread(NULL, 0,
+ pausingThread, NULL,
+ 0, NULL);
+ Sleep(500);
+
+ if (!createEarly) {
+ trace("Creating buffer");
+ TimeMeasurement tm1;
+ buf = createBuffer();
+ const double elapsed1 = tm1.elapsed();
+ if (elapsed1 >= 0.250) {
+ printf("!!! TEST FAILED !!!\n");
+ Sleep(2000);
+ return;
+ }
+ }
+
+ trace("Writing to aux buffer");
+ TimeMeasurement tm2;
+ DWORD actual = 0;
+ BOOL ret = WriteConsoleW(buf, L"HI", 2, &actual, NULL);
+ const double elapsed2 = tm2.elapsed();
+ trace("Writing to aux buffer: finished: ret=%d actual=%d (elapsed=%1.3f)", ret, actual, elapsed2);
+ if (elapsed2 < 0.250) {
+ printf("!!! TEST FAILED !!!\n");
+ } else {
+ printf("TEST PASSED\n");
+ }
+ Sleep(2000);
+}
+
+int main(int argc, char **argv) {
+ if (argc == 1) {
+ startChildProcess(L"child");
+ return 0;
+ }
+
+ std::string arg = argv[1];
+ if (arg == "child") {
+ for (int useMark = 0; useMark <= 1; useMark++) {
+ for (int createEarly = 0; createEarly <= 1; createEarly++) {
+ runTest(useMark, createEarly);
+ }
+ }
+ printf("done...\n");
+ Sleep(1000);
+ }
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/ScreenBufferTest.cc b/src/libs/3rdparty/winpty/misc/ScreenBufferTest.cc
new file mode 100644
index 00000000000..fa584b9fae9
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/ScreenBufferTest.cc
@@ -0,0 +1,671 @@
+//
+// Windows versions tested
+//
+// Vista Enterprise SP2 32-bit
+// - ver reports [Version 6.0.6002]
+// - kernel32.dll product/file versions are 6.0.6002.19381
+//
+// Windows 7 Ultimate SP1 32-bit
+// - ver reports [Version 6.1.7601]
+// - conhost.exe product/file versions are 6.1.7601.18847
+// - kernel32.dll product/file versions are 6.1.7601.18847
+//
+// Windows Server 2008 R2 Datacenter SP1 64-bit
+// - ver reports [Version 6.1.7601]
+// - conhost.exe product/file versions are 6.1.7601.23153
+// - kernel32.dll product/file versions are 6.1.7601.23153
+//
+// Windows 8 Enterprise 32-bit
+// - ver reports [Version 6.2.9200]
+// - conhost.exe product/file versions are 6.2.9200.16578
+// - kernel32.dll product/file versions are 6.2.9200.16859
+//
+
+//
+// Specific version details on working Server 2008 R2:
+//
+// dwMajorVersion = 6
+// dwMinorVersion = 1
+// dwBuildNumber = 7601
+// dwPlatformId = 2
+// szCSDVersion = Service Pack 1
+// wServicePackMajor = 1
+// wServicePackMinor = 0
+// wSuiteMask = 0x190
+// wProductType = 0x3
+//
+// Specific version details on broken Win7:
+//
+// dwMajorVersion = 6
+// dwMinorVersion = 1
+// dwBuildNumber = 7601
+// dwPlatformId = 2
+// szCSDVersion = Service Pack 1
+// wServicePackMajor = 1
+// wServicePackMinor = 0
+// wSuiteMask = 0x100
+// wProductType = 0x1
+//
+
+#include <windows.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "TestUtil.cc"
+
+const char *g_prefix = "";
+
+static void dumpHandles() {
+ trace("%sSTDIN=0x%I64x STDOUT=0x%I64x STDERR=0x%I64x",
+ g_prefix,
+ (long long)GetStdHandle(STD_INPUT_HANDLE),
+ (long long)GetStdHandle(STD_OUTPUT_HANDLE),
+ (long long)GetStdHandle(STD_ERROR_HANDLE));
+}
+
+static const char *successOrFail(BOOL ret) {
+ return ret ? "ok" : "FAILED";
+}
+
+static void startChildInSameConsole(const wchar_t *args, BOOL
+ bInheritHandles=FALSE) {
+ wchar_t program[1024];
+ wchar_t cmdline[1024];
+ GetModuleFileNameW(NULL, program, 1024);
+ swprintf(cmdline, L"\"%ls\" %ls", program, args);
+
+ STARTUPINFOW sui;
+ PROCESS_INFORMATION pi;
+ memset(&sui, 0, sizeof(sui));
+ memset(&pi, 0, sizeof(pi));
+ sui.cb = sizeof(sui);
+
+ CreateProcessW(program, cmdline,
+ NULL, NULL,
+ /*bInheritHandles=*/bInheritHandles,
+ /*dwCreationFlags=*/0,
+ NULL, NULL,
+ &sui, &pi);
+}
+
+static void closeHandle(HANDLE h) {
+ trace("%sClosing handle 0x%I64x...", g_prefix, (long long)h);
+ trace("%sClosing handle 0x%I64x... %s", g_prefix, (long long)h, successOrFail(CloseHandle(h)));
+}
+
+static HANDLE createBuffer() {
+
+ // If sa isn't provided, the handle defaults to not-inheritable.
+ SECURITY_ATTRIBUTES sa = {0};
+ sa.nLength = sizeof(sa);
+ sa.bInheritHandle = TRUE;
+
+ trace("%sCreating a new buffer...", g_prefix);
+ HANDLE conout = CreateConsoleScreenBuffer(
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ &sa,
+ CONSOLE_TEXTMODE_BUFFER, NULL);
+
+ trace("%sCreating a new buffer... 0x%I64x", g_prefix, (long long)conout);
+ return conout;
+}
+
+static HANDLE openConout() {
+
+ // If sa isn't provided, the handle defaults to not-inheritable.
+ SECURITY_ATTRIBUTES sa = {0};
+ sa.nLength = sizeof(sa);
+ sa.bInheritHandle = TRUE;
+
+ trace("%sOpening CONOUT...", g_prefix);
+ HANDLE conout = CreateFileW(L"CONOUT$",
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ &sa,
+ OPEN_EXISTING, 0, NULL);
+ trace("%sOpening CONOUT... 0x%I64x", g_prefix, (long long)conout);
+ return conout;
+}
+
+static void setConsoleActiveScreenBuffer(HANDLE conout) {
+ trace("%sSetConsoleActiveScreenBuffer(0x%I64x) called...",
+ g_prefix, (long long)conout);
+ trace("%sSetConsoleActiveScreenBuffer(0x%I64x) called... %s",
+ g_prefix, (long long)conout,
+ successOrFail(SetConsoleActiveScreenBuffer(conout)));
+}
+
+static void writeTest(HANDLE conout, const char *msg) {
+ char writeData[256];
+ sprintf(writeData, "%s%s\n", g_prefix, msg);
+
+ trace("%sWriting to 0x%I64x: '%s'...",
+ g_prefix, (long long)conout, msg);
+ DWORD actual = 0;
+ BOOL ret = WriteConsoleA(conout, writeData, strlen(writeData), &actual, NULL);
+ trace("%sWriting to 0x%I64x: '%s'... %s",
+ g_prefix, (long long)conout, msg,
+ successOrFail(ret && actual == strlen(writeData)));
+}
+
+static void writeTest(const char *msg) {
+ writeTest(GetStdHandle(STD_OUTPUT_HANDLE), msg);
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// TEST 1 -- create new buffer, activate it, and close the handle. The console
+// automatically switches the screen buffer back to the original.
+//
+// This test passes everywhere.
+//
+
+static void test1(int argc, char *argv[]) {
+ if (!strcmp(argv[1], "1")) {
+ startChildProcess(L"1:child");
+ return;
+ }
+
+ HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE);
+ writeTest(origBuffer, "<-- origBuffer -->");
+
+ HANDLE newBuffer = createBuffer();
+ writeTest(newBuffer, "<-- newBuffer -->");
+ setConsoleActiveScreenBuffer(newBuffer);
+ Sleep(2000);
+
+ writeTest(origBuffer, "TEST PASSED!");
+
+ // Closing the handle w/o switching the active screen buffer automatically
+ // switches the console back to the original buffer.
+ closeHandle(newBuffer);
+
+ while (true) {
+ Sleep(1000);
+ }
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// TEST 2 -- Test program that creates and activates newBuffer, starts a child
+// process, then closes its newBuffer handle. newBuffer remains activated,
+// because the child keeps it active. (Also see TEST D.)
+//
+
+static void test2(int argc, char *argv[]) {
+ if (!strcmp(argv[1], "2")) {
+ startChildProcess(L"2:parent");
+ return;
+ }
+
+ if (!strcmp(argv[1], "2:parent")) {
+ g_prefix = "parent: ";
+ dumpHandles();
+ HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE);
+ writeTest(origBuffer, "<-- origBuffer -->");
+
+ HANDLE newBuffer = createBuffer();
+ writeTest(newBuffer, "<-- newBuffer -->");
+ setConsoleActiveScreenBuffer(newBuffer);
+
+ Sleep(1000);
+ writeTest(newBuffer, "bInheritHandles=FALSE:");
+ startChildInSameConsole(L"2:child", FALSE);
+ Sleep(1000);
+ writeTest(newBuffer, "bInheritHandles=TRUE:");
+ startChildInSameConsole(L"2:child", TRUE);
+
+ Sleep(1000);
+ trace("parent:----");
+
+ // Close the new buffer. The active screen buffer doesn't automatically
+ // switch back to origBuffer, because the child process has a handle open
+ // to the original buffer.
+ closeHandle(newBuffer);
+
+ Sleep(600 * 1000);
+ return;
+ }
+
+ if (!strcmp(argv[1], "2:child")) {
+ g_prefix = "child: ";
+ dumpHandles();
+ // The child's output isn't visible, because it's still writing to
+ // origBuffer.
+ trace("child:----");
+ writeTest("writing to STDOUT");
+
+ // Handle inheritability is curious. The console handles this program
+ // creates are inheritable, but CreateProcess is called with both
+ // bInheritHandles=TRUE and bInheritHandles=FALSE.
+ //
+ // Vista and Windows 7: bInheritHandles has no effect. The child and
+ // parent processes have the same STDIN/STDOUT/STDERR handles:
+ // 0x3, 0x7, and 0xB. The parent has a 0xF handle for newBuffer.
+ // The child can only write to 0x7, 0xB, and 0xF. Only the writes to
+ // 0xF are visible (i.e. they touch newBuffer).
+ //
+ // Windows 8 or Windows 10 (legacy or non-legacy): the lowest 2 bits of
+ // the HANDLE to WriteConsole seem to be ignored. The new process'
+ // console handles always refer to the buffer that was active when they
+ // started, but the values of the handles depend upon bInheritHandles.
+ // With bInheritHandles=TRUE, the child has the same
+ // STDIN/STDOUT/STDERR/newBuffer handles as the parent, and the three
+ // output handles all work, though their output is all visible. With
+ // bInheritHandles=FALSE, the child has different STDIN/STDOUT/STDERR
+ // handles, and only the new STDOUT/STDERR handles work.
+ //
+ for (unsigned int i = 0x1; i <= 0xB0; ++i) {
+ char msg[256];
+ sprintf(msg, "Write to handle 0x%x", i);
+ HANDLE h = reinterpret_cast<HANDLE>(i);
+ writeTest(h, msg);
+ }
+
+ Sleep(600 * 1000);
+ return;
+ }
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// TEST A -- demonstrate an apparent Windows bug with screen buffers
+//
+// Steps:
+// - The parent starts a child process.
+// - The child process creates and activates newBuffer
+// - The parent opens CONOUT$ and writes to it.
+// - The parent closes CONOUT$.
+// - At this point, broken Windows reactivates origBuffer.
+// - The child writes to newBuffer again.
+// - The child activates origBuffer again, then closes newBuffer.
+//
+// Test passes if the message "TEST PASSED!" is visible.
+// Test commonly fails if conhost.exe crashes.
+//
+// Results:
+// - Windows 7 Ultimate SP1 32-bit: conhost.exe crashes
+// - Windows Server 2008 R2 Datacenter SP1 64-bit: PASS
+// - Windows 8 Enterprise 32-bit: PASS
+// - Windows 10 64-bit (legacy and non-legacy): PASS
+//
+
+static void testA_parentWork() {
+ // Open an extra CONOUT$ handle so that the HANDLE values in parent and
+ // child don't collide. I think it's OK if they collide, but since we're
+ // trying to track down a Windows bug, it's best to avoid unnecessary
+ // complication.
+ HANDLE dummy = openConout();
+
+ Sleep(3000);
+
+ // Step 2: Open CONOUT$ in the parent. This opens the active buffer, which
+ // was just created in the child. It's handle 0x13. Write to it.
+
+ HANDLE newBuffer = openConout();
+ writeTest(newBuffer, "step2: writing to newBuffer");
+
+ Sleep(3000);
+
+ // Step 3: Close handle 0x13. With Windows 7, the console switches back to
+ // origBuffer, and (unless I'm missing something) it shouldn't.
+
+ closeHandle(newBuffer);
+}
+
+static void testA_childWork() {
+ HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE);
+
+ //
+ // Step 1: Create the new screen buffer in the child process and make it
+ // active. (Typically, it's handle 0x0F.)
+ //
+
+ HANDLE newBuffer = createBuffer();
+
+ setConsoleActiveScreenBuffer(newBuffer);
+ writeTest(newBuffer, "<-- newBuffer -->");
+
+ Sleep(9000);
+ trace("child:----");
+
+ // Step 4: write to the newBuffer again.
+ writeTest(newBuffer, "TEST PASSED!");
+
+ //
+ // Step 5: Switch back to the original screen buffer and close the new
+ // buffer. The switch call succeeds, but the CloseHandle call freezes for
+ // several seconds, because conhost.exe crashes.
+ //
+ Sleep(3000);
+
+ setConsoleActiveScreenBuffer(origBuffer);
+ writeTest(origBuffer, "writing to origBuffer");
+
+ closeHandle(newBuffer);
+
+ // The console HWND is NULL.
+ trace("child: console HWND=0x%I64x", (long long)GetConsoleWindow());
+
+ // At this point, the console window has closed, but the parent/child
+ // processes are still running. Calling AllocConsole would fail, but
+ // calling FreeConsole followed by AllocConsole would both succeed, and a
+ // new console would appear.
+}
+
+static void testA(int argc, char *argv[]) {
+
+ if (!strcmp(argv[1], "A")) {
+ startChildProcess(L"A:parent");
+ return;
+ }
+
+ if (!strcmp(argv[1], "A:parent")) {
+ g_prefix = "parent: ";
+ trace("parent:----");
+ dumpHandles();
+ writeTest("<-- origBuffer -->");
+ startChildInSameConsole(L"A:child");
+ testA_parentWork();
+ Sleep(120000);
+ return;
+ }
+
+ if (!strcmp(argv[1], "A:child")) {
+ g_prefix = "child: ";
+ dumpHandles();
+ testA_childWork();
+ Sleep(120000);
+ return;
+ }
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// TEST B -- invert TEST A -- also crashes conhost on Windows 7
+//
+// Test passes if the message "TEST PASSED!" is visible.
+// Test commonly fails if conhost.exe crashes.
+//
+// Results:
+// - Windows 7 Ultimate SP1 32-bit: conhost.exe crashes
+// - Windows Server 2008 R2 Datacenter SP1 64-bit: PASS
+// - Windows 8 Enterprise 32-bit: PASS
+// - Windows 10 64-bit (legacy and non-legacy): PASS
+//
+
+static void testB(int argc, char *argv[]) {
+ if (!strcmp(argv[1], "B")) {
+ startChildProcess(L"B:parent");
+ return;
+ }
+
+ if (!strcmp(argv[1], "B:parent")) {
+ g_prefix = "parent: ";
+ startChildInSameConsole(L"B:child");
+ writeTest("<-- origBuffer -->");
+ HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE);
+
+ //
+ // Step 1: Create the new buffer and make it active.
+ //
+ trace("%s----", g_prefix);
+ HANDLE newBuffer = createBuffer();
+ setConsoleActiveScreenBuffer(newBuffer);
+ writeTest(newBuffer, "<-- newBuffer -->");
+
+ //
+ // Step 4: Attempt to write again to the new buffer.
+ //
+ Sleep(9000);
+ trace("%s----", g_prefix);
+ writeTest(newBuffer, "TEST PASSED!");
+
+ //
+ // Step 5: Switch back to the original buffer.
+ //
+ Sleep(3000);
+ trace("%s----", g_prefix);
+ setConsoleActiveScreenBuffer(origBuffer);
+ closeHandle(newBuffer);
+ writeTest(origBuffer, "writing to the initial buffer");
+
+ Sleep(60000);
+ return;
+ }
+
+ if (!strcmp(argv[1], "B:child")) {
+ g_prefix = "child: ";
+ Sleep(3000);
+ trace("%s----", g_prefix);
+
+ //
+ // Step 2: Open the newly active buffer and write to it.
+ //
+ HANDLE newBuffer = openConout();
+ writeTest(newBuffer, "writing to newBuffer");
+
+ //
+ // Step 3: Close the newly active buffer.
+ //
+ Sleep(3000);
+ closeHandle(newBuffer);
+
+ Sleep(60000);
+ return;
+ }
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// TEST C -- Interleaving open/close of console handles also seems to break on
+// Windows 7.
+//
+// Test:
+// - child creates and activates newBuf1
+// - parent opens newBuf1
+// - child creates and activates newBuf2
+// - parent opens newBuf2, then closes newBuf1
+// - child switches back to newBuf1
+// * At this point, the console starts malfunctioning.
+// - parent and child close newBuf2
+// - child closes newBuf1
+//
+// Test passes if the message "TEST PASSED!" is visible.
+// Test commonly fails if conhost.exe crashes.
+//
+// Results:
+// - Windows 7 Ultimate SP1 32-bit: conhost.exe crashes
+// - Windows Server 2008 R2 Datacenter SP1 64-bit: PASS
+// - Windows 8 Enterprise 32-bit: PASS
+// - Windows 10 64-bit (legacy and non-legacy): PASS
+//
+
+static void testC(int argc, char *argv[]) {
+ if (!strcmp(argv[1], "C")) {
+ startChildProcess(L"C:parent");
+ return;
+ }
+
+ if (!strcmp(argv[1], "C:parent")) {
+ startChildInSameConsole(L"C:child");
+ writeTest("<-- origBuffer -->");
+ g_prefix = "parent: ";
+
+ // At time=4, open newBuffer1.
+ Sleep(4000);
+ trace("%s---- t=4", g_prefix);
+ const HANDLE newBuffer1 = openConout();
+
+ // At time=8, open newBuffer2, and close newBuffer1.
+ Sleep(4000);
+ trace("%s---- t=8", g_prefix);
+ const HANDLE newBuffer2 = openConout();
+ closeHandle(newBuffer1);
+
+ // At time=25, cleanup of newBuffer2.
+ Sleep(17000);
+ trace("%s---- t=25", g_prefix);
+ closeHandle(newBuffer2);
+
+ Sleep(240000);
+ return;
+ }
+
+ if (!strcmp(argv[1], "C:child")) {
+ g_prefix = "child: ";
+
+ // At time=2, create newBuffer1 and activate it.
+ Sleep(2000);
+ trace("%s---- t=2", g_prefix);
+ const HANDLE newBuffer1 = createBuffer();
+ setConsoleActiveScreenBuffer(newBuffer1);
+ writeTest(newBuffer1, "<-- newBuffer1 -->");
+
+ // At time=6, create newBuffer2 and activate it.
+ Sleep(4000);
+ trace("%s---- t=6", g_prefix);
+ const HANDLE newBuffer2 = createBuffer();
+ setConsoleActiveScreenBuffer(newBuffer2);
+ writeTest(newBuffer2, "<-- newBuffer2 -->");
+
+ // At time=10, attempt to switch back to newBuffer1. The parent process
+ // has opened and closed its handle to newBuffer1, so does it still exist?
+ Sleep(4000);
+ trace("%s---- t=10", g_prefix);
+ setConsoleActiveScreenBuffer(newBuffer1);
+ writeTest(newBuffer1, "write to newBuffer1: TEST PASSED!");
+
+ // At time=25, cleanup of newBuffer2.
+ Sleep(15000);
+ trace("%s---- t=25", g_prefix);
+ closeHandle(newBuffer2);
+
+ // At time=35, cleanup of newBuffer1. The console should switch to the
+ // initial buffer again.
+ Sleep(10000);
+ trace("%s---- t=35", g_prefix);
+ closeHandle(newBuffer1);
+
+ Sleep(240000);
+ return;
+ }
+}
+
+
+
+///////////////////////////////////////////////////////////////////////////////
+// TEST D -- parent creates a new buffer, child launches, writes,
+// closes it output handle, then parent writes again. (Also see TEST 2.)
+//
+// On success, this will appear:
+//
+// parent: <-- newBuffer -->
+// child: writing to newBuffer
+// parent: TEST PASSED!
+//
+// If this appears, it indicates that the child's closing its output handle did
+// not destroy newBuffer.
+//
+// Results:
+// - Windows 7 Ultimate SP1 32-bit: PASS
+// - Windows 8 Enterprise 32-bit: PASS
+// - Windows 10 64-bit (legacy and non-legacy): PASS
+//
+
+static void testD(int argc, char *argv[]) {
+ if (!strcmp(argv[1], "D")) {
+ startChildProcess(L"D:parent");
+ return;
+ }
+
+ if (!strcmp(argv[1], "D:parent")) {
+ g_prefix = "parent: ";
+ HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE);
+ writeTest(origBuffer, "<-- origBuffer -->");
+
+ HANDLE newBuffer = createBuffer();
+ writeTest(newBuffer, "<-- newBuffer -->");
+ setConsoleActiveScreenBuffer(newBuffer);
+
+ // At t=2, start a child process, explicitly forcing it to use
+ // newBuffer for its standard handles. These calls are apparently
+ // redundant on Windows 8 and up.
+ Sleep(2000);
+ trace("parent:----");
+ trace("parent: starting child process");
+ SetStdHandle(STD_OUTPUT_HANDLE, newBuffer);
+ SetStdHandle(STD_ERROR_HANDLE, newBuffer);
+ startChildInSameConsole(L"D:child");
+ SetStdHandle(STD_OUTPUT_HANDLE, origBuffer);
+ SetStdHandle(STD_ERROR_HANDLE, origBuffer);
+
+ // At t=6, write again to newBuffer.
+ Sleep(4000);
+ trace("parent:----");
+ writeTest(newBuffer, "TEST PASSED!");
+
+ // At t=8, close the newBuffer. In earlier versions of windows
+ // (including Server 2008 R2), the console then switches back to
+ // origBuffer. As of Windows 8, it doesn't, because somehow the child
+ // process is keeping the console on newBuffer, even though the child
+ // process closed its STDIN/STDOUT/STDERR handles. Killing the child
+ // process by hand after the test finishes *does* force the console
+ // back to origBuffer.
+ Sleep(2000);
+ closeHandle(newBuffer);
+
+ Sleep(120000);
+ return;
+ }
+
+ if (!strcmp(argv[1], "D:child")) {
+ g_prefix = "child: ";
+ // At t=2, the child starts.
+ trace("child:----");
+ dumpHandles();
+ writeTest("writing to newBuffer");
+
+ // At t=4, the child explicitly closes its handle.
+ Sleep(2000);
+ trace("child:----");
+ if (GetStdHandle(STD_ERROR_HANDLE) != GetStdHandle(STD_OUTPUT_HANDLE)) {
+ closeHandle(GetStdHandle(STD_ERROR_HANDLE));
+ }
+ closeHandle(GetStdHandle(STD_OUTPUT_HANDLE));
+ closeHandle(GetStdHandle(STD_INPUT_HANDLE));
+
+ Sleep(120000);
+ return;
+ }
+}
+
+
+
+int main(int argc, char *argv[]) {
+ if (argc == 1) {
+ printf("USAGE: %s testnum\n", argv[0]);
+ return 0;
+ }
+
+ if (argv[1][0] == '1') {
+ test1(argc, argv);
+ } else if (argv[1][0] == '2') {
+ test2(argc, argv);
+ } else if (argv[1][0] == 'A') {
+ testA(argc, argv);
+ } else if (argv[1][0] == 'B') {
+ testB(argc, argv);
+ } else if (argv[1][0] == 'C') {
+ testC(argc, argv);
+ } else if (argv[1][0] == 'D') {
+ testD(argc, argv);
+ }
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/ScreenBufferTest2.cc b/src/libs/3rdparty/winpty/misc/ScreenBufferTest2.cc
new file mode 100644
index 00000000000..2b648c94093
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/ScreenBufferTest2.cc
@@ -0,0 +1,151 @@
+#include <windows.h>
+
+#include "TestUtil.cc"
+
+const char *g_prefix = "";
+
+static void dumpHandles() {
+ trace("%sSTDIN=0x%I64x STDOUT=0x%I64x STDERR=0x%I64x",
+ g_prefix,
+ (long long)GetStdHandle(STD_INPUT_HANDLE),
+ (long long)GetStdHandle(STD_OUTPUT_HANDLE),
+ (long long)GetStdHandle(STD_ERROR_HANDLE));
+}
+
+static HANDLE createBuffer() {
+
+ // If sa isn't provided, the handle defaults to not-inheritable.
+ SECURITY_ATTRIBUTES sa = {0};
+ sa.nLength = sizeof(sa);
+ sa.bInheritHandle = TRUE;
+
+ trace("%sCreating a new buffer...", g_prefix);
+ HANDLE conout = CreateConsoleScreenBuffer(
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ &sa,
+ CONSOLE_TEXTMODE_BUFFER, NULL);
+
+ trace("%sCreating a new buffer... 0x%I64x", g_prefix, (long long)conout);
+ return conout;
+}
+
+static const char *successOrFail(BOOL ret) {
+ return ret ? "ok" : "FAILED";
+}
+
+static void setConsoleActiveScreenBuffer(HANDLE conout) {
+ trace("%sSetConsoleActiveScreenBuffer(0x%I64x) called...",
+ g_prefix, (long long)conout);
+ trace("%sSetConsoleActiveScreenBuffer(0x%I64x) called... %s",
+ g_prefix, (long long)conout,
+ successOrFail(SetConsoleActiveScreenBuffer(conout)));
+}
+
+static void writeTest(HANDLE conout, const char *msg) {
+ char writeData[256];
+ sprintf(writeData, "%s%s\n", g_prefix, msg);
+
+ trace("%sWriting to 0x%I64x: '%s'...",
+ g_prefix, (long long)conout, msg);
+ DWORD actual = 0;
+ BOOL ret = WriteConsoleA(conout, writeData, strlen(writeData), &actual, NULL);
+ trace("%sWriting to 0x%I64x: '%s'... %s",
+ g_prefix, (long long)conout, msg,
+ successOrFail(ret && actual == strlen(writeData)));
+}
+
+static HANDLE startChildInSameConsole(const wchar_t *args, BOOL
+ bInheritHandles=FALSE) {
+ wchar_t program[1024];
+ wchar_t cmdline[1024];
+ GetModuleFileNameW(NULL, program, 1024);
+ swprintf(cmdline, L"\"%ls\" %ls", program, args);
+
+ STARTUPINFOW sui;
+ PROCESS_INFORMATION pi;
+ memset(&sui, 0, sizeof(sui));
+ memset(&pi, 0, sizeof(pi));
+ sui.cb = sizeof(sui);
+
+ CreateProcessW(program, cmdline,
+ NULL, NULL,
+ /*bInheritHandles=*/bInheritHandles,
+ /*dwCreationFlags=*/0,
+ NULL, NULL,
+ &sui, &pi);
+
+ return pi.hProcess;
+}
+
+static HANDLE dup(HANDLE h, HANDLE targetProcess) {
+ HANDLE h2 = INVALID_HANDLE_VALUE;
+ BOOL ret = DuplicateHandle(
+ GetCurrentProcess(), h,
+ targetProcess, &h2,
+ 0, TRUE, DUPLICATE_SAME_ACCESS);
+ trace("dup(0x%I64x) to process 0x%I64x... %s, 0x%I64x",
+ (long long)h,
+ (long long)targetProcess,
+ successOrFail(ret),
+ (long long)h2);
+ return h2;
+}
+
+int main(int argc, char *argv[]) {
+ if (argc == 1) {
+ startChildProcess(L"parent");
+ return 0;
+ }
+
+ if (!strcmp(argv[1], "parent")) {
+ g_prefix = "parent: ";
+ dumpHandles();
+ HANDLE hChild = startChildInSameConsole(L"child");
+
+ // Windows 10.
+ HANDLE orig1 = GetStdHandle(STD_OUTPUT_HANDLE);
+ HANDLE new1 = createBuffer();
+
+ Sleep(2000);
+ setConsoleActiveScreenBuffer(new1);
+
+ // Handle duplication results to child process in same console:
+ // - Windows XP: fails
+ // - Windows 7 Ultimate SP1 32-bit: fails
+ // - Windows Server 2008 R2 Datacenter SP1 64-bit: fails
+ // - Windows 8 Enterprise 32-bit: succeeds
+ // - Windows 10: succeeds
+ HANDLE orig2 = dup(orig1, GetCurrentProcess());
+ HANDLE new2 = dup(new1, GetCurrentProcess());
+
+ dup(orig1, hChild);
+ dup(new1, hChild);
+
+ // The writes to orig1/orig2 are invisible. The writes to new1/new2
+ // are visible.
+ writeTest(orig1, "write to orig1");
+ writeTest(orig2, "write to orig2");
+ writeTest(new1, "write to new1");
+ writeTest(new2, "write to new2");
+
+ Sleep(120000);
+ return 0;
+ }
+
+ if (!strcmp(argv[1], "child")) {
+ g_prefix = "child: ";
+ dumpHandles();
+ Sleep(4000);
+ for (unsigned int i = 0x1; i <= 0xB0; ++i) {
+ char msg[256];
+ sprintf(msg, "Write to handle 0x%x", i);
+ HANDLE h = reinterpret_cast<HANDLE>(i);
+ writeTest(h, msg);
+ }
+ Sleep(120000);
+ return 0;
+ }
+
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/SelectAllTest.cc b/src/libs/3rdparty/winpty/misc/SelectAllTest.cc
new file mode 100644
index 00000000000..a6c27739d80
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/SelectAllTest.cc
@@ -0,0 +1,45 @@
+#define _WIN32_WINNT 0x0501
+#include <stdio.h>
+#include <windows.h>
+
+#include "../src/shared/DebugClient.cc"
+
+const int SC_CONSOLE_MARK = 0xFFF2;
+const int SC_CONSOLE_SELECT_ALL = 0xFFF5;
+
+CALLBACK DWORD pausingThread(LPVOID dummy)
+{
+ HWND hwnd = GetConsoleWindow();
+ while (true) {
+ SendMessage(hwnd, WM_SYSCOMMAND, SC_CONSOLE_SELECT_ALL, 0);
+ Sleep(1000);
+ SendMessage(hwnd, WM_CHAR, 27, 0x00010001);
+ Sleep(1000);
+ }
+}
+
+int main()
+{
+ HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
+ CONSOLE_SCREEN_BUFFER_INFO info;
+
+ GetConsoleScreenBufferInfo(out, &info);
+ COORD initial = info.dwCursorPosition;
+
+ CreateThread(NULL, 0,
+ pausingThread, NULL,
+ 0, NULL);
+
+ for (int i = 0; i < 30; ++i) {
+ Sleep(100);
+ GetConsoleScreenBufferInfo(out, &info);
+ if (memcmp(&info.dwCursorPosition, &initial, sizeof(COORD)) != 0) {
+ trace("cursor moved to [%d,%d]",
+ info.dwCursorPosition.X,
+ info.dwCursorPosition.Y);
+ } else {
+ trace("cursor in expected position");
+ }
+ }
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/SetBufInfo.cc b/src/libs/3rdparty/winpty/misc/SetBufInfo.cc
new file mode 100644
index 00000000000..f37c31bdf72
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/SetBufInfo.cc
@@ -0,0 +1,90 @@
+#include <windows.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "TestUtil.cc"
+
+static void usage() {
+ printf("usage: SetBufInfo [-set] [-buf W H] [-win W H] [-pos X Y]\n");
+}
+
+int main(int argc, char *argv[]) {
+ const HANDLE conout = CreateFileW(L"CONOUT$",
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL, OPEN_EXISTING, 0, NULL);
+ ASSERT(conout != INVALID_HANDLE_VALUE);
+
+ bool change = false;
+ BOOL success;
+ CONSOLE_SCREEN_BUFFER_INFOEX info = {};
+ info.cbSize = sizeof(info);
+
+ success = GetConsoleScreenBufferInfoEx(conout, &info);
+ ASSERT(success && "GetConsoleScreenBufferInfoEx failed");
+
+ for (int i = 1; i < argc; ) {
+ std::string arg = argv[i];
+ if (arg == "-buf" && (i + 2) < argc) {
+ info.dwSize.X = atoi(argv[i + 1]);
+ info.dwSize.Y = atoi(argv[i + 2]);
+ i += 3;
+ change = true;
+ } else if (arg == "-pos" && (i + 2) < argc) {
+ int dx = info.srWindow.Right - info.srWindow.Left;
+ int dy = info.srWindow.Bottom - info.srWindow.Top;
+ info.srWindow.Left = atoi(argv[i + 1]);
+ info.srWindow.Top = atoi(argv[i + 2]);
+ i += 3;
+ info.srWindow.Right = info.srWindow.Left + dx;
+ info.srWindow.Bottom = info.srWindow.Top + dy;
+ change = true;
+ } else if (arg == "-win" && (i + 2) < argc) {
+ info.srWindow.Right = info.srWindow.Left + atoi(argv[i + 1]) - 1;
+ info.srWindow.Bottom = info.srWindow.Top + atoi(argv[i + 2]) - 1;
+ i += 3;
+ change = true;
+ } else if (arg == "-set") {
+ change = true;
+ ++i;
+ } else if (arg == "--help" || arg == "-help") {
+ usage();
+ exit(0);
+ } else {
+ fprintf(stderr, "error: unrecognized argument: %s\n", arg.c_str());
+ usage();
+ exit(1);
+ }
+ }
+
+ if (change) {
+ success = SetConsoleScreenBufferInfoEx(conout, &info);
+ if (success) {
+ printf("success\n");
+ } else {
+ printf("SetConsoleScreenBufferInfoEx call failed\n");
+ }
+ success = GetConsoleScreenBufferInfoEx(conout, &info);
+ ASSERT(success && "GetConsoleScreenBufferInfoEx failed");
+ }
+
+ auto dump = [](const char *fmt, ...) {
+ char msg[256];
+ va_list ap;
+ va_start(ap, fmt);
+ vsprintf(msg, fmt, ap);
+ va_end(ap);
+ trace("%s", msg);
+ printf("%s\n", msg);
+ };
+
+ dump("buffer-size: %d x %d", info.dwSize.X, info.dwSize.Y);
+ dump("window-size: %d x %d",
+ info.srWindow.Right - info.srWindow.Left + 1,
+ info.srWindow.Bottom - info.srWindow.Top + 1);
+ dump("window-pos: %d, %d", info.srWindow.Left, info.srWindow.Top);
+
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/SetBufferSize.cc b/src/libs/3rdparty/winpty/misc/SetBufferSize.cc
new file mode 100644
index 00000000000..b50a1f8dc36
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/SetBufferSize.cc
@@ -0,0 +1,32 @@
+#include <windows.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "TestUtil.cc"
+
+int main(int argc, char *argv[]) {
+ if (argc != 3) {
+ printf("Usage: %s x y width height\n", argv[0]);
+ return 1;
+ }
+
+ const HANDLE conout = CreateFileW(L"CONOUT$",
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL, OPEN_EXISTING, 0, NULL);
+ ASSERT(conout != INVALID_HANDLE_VALUE);
+
+ COORD size = {
+ (short)atoi(argv[1]),
+ (short)atoi(argv[2]),
+ };
+
+ BOOL ret = SetConsoleScreenBufferSize(conout, size);
+ const unsigned lastError = GetLastError();
+ const char *const retStr = ret ? "OK" : "failed";
+ trace("SetConsoleScreenBufferSize ret: %s (LastError=0x%x)", retStr, lastError);
+ printf("SetConsoleScreenBufferSize ret: %s (LastError=0x%x)\n", retStr, lastError);
+
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/SetCursorPos.cc b/src/libs/3rdparty/winpty/misc/SetCursorPos.cc
new file mode 100644
index 00000000000..d20fdbdfc0d
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/SetCursorPos.cc
@@ -0,0 +1,10 @@
+#include <windows.h>
+
+#include "TestUtil.cc"
+
+int main(int argc, char *argv[]) {
+ int col = atoi(argv[1]);
+ int row = atoi(argv[2]);
+ setCursorPos(col, row);
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/SetFont.cc b/src/libs/3rdparty/winpty/misc/SetFont.cc
new file mode 100644
index 00000000000..9bcd4b4cc97
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/SetFont.cc
@@ -0,0 +1,145 @@
+#include <windows.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string>
+
+#include "TestUtil.cc"
+
+#define COUNT_OF(array) (sizeof(array) / sizeof((array)[0]))
+
+// See https://en.wikipedia.org/wiki/List_of_CJK_fonts
+const wchar_t kMSGothic[] = { 0xff2d, 0xff33, 0x0020, 0x30b4, 0x30b7, 0x30c3, 0x30af, 0 }; // Japanese
+const wchar_t kNSimSun[] = { 0x65b0, 0x5b8b, 0x4f53, 0 }; // Simplified Chinese
+const wchar_t kMingLight[] = { 0x7d30, 0x660e, 0x9ad4, 0 }; // Traditional Chinese
+const wchar_t kGulimChe[] = { 0xad74, 0xb9bc, 0xccb4, 0 }; // Korean
+
+int main() {
+ setlocale(LC_ALL, "");
+ wchar_t *cmdline = GetCommandLineW();
+ int argc = 0;
+ wchar_t **argv = CommandLineToArgvW(cmdline, &argc);
+ const HANDLE conout = openConout();
+
+ if (argc == 1) {
+ cprintf(L"Usage:\n");
+ cprintf(L" SetFont <index>\n");
+ cprintf(L" SetFont options\n");
+ cprintf(L"\n");
+ cprintf(L"Options for SetCurrentConsoleFontEx:\n");
+ cprintf(L" -idx INDEX\n");
+ cprintf(L" -w WIDTH\n");
+ cprintf(L" -h HEIGHT\n");
+ cprintf(L" -family (0xNN|NN)\n");
+ cprintf(L" -weight (normal|bold|NNN)\n");
+ cprintf(L" -face FACENAME\n");
+ cprintf(L" -face-{gothic|simsun|minglight|gulimche) [JP,CN-sim,CN-tra,KR]\n");
+ cprintf(L" -tt\n");
+ cprintf(L" -vec\n");
+ cprintf(L" -vp\n");
+ cprintf(L" -dev\n");
+ cprintf(L" -roman\n");
+ cprintf(L" -swiss\n");
+ cprintf(L" -modern\n");
+ cprintf(L" -script\n");
+ cprintf(L" -decorative\n");
+ return 0;
+ }
+
+ if (isdigit(argv[1][0])) {
+ int index = _wtoi(argv[1]);
+ HMODULE kernel32 = LoadLibraryW(L"kernel32.dll");
+ FARPROC proc = GetProcAddress(kernel32, "SetConsoleFont");
+ if (proc == NULL) {
+ cprintf(L"Couldn't get address of SetConsoleFont\n");
+ } else {
+ BOOL ret = reinterpret_cast<BOOL WINAPI(*)(HANDLE, DWORD)>(proc)(
+ conout, index);
+ cprintf(L"SetFont returned %d\n", ret);
+ }
+ return 0;
+ }
+
+ CONSOLE_FONT_INFOEX fontex = {0};
+ fontex.cbSize = sizeof(fontex);
+
+ for (int i = 1; i < argc; ++i) {
+ std::wstring arg = argv[i];
+ if (i + 1 < argc) {
+ std::wstring next = argv[i + 1];
+ if (arg == L"-idx") {
+ fontex.nFont = _wtoi(next.c_str());
+ ++i; continue;
+ } else if (arg == L"-w") {
+ fontex.dwFontSize.X = _wtoi(next.c_str());
+ ++i; continue;
+ } else if (arg == L"-h") {
+ fontex.dwFontSize.Y = _wtoi(next.c_str());
+ ++i; continue;
+ } else if (arg == L"-weight") {
+ if (next == L"normal") {
+ fontex.FontWeight = 400;
+ } else if (next == L"bold") {
+ fontex.FontWeight = 700;
+ } else {
+ fontex.FontWeight = _wtoi(next.c_str());
+ }
+ ++i; continue;
+ } else if (arg == L"-face") {
+ wcsncpy(fontex.FaceName, next.c_str(), COUNT_OF(fontex.FaceName));
+ ++i; continue;
+ } else if (arg == L"-family") {
+ fontex.FontFamily = strtol(narrowString(next).c_str(), nullptr, 0);
+ ++i; continue;
+ }
+ }
+ if (arg == L"-tt") {
+ fontex.FontFamily |= TMPF_TRUETYPE;
+ } else if (arg == L"-vec") {
+ fontex.FontFamily |= TMPF_VECTOR;
+ } else if (arg == L"-vp") {
+ // Setting the TMPF_FIXED_PITCH bit actually indicates variable
+ // pitch.
+ fontex.FontFamily |= TMPF_FIXED_PITCH;
+ } else if (arg == L"-dev") {
+ fontex.FontFamily |= TMPF_DEVICE;
+ } else if (arg == L"-roman") {
+ fontex.FontFamily = (fontex.FontFamily & ~0xF0) | FF_ROMAN;
+ } else if (arg == L"-swiss") {
+ fontex.FontFamily = (fontex.FontFamily & ~0xF0) | FF_SWISS;
+ } else if (arg == L"-modern") {
+ fontex.FontFamily = (fontex.FontFamily & ~0xF0) | FF_MODERN;
+ } else if (arg == L"-script") {
+ fontex.FontFamily = (fontex.FontFamily & ~0xF0) | FF_SCRIPT;
+ } else if (arg == L"-decorative") {
+ fontex.FontFamily = (fontex.FontFamily & ~0xF0) | FF_DECORATIVE;
+ } else if (arg == L"-face-gothic") {
+ wcsncpy(fontex.FaceName, kMSGothic, COUNT_OF(fontex.FaceName));
+ } else if (arg == L"-face-simsun") {
+ wcsncpy(fontex.FaceName, kNSimSun, COUNT_OF(fontex.FaceName));
+ } else if (arg == L"-face-minglight") {
+ wcsncpy(fontex.FaceName, kMingLight, COUNT_OF(fontex.FaceName));
+ } else if (arg == L"-face-gulimche") {
+ wcsncpy(fontex.FaceName, kGulimChe, COUNT_OF(fontex.FaceName));
+ } else {
+ cprintf(L"Unrecognized argument: %ls\n", arg.c_str());
+ exit(1);
+ }
+ }
+
+ cprintf(L"Setting to: nFont=%u dwFontSize=(%d,%d) "
+ L"FontFamily=0x%x FontWeight=%u "
+ L"FaceName=\"%ls\"\n",
+ static_cast<unsigned>(fontex.nFont),
+ fontex.dwFontSize.X, fontex.dwFontSize.Y,
+ fontex.FontFamily, fontex.FontWeight,
+ fontex.FaceName);
+
+ BOOL ret = SetCurrentConsoleFontEx(
+ conout,
+ FALSE,
+ &fontex);
+ cprintf(L"SetCurrentConsoleFontEx returned %d\n", ret);
+
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/SetWindowRect.cc b/src/libs/3rdparty/winpty/misc/SetWindowRect.cc
new file mode 100644
index 00000000000..6291dd67459
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/SetWindowRect.cc
@@ -0,0 +1,36 @@
+#include <windows.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "TestUtil.cc"
+
+int main(int argc, char *argv[]) {
+ if (argc != 5) {
+ printf("Usage: %s x y width height\n", argv[0]);
+ return 1;
+ }
+
+ const HANDLE conout = CreateFileW(L"CONOUT$",
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL, OPEN_EXISTING, 0, NULL);
+ ASSERT(conout != INVALID_HANDLE_VALUE);
+
+ SMALL_RECT sr = {
+ (short)atoi(argv[1]),
+ (short)atoi(argv[2]),
+ (short)(atoi(argv[1]) + atoi(argv[3]) - 1),
+ (short)(atoi(argv[2]) + atoi(argv[4]) - 1),
+ };
+
+ trace("Calling SetConsoleWindowInfo with {L=%d,T=%d,R=%d,B=%d}",
+ sr.Left, sr.Top, sr.Right, sr.Bottom);
+ BOOL ret = SetConsoleWindowInfo(conout, TRUE, &sr);
+ const unsigned lastError = GetLastError();
+ const char *const retStr = ret ? "OK" : "failed";
+ trace("SetConsoleWindowInfo ret: %s (LastError=0x%x)", retStr, lastError);
+ printf("SetConsoleWindowInfo ret: %s (LastError=0x%x)\n", retStr, lastError);
+
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/ShowArgv.cc b/src/libs/3rdparty/winpty/misc/ShowArgv.cc
new file mode 100644
index 00000000000..29a0f091316
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/ShowArgv.cc
@@ -0,0 +1,12 @@
+// This test program is useful for studying commandline<->argv conversion.
+
+#include <stdio.h>
+#include <windows.h>
+
+int main(int argc, char **argv)
+{
+ printf("cmdline = [%s]\n", GetCommandLine());
+ for (int i = 0; i < argc; ++i)
+ printf("[%s]\n", argv[i]);
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/ShowConsoleInput.cc b/src/libs/3rdparty/winpty/misc/ShowConsoleInput.cc
new file mode 100644
index 00000000000..75fbfb81f10
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/ShowConsoleInput.cc
@@ -0,0 +1,40 @@
+#include <windows.h>
+#include <stdio.h>
+#include <ctype.h>
+
+int main(int argc, char *argv[])
+{
+ static int escCount = 0;
+
+ HANDLE hStdin = GetStdHandle(STD_INPUT_HANDLE);
+ while (true) {
+ DWORD count;
+ INPUT_RECORD ir;
+ if (!ReadConsoleInput(hStdin, &ir, 1, &count)) {
+ printf("ReadConsoleInput failed\n");
+ return 1;
+ }
+
+ if (true) {
+ DWORD mode;
+ GetConsoleMode(hStdin, &mode);
+ SetConsoleMode(hStdin, mode & ~ENABLE_PROCESSED_INPUT);
+ }
+
+ if (ir.EventType == KEY_EVENT) {
+ const KEY_EVENT_RECORD &ker = ir.Event.KeyEvent;
+ printf("%s", ker.bKeyDown ? "dn" : "up");
+ printf(" ch=");
+ if (isprint(ker.uChar.AsciiChar))
+ printf("'%c'", ker.uChar.AsciiChar);
+ printf("%d", ker.uChar.AsciiChar);
+ printf(" vk=%#x", ker.wVirtualKeyCode);
+ printf(" scan=%#x", ker.wVirtualScanCode);
+ printf(" state=%#x", (int)ker.dwControlKeyState);
+ printf(" repeat=%d", ker.wRepeatCount);
+ printf("\n");
+ if (ker.uChar.AsciiChar == 27 && ++escCount == 6)
+ break;
+ }
+ }
+}
diff --git a/src/libs/3rdparty/winpty/misc/Spew.py b/src/libs/3rdparty/winpty/misc/Spew.py
new file mode 100644
index 00000000000..9d1796af375
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/Spew.py
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+i = 0;
+while True:
+ i += 1
+ print(i)
diff --git a/src/libs/3rdparty/winpty/misc/TestUtil.cc b/src/libs/3rdparty/winpty/misc/TestUtil.cc
new file mode 100644
index 00000000000..c832a12b858
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/TestUtil.cc
@@ -0,0 +1,172 @@
+// This file is included into test programs using #include
+
+#include <windows.h>
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include <wchar.h>
+#include <vector>
+#include <string>
+
+#include "../src/shared/DebugClient.h"
+#include "../src/shared/TimeMeasurement.h"
+
+#include "../src/shared/DebugClient.cc"
+#include "../src/shared/WinptyAssert.cc"
+#include "../src/shared/WinptyException.cc"
+
+// Launch this test program again, in a new console that we will destroy.
+static void startChildProcess(const wchar_t *args) {
+ wchar_t program[1024];
+ wchar_t cmdline[1024];
+ GetModuleFileNameW(NULL, program, 1024);
+ swprintf(cmdline, L"\"%ls\" %ls", program, args);
+
+ STARTUPINFOW sui;
+ PROCESS_INFORMATION pi;
+ memset(&sui, 0, sizeof(sui));
+ memset(&pi, 0, sizeof(pi));
+ sui.cb = sizeof(sui);
+
+ CreateProcessW(program, cmdline,
+ NULL, NULL,
+ /*bInheritHandles=*/FALSE,
+ /*dwCreationFlags=*/CREATE_NEW_CONSOLE,
+ NULL, NULL,
+ &sui, &pi);
+}
+
+static void setBufferSize(HANDLE conout, int x, int y) {
+ COORD size = { static_cast<SHORT>(x), static_cast<SHORT>(y) };
+ BOOL success = SetConsoleScreenBufferSize(conout, size);
+ trace("setBufferSize: (%d,%d), result=%d", x, y, success);
+}
+
+static void setWindowPos(HANDLE conout, int x, int y, int w, int h) {
+ SMALL_RECT r = {
+ static_cast<SHORT>(x), static_cast<SHORT>(y),
+ static_cast<SHORT>(x + w - 1),
+ static_cast<SHORT>(y + h - 1)
+ };
+ BOOL success = SetConsoleWindowInfo(conout, /*bAbsolute=*/TRUE, &r);
+ trace("setWindowPos: (%d,%d,%d,%d), result=%d", x, y, w, h, success);
+}
+
+static void setCursorPos(HANDLE conout, int x, int y) {
+ COORD coord = { static_cast<SHORT>(x), static_cast<SHORT>(y) };
+ SetConsoleCursorPosition(conout, coord);
+}
+
+static void setBufferSize(int x, int y) {
+ setBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), x, y);
+}
+
+static void setWindowPos(int x, int y, int w, int h) {
+ setWindowPos(GetStdHandle(STD_OUTPUT_HANDLE), x, y, w, h);
+}
+
+static void setCursorPos(int x, int y) {
+ setCursorPos(GetStdHandle(STD_OUTPUT_HANDLE), x, y);
+}
+
+static void countDown(int sec) {
+ for (int i = sec; i > 0; --i) {
+ printf("%d.. ", i);
+ fflush(stdout);
+ Sleep(1000);
+ }
+ printf("\n");
+}
+
+static void writeBox(int x, int y, int w, int h, char ch, int attributes=7) {
+ CHAR_INFO info = { 0 };
+ info.Char.AsciiChar = ch;
+ info.Attributes = attributes;
+ std::vector<CHAR_INFO> buf(w * h, info);
+ HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE);
+ COORD bufSize = { static_cast<SHORT>(w), static_cast<SHORT>(h) };
+ COORD bufCoord = { 0, 0 };
+ SMALL_RECT writeRegion = {
+ static_cast<SHORT>(x),
+ static_cast<SHORT>(y),
+ static_cast<SHORT>(x + w - 1),
+ static_cast<SHORT>(y + h - 1)
+ };
+ WriteConsoleOutputA(conout, buf.data(), bufSize, bufCoord, &writeRegion);
+}
+
+static void setChar(int x, int y, char ch, int attributes=7) {
+ writeBox(x, y, 1, 1, ch, attributes);
+}
+
+static void fillChar(int x, int y, int repeat, char ch) {
+ COORD coord = { static_cast<SHORT>(x), static_cast<SHORT>(y) };
+ DWORD actual = 0;
+ FillConsoleOutputCharacterA(
+ GetStdHandle(STD_OUTPUT_HANDLE),
+ ch, repeat, coord, &actual);
+}
+
+static void repeatChar(int count, char ch) {
+ for (int i = 0; i < count; ++i) {
+ putchar(ch);
+ }
+ fflush(stdout);
+}
+
+// I don't know why, but wprintf fails to print this face name,
+// "MS ゴシック" (aka MS Gothic). It helps to use wprintf instead of printf, and
+// it helps to call `setlocale(LC_ALL, "")`, but the Japanese symbols are
+// ultimately converted to `?` symbols, even though MS Gothic is able to
+// display its own name, and the current code page is 932 (Shift-JIS).
+static void cvfprintf(HANDLE conout, const wchar_t *fmt, va_list ap) {
+ wchar_t buffer[256];
+ vswprintf(buffer, 256 - 1, fmt, ap);
+ buffer[255] = L'\0';
+ DWORD actual = 0;
+ if (!WriteConsoleW(conout, buffer, wcslen(buffer), &actual, NULL)) {
+ wprintf(L"WriteConsoleW call failed!\n");
+ }
+}
+
+static void cfprintf(HANDLE conout, const wchar_t *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ cvfprintf(conout, fmt, ap);
+ va_end(ap);
+}
+
+static void cprintf(const wchar_t *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ cvfprintf(GetStdHandle(STD_OUTPUT_HANDLE), fmt, ap);
+ va_end(ap);
+}
+
+static std::string narrowString(const std::wstring &input)
+{
+ int mblen = WideCharToMultiByte(
+ CP_UTF8, 0,
+ input.data(), input.size(),
+ NULL, 0, NULL, NULL);
+ if (mblen <= 0) {
+ return std::string();
+ }
+ std::vector<char> tmp(mblen);
+ int mblen2 = WideCharToMultiByte(
+ CP_UTF8, 0,
+ input.data(), input.size(),
+ tmp.data(), tmp.size(),
+ NULL, NULL);
+ assert(mblen2 == mblen);
+ return std::string(tmp.data(), tmp.size());
+}
+
+HANDLE openConout() {
+ const HANDLE conout = CreateFileW(L"CONOUT$",
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL, OPEN_EXISTING, 0, NULL);
+ ASSERT(conout != INVALID_HANDLE_VALUE);
+ return conout;
+}
diff --git a/src/libs/3rdparty/winpty/misc/UnicodeDoubleWidthTest.cc b/src/libs/3rdparty/winpty/misc/UnicodeDoubleWidthTest.cc
new file mode 100644
index 00000000000..7210d410323
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/UnicodeDoubleWidthTest.cc
@@ -0,0 +1,102 @@
+// Demonstrates how U+30FC is sometimes handled as a single-width character
+// when it should be handled as a double-width character.
+//
+// It only runs on computers where 932 is a valid code page. Set the system
+// local to "Japanese (Japan)" to ensure this.
+//
+// The problem seems to happen when U+30FC is printed in a console using the
+// Lucida Console font, and only when that font is at certain sizes.
+//
+
+#include <windows.h>
+#include <assert.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "TestUtil.cc"
+
+#define COUNT_OF(x) (sizeof(x) / sizeof((x)[0]))
+
+static void setFont(const wchar_t *faceName, int pxSize) {
+ CONSOLE_FONT_INFOEX infoex = {0};
+ infoex.cbSize = sizeof(infoex);
+ infoex.dwFontSize.Y = pxSize;
+ wcsncpy(infoex.FaceName, faceName, COUNT_OF(infoex.FaceName));
+ BOOL ret = SetCurrentConsoleFontEx(
+ GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &infoex);
+ assert(ret);
+}
+
+static bool performTest(const wchar_t testChar) {
+ const HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE);
+
+ SetConsoleTextAttribute(conout, 7);
+
+ system("cls");
+ DWORD actual = 0;
+ BOOL ret = WriteConsoleW(conout, &testChar, 1, &actual, NULL);
+ assert(ret && actual == 1);
+
+ CHAR_INFO verify[2];
+ COORD bufSize = {2, 1};
+ COORD bufCoord = {0, 0};
+ const SMALL_RECT readRegion = {0, 0, 1, 0};
+ SMALL_RECT actualRegion = readRegion;
+ ret = ReadConsoleOutputW(conout, verify, bufSize, bufCoord, &actualRegion);
+ assert(ret && !memcmp(&readRegion, &actualRegion, sizeof(readRegion)));
+ assert(verify[0].Char.UnicodeChar == testChar);
+
+ if (verify[1].Char.UnicodeChar == testChar) {
+ // Typical double-width behavior with a TrueType font. Pass.
+ assert(verify[0].Attributes == 0x107);
+ assert(verify[1].Attributes == 0x207);
+ return true;
+ } else if (verify[1].Char.UnicodeChar == 0) {
+ // Typical double-width behavior with a Raster Font. Pass.
+ assert(verify[0].Attributes == 7);
+ assert(verify[1].Attributes == 0);
+ return true;
+ } else if (verify[1].Char.UnicodeChar == L' ') {
+ // Single-width behavior. Fail.
+ assert(verify[0].Attributes == 7);
+ assert(verify[1].Attributes == 7);
+ return false;
+ } else {
+ // Unexpected output.
+ assert(false);
+ }
+}
+
+int main(int argc, char *argv[]) {
+ setlocale(LC_ALL, "");
+ if (argc == 1) {
+ startChildProcess(L"CHILD");
+ return 0;
+ }
+
+ assert(SetConsoleCP(932));
+ assert(SetConsoleOutputCP(932));
+
+ const wchar_t testChar = 0x30FC;
+ const wchar_t *const faceNames[] = {
+ L"Lucida Console",
+ L"Consolas",
+ L"MS ゴシック",
+ };
+
+ trace("Test started");
+
+ for (auto faceName : faceNames) {
+ for (int px = 1; px <= 50; ++px) {
+ setFont(faceName, px);
+ if (!performTest(testChar)) {
+ trace("FAILURE: %s %dpx", narrowString(faceName).c_str(), px);
+ }
+ }
+ }
+
+ trace("Test complete");
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/UnicodeWideTest1.cc b/src/libs/3rdparty/winpty/misc/UnicodeWideTest1.cc
new file mode 100644
index 00000000000..a8d798e70dd
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/UnicodeWideTest1.cc
@@ -0,0 +1,246 @@
+#include <windows.h>
+
+#include <assert.h>
+#include <vector>
+
+#include "TestUtil.cc"
+
+#define COUNT_OF(x) (sizeof(x) / sizeof((x)[0]))
+
+
+CHAR_INFO ci(wchar_t ch, WORD attributes) {
+ CHAR_INFO ret;
+ ret.Char.UnicodeChar = ch;
+ ret.Attributes = attributes;
+ return ret;
+}
+
+CHAR_INFO ci(wchar_t ch) {
+ return ci(ch, 7);
+}
+
+CHAR_INFO ci() {
+ return ci(L' ');
+}
+
+bool operator==(SMALL_RECT x, SMALL_RECT y) {
+ return !memcmp(&x, &y, sizeof(x));
+}
+
+SMALL_RECT sr(COORD pt, COORD size) {
+ return {
+ pt.X, pt.Y,
+ static_cast<SHORT>(pt.X + size.X - 1),
+ static_cast<SHORT>(pt.Y + size.Y - 1)
+ };
+}
+
+static void set(
+ const COORD pt,
+ const COORD size,
+ const std::vector<CHAR_INFO> &data) {
+ assert(data.size() == size.X * size.Y);
+ SMALL_RECT writeRegion = sr(pt, size);
+ BOOL ret = WriteConsoleOutputW(
+ GetStdHandle(STD_OUTPUT_HANDLE),
+ data.data(), size, {0, 0}, &writeRegion);
+ assert(ret && writeRegion == sr(pt, size));
+}
+
+static void set(
+ const COORD pt,
+ const std::vector<CHAR_INFO> &data) {
+ set(pt, {static_cast<SHORT>(data.size()), 1}, data);
+}
+
+static void writeAttrsAt(
+ const COORD pt,
+ const std::vector<WORD> &data) {
+ DWORD actual = 0;
+ BOOL ret = WriteConsoleOutputAttribute(
+ GetStdHandle(STD_OUTPUT_HANDLE),
+ data.data(), data.size(), pt, &actual);
+ assert(ret && actual == data.size());
+}
+
+static void writeCharsAt(
+ const COORD pt,
+ const std::vector<wchar_t> &data) {
+ DWORD actual = 0;
+ BOOL ret = WriteConsoleOutputCharacterW(
+ GetStdHandle(STD_OUTPUT_HANDLE),
+ data.data(), data.size(), pt, &actual);
+ assert(ret && actual == data.size());
+}
+
+static void writeChars(
+ const std::vector<wchar_t> &data) {
+ DWORD actual = 0;
+ BOOL ret = WriteConsoleW(
+ GetStdHandle(STD_OUTPUT_HANDLE),
+ data.data(), data.size(), &actual, NULL);
+ assert(ret && actual == data.size());
+}
+
+std::vector<CHAR_INFO> get(
+ const COORD pt,
+ const COORD size) {
+ std::vector<CHAR_INFO> data(size.X * size.Y);
+ SMALL_RECT readRegion = sr(pt, size);
+ BOOL ret = ReadConsoleOutputW(
+ GetStdHandle(STD_OUTPUT_HANDLE),
+ data.data(), size, {0, 0}, &readRegion);
+ assert(ret && readRegion == sr(pt, size));
+ return data;
+}
+
+std::vector<wchar_t> readCharsAt(
+ const COORD pt,
+ int size) {
+ std::vector<wchar_t> data(size);
+ DWORD actual = 0;
+ BOOL ret = ReadConsoleOutputCharacterW(
+ GetStdHandle(STD_OUTPUT_HANDLE),
+ data.data(), data.size(), pt, &actual);
+ assert(ret);
+ data.resize(actual); // With double-width chars, we can read fewer than `size`.
+ return data;
+}
+
+static void dump(const COORD pt, const COORD size) {
+ for (CHAR_INFO ci : get(pt, size)) {
+ printf("%04X %04X\n", ci.Char.UnicodeChar, ci.Attributes);
+ }
+}
+
+static void dumpCharsAt(const COORD pt, int size) {
+ for (wchar_t ch : readCharsAt(pt, size)) {
+ printf("%04X\n", ch);
+ }
+}
+
+static COORD getCursorPos() {
+ CONSOLE_SCREEN_BUFFER_INFO info = { sizeof(info) };
+ assert(GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info));
+ return info.dwCursorPosition;
+}
+
+static void test1() {
+ // We write "䀀䀀", then write "䀁" in the middle of the two. The second
+ // write turns the first and last cells into spaces. The LEADING/TRAILING
+ // flags retain consistency.
+ printf("test1 - overlap full-width char with full-width char\n");
+ writeCharsAt({1,0}, {0x4000, 0x4000});
+ dump({0,0}, {6,1});
+ printf("\n");
+ writeCharsAt({2,0}, {0x4001});
+ dump({0,0}, {6,1});
+ printf("\n");
+}
+
+static void test2() {
+ // Like `test1`, but use a lower-level API to do the write. Consistency is
+ // preserved here too -- the first and last cells are replaced with spaces.
+ printf("test2 - overlap full-width char with full-width char (lowlevel)\n");
+ writeCharsAt({1,0}, {0x4000, 0x4000});
+ dump({0,0}, {6,1});
+ printf("\n");
+ set({2,0}, {ci(0x4001,0x107), ci(0x4001,0x207)});
+ dump({0,0}, {6,1});
+ printf("\n");
+}
+
+static void test3() {
+ // However, the lower-level API can break the LEADING/TRAILING invariant
+ // explicitly:
+ printf("test3 - explicitly violate LEADING/TRAILING using lowlevel API\n");
+ set({1,0}, {
+ ci(0x4000, 0x207),
+ ci(0x4001, 0x107),
+ ci(0x3044, 7),
+ ci(L'X', 0x107),
+ ci(L'X', 0x207),
+ });
+ dump({0,0}, {7,1});
+}
+
+static void test4() {
+ // It is possible for the two cells of a double-width character to have two
+ // colors.
+ printf("test4 - use lowlevel to assign two colors to one full-width char\n");
+ set({0,0}, {
+ ci(0x4000, 0x142),
+ ci(0x4000, 0x224),
+ });
+ dump({0,0}, {2,1});
+}
+
+static void test5() {
+ // WriteConsoleOutputAttribute doesn't seem to affect the LEADING/TRAILING
+ // flags.
+ printf("test5 - WriteConsoleOutputAttribute cannot affect LEADING/TRAILING\n");
+
+ // Trying to clear the flags doesn't work...
+ writeCharsAt({0,0}, {0x4000});
+ dump({0,0}, {2,1});
+ writeAttrsAt({0,0}, {0x42, 0x24});
+ printf("\n");
+ dump({0,0}, {2,1});
+
+ // ... and trying to add them also doesn't work.
+ writeCharsAt({0,1}, {'A', ' '});
+ writeAttrsAt({0,1}, {0x107, 0x207});
+ printf("\n");
+ dump({0,1}, {2,1});
+}
+
+static void test6() {
+ // The cursor position may be on either cell of a double-width character.
+ // Visually, the cursor appears under both cells, regardless of which
+ // specific one has the cursor.
+ printf("test6 - cursor can be either left or right cell of full-width char\n");
+
+ writeCharsAt({2,1}, {0x4000});
+
+ setCursorPos(2, 1);
+ auto pos1 = getCursorPos();
+ Sleep(1000);
+
+ setCursorPos(3, 1);
+ auto pos2 = getCursorPos();
+ Sleep(1000);
+
+ setCursorPos(0, 15);
+ printf("%d,%d\n", pos1.X, pos1.Y);
+ printf("%d,%d\n", pos2.X, pos2.Y);
+}
+
+static void runTest(void (&test)()) {
+ system("cls");
+ setCursorPos(0, 14);
+ test();
+ system("pause");
+}
+
+int main(int argc, char *argv[]) {
+ if (argc == 1) {
+ startChildProcess(L"CHILD");
+ return 0;
+ }
+
+ setWindowPos(0, 0, 1, 1);
+ setBufferSize(80, 40);
+ setWindowPos(0, 0, 80, 40);
+
+ auto cp = GetConsoleOutputCP();
+ assert(cp == 932 || cp == 936 || cp == 949 || cp == 950);
+
+ runTest(test1);
+ runTest(test2);
+ runTest(test3);
+ runTest(test4);
+ runTest(test5);
+ runTest(test6);
+
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/UnicodeWideTest2.cc b/src/libs/3rdparty/winpty/misc/UnicodeWideTest2.cc
new file mode 100644
index 00000000000..05f80f70bd1
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/UnicodeWideTest2.cc
@@ -0,0 +1,130 @@
+//
+// Test half-width vs full-width characters.
+//
+
+#include <windows.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include "TestUtil.cc"
+
+static void writeChars(const wchar_t *text) {
+ wcslen(text);
+ const int len = wcslen(text);
+ DWORD actual = 0;
+ BOOL ret = WriteConsoleW(
+ GetStdHandle(STD_OUTPUT_HANDLE),
+ text, len, &actual, NULL);
+ trace("writeChars: ret=%d, actual=%lld", ret, (long long)actual);
+}
+
+static void dumpChars(int x, int y, int w, int h) {
+ BOOL ret;
+ const COORD bufSize = {w, h};
+ const COORD bufCoord = {0, 0};
+ const SMALL_RECT topLeft = {x, y, x + w - 1, y + h - 1};
+ CHAR_INFO mbcsData[w * h];
+ CHAR_INFO unicodeData[w * h];
+ SMALL_RECT readRegion;
+ readRegion = topLeft;
+ ret = ReadConsoleOutputW(GetStdHandle(STD_OUTPUT_HANDLE), unicodeData,
+ bufSize, bufCoord, &readRegion);
+ assert(ret);
+ readRegion = topLeft;
+ ret = ReadConsoleOutputA(GetStdHandle(STD_OUTPUT_HANDLE), mbcsData,
+ bufSize, bufCoord, &readRegion);
+ assert(ret);
+
+ printf("\n");
+ for (int i = 0; i < w * h; ++i) {
+ printf("(%02d,%02d) CHAR: %04x %4x -- %02x %4x\n",
+ x + i % w, y + i / w,
+ (unsigned short)unicodeData[i].Char.UnicodeChar,
+ (unsigned short)unicodeData[i].Attributes,
+ (unsigned char)mbcsData[i].Char.AsciiChar,
+ (unsigned short)mbcsData[i].Attributes);
+ }
+}
+
+int main(int argc, char *argv[]) {
+ system("cls");
+ setWindowPos(0, 0, 1, 1);
+ setBufferSize(80, 38);
+ setWindowPos(0, 0, 80, 38);
+
+ // Write text.
+ const wchar_t text1[] = {
+ 0x3044, // U+3044 (HIRAGANA LETTER I)
+ 0x2014, // U+2014 (EM DASH)
+ 0x3044, // U+3044 (HIRAGANA LETTER I)
+ 0xFF2D, // U+FF2D (FULLWIDTH LATIN CAPITAL LETTER M)
+ 0x30FC, // U+30FC (KATAKANA-HIRAGANA PROLONGED SOUND MARK)
+ 0x0031, // U+3031 (DIGIT ONE)
+ 0x2014, // U+2014 (EM DASH)
+ 0x0032, // U+0032 (DIGIT TWO)
+ 0x005C, // U+005C (REVERSE SOLIDUS)
+ 0x3044, // U+3044 (HIRAGANA LETTER I)
+ 0
+ };
+ setCursorPos(0, 0);
+ writeChars(text1);
+
+ setCursorPos(78, 1);
+ writeChars(L"<>");
+
+ const wchar_t text2[] = {
+ 0x0032, // U+3032 (DIGIT TWO)
+ 0x3044, // U+3044 (HIRAGANA LETTER I)
+ 0,
+ };
+ setCursorPos(78, 1);
+ writeChars(text2);
+
+ system("pause");
+
+ dumpChars(0, 0, 17, 1);
+ dumpChars(2, 0, 2, 1);
+ dumpChars(2, 0, 1, 1);
+ dumpChars(3, 0, 1, 1);
+ dumpChars(78, 1, 2, 1);
+ dumpChars(0, 2, 2, 1);
+
+ system("pause");
+ system("cls");
+
+ const wchar_t text3[] = {
+ 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 1
+ 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 2
+ 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 3
+ 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 4
+ 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 5
+ 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 6
+ 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 7
+ 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 8
+ 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 9
+ 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 10
+ 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 11
+ 0x30FC, 0x30FC, 0x30FC, 0xFF2D, // 12
+ L'\r', '\n',
+ L'\r', '\n',
+ 0
+ };
+ writeChars(text3);
+ system("pause");
+ {
+ const COORD bufSize = {80, 2};
+ const COORD bufCoord = {0, 0};
+ SMALL_RECT readRegion = {0, 0, 79, 1};
+ CHAR_INFO unicodeData[160];
+ BOOL ret = ReadConsoleOutputW(GetStdHandle(STD_OUTPUT_HANDLE), unicodeData,
+ bufSize, bufCoord, &readRegion);
+ assert(ret);
+ for (int i = 0; i < 96; ++i) {
+ printf("%04x ", unicodeData[i].Char.UnicodeChar);
+ }
+ printf("\n");
+ }
+
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/UnixEcho.cc b/src/libs/3rdparty/winpty/misc/UnixEcho.cc
new file mode 100644
index 00000000000..372e0451574
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/UnixEcho.cc
@@ -0,0 +1,89 @@
+/*
+ * Unix test code that puts the terminal into raw mode, then echos typed
+ * characters to stdout. Derived from sample code in the Stevens book, posted
+ * online at http://www.lafn.org/~dave/linux/terminalIO.html.
+ */
+
+#include <termios.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "FormatChar.h"
+
+static struct termios save_termios;
+static int term_saved;
+
+/* RAW! mode */
+int tty_raw(int fd)
+{
+ struct termios buf;
+
+ if (tcgetattr(fd, &save_termios) < 0) /* get the original state */
+ return -1;
+
+ buf = save_termios;
+
+ /* echo off, canonical mode off, extended input
+ processing off, signal chars off */
+ buf.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
+
+ /* no SIGINT on BREAK, CR-to-NL off, input parity
+ check off, don't strip the 8th bit on input,
+ ouput flow control off */
+ buf.c_iflag &= ~(BRKINT | ICRNL | ISTRIP | IXON);
+
+ /* clear size bits, parity checking off */
+ buf.c_cflag &= ~(CSIZE | PARENB);
+
+ /* set 8 bits/char */
+ buf.c_cflag |= CS8;
+
+ /* output processing off */
+ buf.c_oflag &= ~(OPOST);
+
+ buf.c_cc[VMIN] = 1; /* 1 byte at a time */
+ buf.c_cc[VTIME] = 0; /* no timer on input */
+
+ if (tcsetattr(fd, TCSAFLUSH, &buf) < 0)
+ return -1;
+
+ term_saved = 1;
+
+ return 0;
+}
+
+
+/* set it to normal! */
+int tty_reset(int fd)
+{
+ if (term_saved)
+ if (tcsetattr(fd, TCSAFLUSH, &save_termios) < 0)
+ return -1;
+
+ return 0;
+}
+
+
+int main()
+{
+ tty_raw(0);
+
+ int count = 0;
+ while (true) {
+ char ch;
+ char buf[16];
+ int actual = read(0, &ch, 1);
+ if (actual != 1) {
+ perror("read error");
+ break;
+ }
+ formatChar(buf, ch);
+ fputs(buf, stdout);
+ fflush(stdout);
+ if (ch == 3) // Ctrl-C
+ break;
+ }
+
+ tty_reset(0);
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/Utf16Echo.cc b/src/libs/3rdparty/winpty/misc/Utf16Echo.cc
new file mode 100644
index 00000000000..ef5f302de47
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/Utf16Echo.cc
@@ -0,0 +1,46 @@
+#include <windows.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <vector>
+#include <string>
+
+int main(int argc, char *argv[]) {
+ system("cls");
+
+ if (argc == 1) {
+ printf("Usage: %s hhhh\n", argv[0]);
+ return 0;
+ }
+
+ std::wstring dataToWrite;
+ for (int i = 1; i < argc; ++i) {
+ wchar_t ch = strtol(argv[i], NULL, 16);
+ dataToWrite.push_back(ch);
+ }
+
+ DWORD actual = 0;
+ BOOL ret = WriteConsoleW(
+ GetStdHandle(STD_OUTPUT_HANDLE),
+ dataToWrite.data(), dataToWrite.size(), &actual, NULL);
+ assert(ret && actual == dataToWrite.size());
+
+ // Read it back.
+ std::vector<CHAR_INFO> readBuffer(dataToWrite.size() * 2);
+ COORD bufSize = {static_cast<short>(readBuffer.size()), 1};
+ COORD bufCoord = {0, 0};
+ SMALL_RECT topLeft = {0, 0, static_cast<short>(readBuffer.size() - 1), 0};
+ ret = ReadConsoleOutputW(
+ GetStdHandle(STD_OUTPUT_HANDLE), readBuffer.data(),
+ bufSize, bufCoord, &topLeft);
+ assert(ret);
+
+ printf("\n");
+ for (int i = 0; i < readBuffer.size(); ++i) {
+ printf("CHAR: %04x %04x\n",
+ readBuffer[i].Char.UnicodeChar,
+ readBuffer[i].Attributes);
+ }
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/VeryLargeRead.cc b/src/libs/3rdparty/winpty/misc/VeryLargeRead.cc
new file mode 100644
index 00000000000..58f0897022e
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/VeryLargeRead.cc
@@ -0,0 +1,122 @@
+//
+// 2015-09-25
+// I measured these limits on the size of a single ReadConsoleOutputW call.
+// The limit seems to more-or-less disppear with Windows 8, which is the first
+// OS to stop using ALPCs for console I/O. My guess is that the new I/O
+// method does not use the 64KiB shared memory buffer that the ALPC method
+// uses.
+//
+// I'm guessing the remaining difference between Windows 8/8.1 and Windows 10
+// might be related to the 32-vs-64-bitness.
+//
+// Client OSs
+//
+// Windows XP 32-bit VM ==> up to 13304 characters
+// - 13304x1 works, but 13305x1 fails instantly
+// Windows 7 32-bit VM ==> between 16-17 thousand characters
+// - 16000x1 works, 17000x1 fails instantly
+// - 163x100 *crashes* conhost.exe but leaves VeryLargeRead.exe running
+// Windows 8 32-bit VM ==> between 240-250 million characters
+// - 10000x24000 works, but 10000x25000 does not
+// Windows 8.1 32-bit VM ==> between 240-250 million characters
+// - 10000x24000 works, but 10000x25000 does not
+// Windows 10 64-bit VM ==> no limit (tested to 576 million characters)
+// - 24000x24000 works
+// - `ver` reports [Version 10.0.10240], conhost.exe and ConhostV1.dll are
+// 10.0.10240.16384 for file and product version. ConhostV2.dll is
+// 10.0.10240.16391 for file and product version.
+//
+// Server OSs
+//
+// Windows Server 2008 64-bit VM ==> 14300-14400 characters
+// - 14300x1 works, 14400x1 fails instantly
+// - This OS does not have conhost.exe.
+// - `ver` reports [Version 6.0.6002]
+// Windows Server 2008 R2 64-bit VM ==> 15600-15700 characters
+// - 15600x1 works, 15700x1 fails instantly
+// - This OS has conhost.exe, and procexp.exe reveals console ALPC ports in
+// use in conhost.exe.
+// - `ver` reports [Version 6.1.7601], conhost.exe is 6.1.7601.23153 for file
+// and product version.
+// Windows Server 2012 64-bit VM ==> at least 100 million characters
+// - 10000x10000 works (VM had only 1GiB of RAM, so I skipped larger tests)
+// - This OS has Windows 8's task manager and procexp.exe reveals the same
+// lack of ALPC ports and the same \Device\ConDrv\* files as Windows 8.
+// - `ver` reports [Version 6.2.9200], conhost.exe is 6.2.9200.16579 for file
+// and product version.
+//
+// To summarize:
+//
+// client-OS server-OS notes
+// ---------------------------------------------------------------------------
+// XP Server 2008 CSRSS, small reads
+// 7 Server 2008 R2 ALPC-to-conhost, small reads
+// 8, 8.1 Server 2012 new I/O interface, large reads allowed
+// 10 <no server OS yet> enhanced console w/rewrapping
+//
+// (Presumably, Win2K, Vista, and Win2K3 behave the same as XP. conhost.exe
+// was announced as a Win7 feature.)
+//
+
+#include <windows.h>
+#include <assert.h>
+#include <vector>
+
+#include "TestUtil.cc"
+
+int main(int argc, char *argv[]) {
+ long long width = 9000;
+ long long height = 9000;
+
+ assert(argc >= 1);
+ if (argc == 4) {
+ width = atoi(argv[2]);
+ height = atoi(argv[3]);
+ } else {
+ if (argc == 3) {
+ width = atoi(argv[1]);
+ height = atoi(argv[2]);
+ }
+ wchar_t args[1024];
+ swprintf(args, 1024, L"CHILD %lld %lld", width, height);
+ startChildProcess(args);
+ return 0;
+ }
+
+ const HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE);
+
+ setWindowPos(0, 0, 1, 1);
+ setBufferSize(width, height);
+ setWindowPos(0, 0, std::min(80LL, width), std::min(50LL, height));
+
+ setCursorPos(0, 0);
+ printf("A");
+ fflush(stdout);
+ setCursorPos(width - 2, height - 1);
+ printf("B");
+ fflush(stdout);
+
+ trace("sizeof(CHAR_INFO) = %d", (int)sizeof(CHAR_INFO));
+
+ trace("Allocating buffer...");
+ CHAR_INFO *buffer = new CHAR_INFO[width * height];
+ assert(buffer != NULL);
+ memset(&buffer[0], 0, sizeof(CHAR_INFO));
+ memset(&buffer[width * height - 2], 0, sizeof(CHAR_INFO));
+
+ COORD bufSize = { width, height };
+ COORD bufCoord = { 0, 0 };
+ SMALL_RECT readRegion = { 0, 0, width - 1, height - 1 };
+ trace("ReadConsoleOutputW: calling...");
+ BOOL success = ReadConsoleOutputW(conout, buffer, bufSize, bufCoord, &readRegion);
+ trace("ReadConsoleOutputW: success=%d", success);
+
+ assert(buffer[0].Char.UnicodeChar == L'A');
+ assert(buffer[width * height - 2].Char.UnicodeChar == L'B');
+ trace("Top-left and bottom-right characters read successfully!");
+
+ Sleep(30000);
+
+ delete [] buffer;
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/VkEscapeTest.cc b/src/libs/3rdparty/winpty/misc/VkEscapeTest.cc
new file mode 100644
index 00000000000..97bf59f998b
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/VkEscapeTest.cc
@@ -0,0 +1,56 @@
+/*
+ * Sending VK_PAUSE to the console window almost works as a mechanism for
+ * pausing it, but it doesn't because the console could turn off the
+ * ENABLE_LINE_INPUT console mode flag.
+ */
+
+#define _WIN32_WINNT 0x0501
+#include <stdio.h>
+#include <stdlib.h>
+#include <windows.h>
+
+CALLBACK DWORD pausingThread(LPVOID dummy)
+{
+ if (1) {
+ Sleep(1000);
+ HWND hwnd = GetConsoleWindow();
+ SendMessage(hwnd, WM_KEYDOWN, VK_PAUSE, 1);
+ Sleep(1000);
+ SendMessage(hwnd, WM_KEYDOWN, VK_ESCAPE, 1);
+ }
+
+ if (0) {
+ INPUT_RECORD ir;
+ memset(&ir, 0, sizeof(ir));
+ ir.EventType = KEY_EVENT;
+ ir.Event.KeyEvent.bKeyDown = TRUE;
+ ir.Event.KeyEvent.wVirtualKeyCode = VK_PAUSE;
+ ir.Event.KeyEvent.wRepeatCount = 1;
+ }
+
+ return 0;
+}
+
+int main()
+{
+ HANDLE hin = GetStdHandle(STD_INPUT_HANDLE);
+ HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
+ COORD c = { 0, 0 };
+
+ DWORD mode;
+ GetConsoleMode(hin, &mode);
+ SetConsoleMode(hin, mode &
+ ~(ENABLE_LINE_INPUT));
+
+ CreateThread(NULL, 0,
+ pausingThread, NULL,
+ 0, NULL);
+
+ int i = 0;
+ while (true) {
+ Sleep(100);
+ printf("%d\n", ++i);
+ }
+
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/Win10ResizeWhileFrozen.cc b/src/libs/3rdparty/winpty/misc/Win10ResizeWhileFrozen.cc
new file mode 100644
index 00000000000..82feaf3c501
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/Win10ResizeWhileFrozen.cc
@@ -0,0 +1,52 @@
+/*
+ * Demonstrates a conhost hang that occurs when widening the console buffer
+ * while selection is in progress. The problem affects the new Windows 10
+ * console, not the "legacy" console mode that Windows 10 also includes.
+ *
+ * First tested with:
+ * - Windows 10.0.10240
+ * - conhost.exe version 10.0.10240.16384
+ * - ConhostV1.dll version 10.0.10240.16384
+ * - ConhostV2.dll version 10.0.10240.16391
+ */
+
+#include <windows.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <wchar.h>
+
+#include "TestUtil.cc"
+
+const int SC_CONSOLE_MARK = 0xFFF2;
+const int SC_CONSOLE_SELECT_ALL = 0xFFF5;
+
+int main(int argc, char *argv[]) {
+ if (argc == 1) {
+ startChildProcess(L"CHILD");
+ return 0;
+ }
+
+ setWindowPos(0, 0, 1, 1);
+ setBufferSize(80, 25);
+ setWindowPos(0, 0, 80, 25);
+
+ countDown(5);
+
+ SendMessage(GetConsoleWindow(), WM_SYSCOMMAND, SC_CONSOLE_SELECT_ALL, 0);
+ Sleep(2000);
+
+ // This API call does not return. In the console window, the "Select All"
+ // operation appears to end. The console window becomes non-responsive,
+ // and the conhost.exe process must be killed from the Task Manager.
+ // (Killing this test program or closing the console window is not
+ // sufficient.)
+ //
+ // The same hang occurs whether line resizing is off or on. It happens
+ // with both "Mark" and "Select All". Calling setBufferSize with the
+ // existing buffer size does not hang, but calling it with only a changed
+ // buffer height *does* hang. Calling setWindowPos does not hang.
+ setBufferSize(120, 25);
+
+ printf("Done...\n");
+ Sleep(2000);
+}
diff --git a/src/libs/3rdparty/winpty/misc/Win10WrapTest1.cc b/src/libs/3rdparty/winpty/misc/Win10WrapTest1.cc
new file mode 100644
index 00000000000..645fa95d542
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/Win10WrapTest1.cc
@@ -0,0 +1,57 @@
+/*
+ * Demonstrates some wrapping behaviors of the new Windows 10 console.
+ */
+
+#include <windows.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "TestUtil.cc"
+
+int main(int argc, char *argv[]) {
+ if (argc == 1) {
+ startChildProcess(L"CHILD");
+ return 0;
+ }
+
+ setWindowPos(0, 0, 1, 1);
+ setBufferSize(40, 20);
+ setWindowPos(0, 0, 40, 20);
+
+ system("cls");
+
+ repeatChar(39, 'A'); repeatChar(1, ' ');
+ repeatChar(39, 'B'); repeatChar(1, ' ');
+ printf("\n");
+
+ repeatChar(39, 'C'); repeatChar(1, ' ');
+ repeatChar(39, 'D'); repeatChar(1, ' ');
+ printf("\n");
+
+ repeatChar(40, 'E');
+ repeatChar(40, 'F');
+ printf("\n");
+
+ repeatChar(39, 'G'); repeatChar(1, ' ');
+ repeatChar(39, 'H'); repeatChar(1, ' ');
+ printf("\n");
+
+ Sleep(2000);
+
+ setChar(39, 0, '*', 0x24);
+ setChar(39, 1, '*', 0x24);
+
+ setChar(39, 3, ' ', 0x24);
+ setChar(39, 4, ' ', 0x24);
+
+ setChar(38, 6, ' ', 0x24);
+ setChar(38, 7, ' ', 0x24);
+
+ Sleep(2000);
+ setWindowPos(0, 0, 35, 20);
+ setBufferSize(35, 20);
+ trace("DONE");
+
+ printf("Sleeping forever...\n");
+ while(true) { Sleep(1000); }
+}
diff --git a/src/libs/3rdparty/winpty/misc/Win10WrapTest2.cc b/src/libs/3rdparty/winpty/misc/Win10WrapTest2.cc
new file mode 100644
index 00000000000..50615fc8c79
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/Win10WrapTest2.cc
@@ -0,0 +1,30 @@
+#include <windows.h>
+
+#include "TestUtil.cc"
+
+int main(int argc, char *argv[]) {
+ if (argc == 1) {
+ startChildProcess(L"CHILD");
+ return 0;
+ }
+
+ const int WIDTH = 25;
+
+ setWindowPos(0, 0, 1, 1);
+ setBufferSize(WIDTH, 40);
+ setWindowPos(0, 0, WIDTH, 20);
+
+ system("cls");
+
+ for (int i = 0; i < 100; ++i) {
+ printf("FOO(%d)\n", i);
+ }
+
+ repeatChar(5, '\n');
+ repeatChar(WIDTH * 5, '.');
+ repeatChar(10, '\n');
+ setWindowPos(0, 20, WIDTH, 20);
+ writeBox(0, 5, 1, 10, '|');
+
+ Sleep(120000);
+}
diff --git a/src/libs/3rdparty/winpty/misc/Win32Echo1.cc b/src/libs/3rdparty/winpty/misc/Win32Echo1.cc
new file mode 100644
index 00000000000..06fc79f7945
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/Win32Echo1.cc
@@ -0,0 +1,26 @@
+/*
+ * A Win32 program that reads raw console input with ReadFile and echos
+ * it to stdout.
+ */
+
+#include <stdio.h>
+#include <conio.h>
+#include <windows.h>
+
+int main()
+{
+ int count = 0;
+ HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
+ HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
+ SetConsoleMode(hStdIn, 0);
+
+ while (true) {
+ DWORD actual;
+ char ch;
+ ReadFile(hStdIn, &ch, 1, &actual, NULL);
+ printf("%02x ", ch);
+ if (++count == 50)
+ break;
+ }
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/Win32Echo2.cc b/src/libs/3rdparty/winpty/misc/Win32Echo2.cc
new file mode 100644
index 00000000000..b2ea2ad1c5d
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/Win32Echo2.cc
@@ -0,0 +1,19 @@
+/*
+ * A Win32 program that reads raw console input with getch and echos
+ * it to stdout.
+ */
+
+#include <stdio.h>
+#include <conio.h>
+
+int main()
+{
+ int count = 0;
+ while (true) {
+ int ch = getch();
+ printf("%02x ", ch);
+ if (++count == 50)
+ break;
+ }
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/Win32Test1.cc b/src/libs/3rdparty/winpty/misc/Win32Test1.cc
new file mode 100644
index 00000000000..a40d318a98c
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/Win32Test1.cc
@@ -0,0 +1,46 @@
+#define _WIN32_WINNT 0x0501
+#include "../src/shared/DebugClient.cc"
+#include <windows.h>
+#include <stdio.h>
+
+const int SC_CONSOLE_MARK = 0xFFF2;
+
+CALLBACK DWORD writerThread(void*)
+{
+ while (true) {
+ Sleep(1000);
+ trace("writing");
+ printf("X\n");
+ trace("written");
+ }
+}
+
+int main()
+{
+ CreateThread(NULL, 0, writerThread, NULL, 0, NULL);
+ trace("marking console");
+ HWND hwnd = GetConsoleWindow();
+ PostMessage(hwnd, WM_SYSCOMMAND, SC_CONSOLE_MARK, 0);
+
+ Sleep(2000);
+
+ trace("reading output");
+ CHAR_INFO buf[1];
+ COORD bufSize = { 1, 1 };
+ COORD zeroCoord = { 0, 0 };
+ SMALL_RECT readRect = { 0, 0, 0, 0 };
+ ReadConsoleOutput(GetStdHandle(STD_OUTPUT_HANDLE),
+ buf,
+ bufSize,
+ zeroCoord,
+ &readRect);
+ trace("done reading output");
+
+ Sleep(2000);
+
+ PostMessage(hwnd, WM_CHAR, 27, 0x00010001);
+
+ Sleep(1100);
+
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/Win32Test2.cc b/src/libs/3rdparty/winpty/misc/Win32Test2.cc
new file mode 100644
index 00000000000..2777bad4562
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/Win32Test2.cc
@@ -0,0 +1,70 @@
+/*
+ * This test demonstrates that putting a console into selection mode does not
+ * block the low-level console APIs, even though it blocks WriteFile.
+ */
+
+#define _WIN32_WINNT 0x0501
+#include "../src/shared/DebugClient.cc"
+#include <windows.h>
+#include <stdio.h>
+
+const int SC_CONSOLE_MARK = 0xFFF2;
+
+CALLBACK DWORD writerThread(void*)
+{
+ CHAR_INFO xChar, fillChar;
+ memset(&xChar, 0, sizeof(xChar));
+ xChar.Char.AsciiChar = 'X';
+ xChar.Attributes = 7;
+ memset(&fillChar, 0, sizeof(fillChar));
+ fillChar.Char.AsciiChar = ' ';
+ fillChar.Attributes = 7;
+ COORD oneCoord = { 1, 1 };
+ COORD zeroCoord = { 0, 0 };
+
+ while (true) {
+ SMALL_RECT writeRegion = { 5, 5, 5, 5 };
+ WriteConsoleOutput(GetStdHandle(STD_OUTPUT_HANDLE),
+ &xChar, oneCoord,
+ zeroCoord,
+ &writeRegion);
+ Sleep(500);
+ SMALL_RECT scrollRect = { 1, 1, 20, 20 };
+ COORD destCoord = { 0, 0 };
+ ScrollConsoleScreenBuffer(GetStdHandle(STD_OUTPUT_HANDLE),
+ &scrollRect,
+ NULL,
+ destCoord,
+ &fillChar);
+ }
+}
+
+int main()
+{
+ CreateThread(NULL, 0, writerThread, NULL, 0, NULL);
+ trace("marking console");
+ HWND hwnd = GetConsoleWindow();
+ PostMessage(hwnd, WM_SYSCOMMAND, SC_CONSOLE_MARK, 0);
+
+ Sleep(2000);
+
+ trace("reading output");
+ CHAR_INFO buf[1];
+ COORD bufSize = { 1, 1 };
+ COORD zeroCoord = { 0, 0 };
+ SMALL_RECT readRect = { 0, 0, 0, 0 };
+ ReadConsoleOutput(GetStdHandle(STD_OUTPUT_HANDLE),
+ buf,
+ bufSize,
+ zeroCoord,
+ &readRect);
+ trace("done reading output");
+
+ Sleep(2000);
+
+ PostMessage(hwnd, WM_CHAR, 27, 0x00010001);
+
+ Sleep(1100);
+
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/Win32Test3.cc b/src/libs/3rdparty/winpty/misc/Win32Test3.cc
new file mode 100644
index 00000000000..1fb92aff3df
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/Win32Test3.cc
@@ -0,0 +1,78 @@
+/*
+ * Creates a window station and starts a process under it. The new process
+ * also gets a new console.
+ */
+
+#include <windows.h>
+#include <string.h>
+#include <stdio.h>
+
+int main()
+{
+ BOOL success;
+
+ SECURITY_ATTRIBUTES sa;
+ memset(&sa, 0, sizeof(sa));
+ sa.bInheritHandle = TRUE;
+
+ HWINSTA originalStation = GetProcessWindowStation();
+ printf("originalStation == 0x%x\n", originalStation);
+ HWINSTA station = CreateWindowStation(NULL,
+ 0,
+ WINSTA_ALL_ACCESS,
+ &sa);
+ printf("station == 0x%x\n", station);
+ if (!SetProcessWindowStation(station))
+ printf("SetWindowStation failed!\n");
+ HDESK desktop = CreateDesktop("Default", NULL, NULL,
+ /*dwFlags=*/0, GENERIC_ALL,
+ &sa);
+ printf("desktop = 0x%x\n", desktop);
+
+ char stationName[256];
+ stationName[0] = '\0';
+ success = GetUserObjectInformation(station, UOI_NAME,
+ stationName, sizeof(stationName),
+ NULL);
+ printf("stationName = [%s]\n", stationName);
+
+ char startupDesktop[256];
+ sprintf(startupDesktop, "%s\\Default", stationName);
+
+ STARTUPINFO sui;
+ PROCESS_INFORMATION pi;
+ memset(&sui, 0, sizeof(sui));
+ memset(&pi, 0, sizeof(pi));
+ sui.cb = sizeof(STARTUPINFO);
+ sui.lpDesktop = startupDesktop;
+
+ // Start a cmd subprocess, and have it start its own cmd subprocess.
+ // Both subprocesses will connect to the same non-interactive window
+ // station.
+
+ const char program[] = "c:\\windows\\system32\\cmd.exe";
+ char cmdline[256];
+ sprintf(cmdline, "%s /c cmd", program);
+ success = CreateProcess(program,
+ cmdline,
+ NULL,
+ NULL,
+ /*bInheritHandles=*/FALSE,
+ /*dwCreationFlags=*/CREATE_NEW_CONSOLE,
+ NULL, NULL,
+ &sui,
+ &pi);
+
+ printf("pid == %d\n", pi.dwProcessId);
+
+ // This sleep is necessary. We must give the child enough time to
+ // connect to the specified window station.
+ Sleep(5000);
+
+ SetProcessWindowStation(originalStation);
+ CloseWindowStation(station);
+ CloseDesktop(desktop);
+ Sleep(5000);
+
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/Win32Write1.cc b/src/libs/3rdparty/winpty/misc/Win32Write1.cc
new file mode 100644
index 00000000000..6e5bf966822
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/Win32Write1.cc
@@ -0,0 +1,44 @@
+/*
+ * A Win32 program that scrolls and writes to the console using the ioctl-like
+ * interface.
+ */
+
+#include <stdio.h>
+#include <windows.h>
+
+int main()
+{
+ HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE);
+
+ for (int i = 0; i < 80; ++i) {
+
+ CONSOLE_SCREEN_BUFFER_INFO info;
+ GetConsoleScreenBufferInfo(conout, &info);
+
+ SMALL_RECT src = { 0, 1, info.dwSize.X - 1, info.dwSize.Y - 1 };
+ COORD destOrigin = { 0, 0 };
+ CHAR_INFO fillCharInfo = { 0 };
+ fillCharInfo.Char.AsciiChar = ' ';
+ fillCharInfo.Attributes = 7;
+ ScrollConsoleScreenBuffer(conout,
+ &src,
+ NULL,
+ destOrigin,
+ &fillCharInfo);
+
+ CHAR_INFO buffer = { 0 };
+ buffer.Char.AsciiChar = 'X';
+ buffer.Attributes = 7;
+ COORD bufferSize = { 1, 1 };
+ COORD bufferCoord = { 0, 0 };
+ SMALL_RECT writeRegion = { 0, 0, 0, 0 };
+ writeRegion.Left = writeRegion.Right = i;
+ writeRegion.Top = writeRegion.Bottom = 5;
+ WriteConsoleOutput(conout,
+ &buffer, bufferSize, bufferCoord,
+ &writeRegion);
+
+ Sleep(250);
+ }
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/WindowsBugCrashReader.cc b/src/libs/3rdparty/winpty/misc/WindowsBugCrashReader.cc
new file mode 100644
index 00000000000..e6d9558df63
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/WindowsBugCrashReader.cc
@@ -0,0 +1,27 @@
+// I noticed this on the ConEmu web site:
+//
+// https://social.msdn.microsoft.com/Forums/en-US/40c8e395-cca9-45c8-b9b8-2fbe6782ac2b/readconsoleoutput-cause-access-violation-writing-location-exception
+// https://conemu.github.io/en/MicrosoftBugs.html
+//
+// In Windows 7, 8, and 8.1, a ReadConsoleOutputW with an out-of-bounds read
+// region crashes the application. I have reproduced the problem on Windows 8
+// and 8.1, but not on Windows 7.
+//
+
+#include <windows.h>
+
+#include "TestUtil.cc"
+
+int main() {
+ setWindowPos(0, 0, 1, 1);
+ setBufferSize(80, 25);
+ setWindowPos(0, 0, 80, 25);
+
+ const HANDLE conout = openConout();
+ static CHAR_INFO lineBuf[80];
+ SMALL_RECT readRegion = { 0, 999, 79, 999 };
+ const BOOL ret = ReadConsoleOutputW(conout, lineBuf, {80, 1}, {0, 0}, &readRegion);
+ ASSERT(!ret && "ReadConsoleOutputW should have failed");
+
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/WriteConsole.cc b/src/libs/3rdparty/winpty/misc/WriteConsole.cc
new file mode 100644
index 00000000000..a03670ca929
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/WriteConsole.cc
@@ -0,0 +1,106 @@
+#include <windows.h>
+
+#include <assert.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <string>
+#include <vector>
+
+static std::wstring mbsToWcs(const std::string &s) {
+ const size_t len = mbstowcs(nullptr, s.c_str(), 0);
+ if (len == static_cast<size_t>(-1)) {
+ assert(false && "mbsToWcs: invalid string");
+ }
+ std::wstring ret;
+ ret.resize(len);
+ const size_t len2 = mbstowcs(&ret[0], s.c_str(), len);
+ assert(len == len2);
+ return ret;
+}
+
+uint32_t parseHex(wchar_t ch, bool &invalid) {
+ if (ch >= L'0' && ch <= L'9') {
+ return ch - L'0';
+ } else if (ch >= L'a' && ch <= L'f') {
+ return ch - L'a' + 10;
+ } else if (ch >= L'A' && ch <= L'F') {
+ return ch - L'A' + 10;
+ } else {
+ invalid = true;
+ return 0;
+ }
+}
+
+int main(int argc, char *argv[]) {
+ std::vector<std::wstring> args;
+ for (int i = 1; i < argc; ++i) {
+ args.push_back(mbsToWcs(argv[i]));
+ }
+
+ std::wstring out;
+ for (const auto &arg : args) {
+ if (!out.empty()) {
+ out.push_back(L' ');
+ }
+ for (size_t i = 0; i < arg.size(); ++i) {
+ wchar_t ch = arg[i];
+ wchar_t nch = i + 1 < arg.size() ? arg[i + 1] : L'\0';
+ if (ch == L'\\') {
+ switch (nch) {
+ case L'a': ch = L'\a'; ++i; break;
+ case L'b': ch = L'\b'; ++i; break;
+ case L'e': ch = L'\x1b'; ++i; break;
+ case L'f': ch = L'\f'; ++i; break;
+ case L'n': ch = L'\n'; ++i; break;
+ case L'r': ch = L'\r'; ++i; break;
+ case L't': ch = L'\t'; ++i; break;
+ case L'v': ch = L'\v'; ++i; break;
+ case L'\\': ch = L'\\'; ++i; break;
+ case L'\'': ch = L'\''; ++i; break;
+ case L'\"': ch = L'\"'; ++i; break;
+ case L'\?': ch = L'\?'; ++i; break;
+ case L'x':
+ if (i + 3 < arg.size()) {
+ bool invalid = false;
+ uint32_t d1 = parseHex(arg[i + 2], invalid);
+ uint32_t d2 = parseHex(arg[i + 3], invalid);
+ if (!invalid) {
+ i += 3;
+ ch = (d1 << 4) | d2;
+ }
+ }
+ break;
+ case L'u':
+ if (i + 5 < arg.size()) {
+ bool invalid = false;
+ uint32_t d1 = parseHex(arg[i + 2], invalid);
+ uint32_t d2 = parseHex(arg[i + 3], invalid);
+ uint32_t d3 = parseHex(arg[i + 4], invalid);
+ uint32_t d4 = parseHex(arg[i + 5], invalid);
+ if (!invalid) {
+ i += 5;
+ ch = (d1 << 24) | (d2 << 16) | (d3 << 8) | d4;
+ }
+ }
+ break;
+ default: break;
+ }
+ }
+ out.push_back(ch);
+ }
+ }
+
+ DWORD actual = 0;
+ if (!WriteConsoleW(
+ GetStdHandle(STD_OUTPUT_HANDLE),
+ out.c_str(),
+ out.size(),
+ &actual,
+ nullptr)) {
+ fprintf(stderr, "WriteConsole failed (is stdout a console?)\n");
+ exit(1);
+ }
+
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/misc/build32.sh b/src/libs/3rdparty/winpty/misc/build32.sh
new file mode 100644
index 00000000000..162993ce337
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/build32.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+set -e
+name=$1
+name=${name%.}
+name=${name%.cc}
+name=${name%.exe}
+echo Compiling $name.cc to $name.exe
+i686-w64-mingw32-g++.exe -static -std=c++11 $name.cc -o $name.exe
+i686-w64-mingw32-strip $name.exe
diff --git a/src/libs/3rdparty/winpty/misc/build64.sh b/src/libs/3rdparty/winpty/misc/build64.sh
new file mode 100644
index 00000000000..67579676847
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/build64.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+set -e
+name=$1
+name=${name%.}
+name=${name%.cc}
+name=${name%.exe}
+echo Compiling $name.cc to $name.exe
+x86_64-w64-mingw32-g++.exe -static -std=c++11 $name.cc -o $name.exe
+x86_64-w64-mingw32-strip $name.exe
diff --git a/src/libs/3rdparty/winpty/misc/color-test.sh b/src/libs/3rdparty/winpty/misc/color-test.sh
new file mode 100644
index 00000000000..065c8094e29
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/color-test.sh
@@ -0,0 +1,212 @@
+#!/bin/bash
+
+FORE=$1
+BACK=$2
+FILL=$3
+
+if [ "$FORE" = "" ]; then
+ FORE=DefaultFore
+fi
+if [ "$BACK" = "" ]; then
+ BACK=DefaultBack
+fi
+
+# To detect color changes, we want a character that fills the whole cell
+# if possible. U+2588 is perfect, except that it becomes invisible in the
+# original xterm, when bolded. For that terminal, use something else, like
+# "#" or "@".
+if [ "$FILL" = "" ]; then
+ FILL="█"
+fi
+
+# SGR (Select Graphic Rendition)
+s() {
+ printf '\033[0m'
+ while [ "$1" != "" ]; do
+ printf '\033['"$1"'m'
+ shift
+ done
+}
+
+# Print
+p() {
+ echo -n "$@"
+}
+
+# Print with newline
+pn() {
+ echo "$@"
+}
+
+# For practical reasons, sandwich black and white in-between the other colors.
+FORE_COLORS="31 30 37 32 33 34 35 36"
+BACK_COLORS="41 40 47 42 43 44 45 46"
+
+
+
+### Test order of Invert(7) -- it does not matter what order it appears in.
+
+# The Red color setting here (31) is shadowed by the green setting (32). The
+# Reverse flag does not cause (32) to alter the background color immediately;
+# instead, the Reverse flag is applied once to determine the final effective
+# Fore/Back colors.
+s 7 31 32; p " -- Should be: $BACK-on-green -- "; s; pn
+s 31 7 32; p " -- Should be: $BACK-on-green -- "; s; pn
+s 31 32 7; p " -- Should be: $BACK-on-green -- "; s; pn
+
+# As above, but for the background color.
+s 7 41 42; p " -- Should be: green-on-$FORE -- "; s; pn
+s 41 7 42; p " -- Should be: green-on-$FORE -- "; s; pn
+s 41 42 7; p " -- Should be: green-on-$FORE -- "; s; pn
+
+# One last, related test
+s 7; p "Invert text"; s 7 1; p " with some words bold"; s; pn;
+s 0; p "Normal text"; s 0 1; p " with some words bold"; s; pn;
+
+pn
+
+
+
+### Test effect of Bold(1) on color, with and without Invert(7).
+
+# The Bold flag does not affect the background color when Reverse is missing.
+# There should always be 8 colored boxes.
+p " "
+for x in $BACK_COLORS; do
+ s $x; p "-"; s $x 1; p "-"
+done
+s; pn " Bold should not affect background"
+
+# On some terminals, Bold affects color, and on some it doesn't. If there
+# are only 8 colored boxes, then the next two tests will also show 8 colored
+# boxes. If there are 16 boxes, then exactly one of the next two tests will
+# also have 16 boxes.
+p " "
+for x in $FORE_COLORS; do
+ s $x; p "$FILL"; s $x 1; p "$FILL"
+done
+s; pn " Does bold affect foreground color?"
+
+# On some terminals, Bold+Invert highlights the final Background color.
+p " "
+for x in $FORE_COLORS; do
+ s $x 7; p "-"; s $x 7 1; p "-"
+done
+s; pn " Test if Bold+Invert affects background color"
+
+# On some terminals, Bold+Invert highlights the final Foreground color.
+p " "
+for x in $BACK_COLORS; do
+ s $x 7; p "$FILL"; s $x 7 1; p "$FILL"
+done
+s; pn " Test if Bold+Invert affects foreground color"
+
+pn
+
+
+
+### Test for support of ForeHi and BackHi properties.
+
+# ForeHi
+p " "
+for x in $FORE_COLORS; do
+ hi=$(( $x + 60 ))
+ s $x; p "$FILL"; s $hi; p "$FILL"
+done
+s; pn " Test for support of ForeHi colors"
+p " "
+for x in $FORE_COLORS; do
+ hi=$(( $x + 60 ))
+ s $x; p "$FILL"; s $x $hi; p "$FILL"
+done
+s; pn " Test for support of ForeHi colors (w/compat)"
+
+# BackHi
+p " "
+for x in $BACK_COLORS; do
+ hi=$(( $x + 60 ))
+ s $x; p "-"; s $hi; p "-"
+done
+s; pn " Test for support of BackHi colors"
+p " "
+for x in $BACK_COLORS; do
+ hi=$(( $x + 60 ))
+ s $x; p "-"; s $x $hi; p "-"
+done
+s; pn " Test for support of BackHi colors (w/compat)"
+
+pn
+
+
+
+### Identify the default fore and back colors.
+
+pn "Match default fore and back colors against 16-color palette"
+pn " ==fore== ==back=="
+for fore in $FORE_COLORS; do
+ forehi=$(( $fore + 60 ))
+ back=$(( $fore + 10 ))
+ backhi=$(( $back + 60 ))
+ p " "
+ s $fore; p "$FILL"; s; p "$FILL"; s $fore; p "$FILL"; s; p " "
+ s $forehi; p "$FILL"; s; p "$FILL"; s $forehi; p "$FILL"; s; p " "
+ s $back; p "-"; s; p "-"; s $back; p "-"; s; p " "
+ s $backhi; p "-"; s; p "-"; s $backhi; p "-"; s; p " "
+ pn " $fore $forehi $back $backhi"
+done
+
+pn
+
+
+
+### Test coloring of rest-of-line.
+
+#
+# When a new line is scrolled in, every cell in the line receives the
+# current background color, which can be the default/transparent color.
+#
+
+p "Newline with red background: usually no red -->"; s 41; pn
+s; pn "This text is plain, but rest is red if scrolled -->"
+s; p " "; s 41; printf '\033[1K'; s; printf '\033[1C'; pn "<-- red Erase-in-Line to beginning"
+s; p "red Erase-in-Line to end -->"; s 41; printf '\033[0K'; s; pn
+pn
+
+
+
+### Moving the cursor around does not change colors of anything.
+
+pn "Test modifying uncolored lines with a colored SGR:"
+pn "aaaa"
+pn
+pn "____e"
+s 31 42; printf '\033[4C\033[3A'; pn "bb"
+pn "cccc"
+pn "dddd"
+s; pn
+
+pn "Test modifying colored+inverted+bold line with plain text:"
+s 42 31 7 1; printf 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\r';
+s; pn "This text is plain and followed by green-on-red -->"
+pn
+
+
+
+### Full-width character overwriting
+
+pn 'Overwrite part of a full-width char with a half-width char'
+p 'initial U+4000 ideographs -->'; s 31 42; p '䀀䀀'; s; pn
+p 'write X to index #1 -->'; s 31 42; p '䀀䀀'; s 35 44; printf '\033[24G'; p X; s; pn
+p 'write X to index #2 -->'; s 31 42; p '䀀䀀'; s 35 44; printf '\033[25G'; p X; s; pn
+p 'write X to index #3 -->'; s 31 42; p '䀀䀀'; s 35 44; printf '\033[26G'; p X; s; pn
+p 'write X to index #4 -->'; s 31 42; p '䀀䀀'; s 35 44; printf '\033[27G'; p X; s; pn
+pn
+
+pn 'Verify that Erase-in-Line can "fix" last char in line'
+p 'original -->'; s 31 42; p '䀀䀀'; s; pn
+p 'overwrite -->'; s 31 42; p '䀀䀀'; s 35 44; printf '\033[30G'; p 'XXX'; s; pn
+p 'overwrite + Erase-in-Line -->'; s 31 42; p '䀀䀀'; s 35 44; printf '\033[30G'; p 'XXX'; s; printf '\033[0K'; pn
+p 'original -->'; s 31 42; p 'X䀀䀀'; s; pn
+p 'overwrite -->'; s 31 42; p 'X䀀䀀'; s 35 44; printf '\033[30G'; p 'ーー'; s; pn
+p 'overwrite + Erase-in-Line -->'; s 31 42; p 'X䀀䀀'; s 35 44; printf '\033[30G'; p 'ーー'; s; printf '\033[0K'; pn
+pn
diff --git a/src/libs/3rdparty/winpty/misc/font-notes.txt b/src/libs/3rdparty/winpty/misc/font-notes.txt
new file mode 100644
index 00000000000..d4e36d8e25d
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/font-notes.txt
@@ -0,0 +1,300 @@
+==================================================================
+Notes regarding fonts, code pages, and East Asian character widths
+==================================================================
+
+
+Registry settings
+=================
+
+ * There are console registry settings in `HKCU\Console`. That key has many
+ default settings (e.g. the default font settings) and also per-app subkeys
+ for app-specific overrides.
+
+ * It is possible to override the code page with an app-specific setting.
+
+ * There are registry settings in
+ `HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Console`. In particular,
+ the `TrueTypeFont` subkey has a list of suitable font names associated with
+ various CJK code pages, as well as default font names.
+
+ * There are two values in `HKLM\SYSTEM\CurrentControlSet\Control\Nls\CodePage`
+ that specify the current code pages -- `OEMCP` and `ACP`. Setting the
+ system locale via the Control Panel's "Region" or "Language" dialogs seems
+ to change these code page values.
+
+
+Console fonts
+=============
+
+ * The `FontFamily` field of `CONSOLE_FONT_INFOEX` has two parts:
+ - The high four bits can be exactly one of the `FF_xxxx` font families:
+ FF_DONTCARE(0x00)
+ FF_ROMAN(0x10)
+ FF_SWISS(0x20)
+ FF_MODERN(0x30)
+ FF_SCRIPT(0x40)
+ FF_DECORATIVE(0x50)
+ - The low four bits are a bitmask:
+ TMPF_FIXED_PITCH(1) -- actually means variable pitch
+ TMPF_VECTOR(2)
+ TMPF_TRUETYPE(4)
+ TMPF_DEVICE(8)
+
+ * Each console has its own independent console font table. The current font
+ is identified with an index into this table. The size of the table is
+ returned by the undocumented `GetNumberOfConsoleFonts` API. It is apparently
+ possible to get the table size without this API, by instead calling
+ `GetConsoleFontSize` on each nonnegative index starting with 0 until the API
+ fails by returning (0, 0).
+
+ * The font table grows dynamically. Each time the console is configured with
+ a previously-unused (FaceName, Size) combination, two entries are added to
+ the font table -- one with normal weight and one with bold weight. Fonts
+ added this way are always TrueType fonts.
+
+ * Initially, the font table appears to contain only raster fonts. For
+ example, on an English Windows 8 installation, here is the initial font
+ table:
+ font 0: 4x6
+ font 1: 6x8
+ font 2: 8x8
+ font 3: 16x8
+ font 4: 5x12
+ font 5: 7x12
+ font 6: 8x12 -- the current font
+ font 7: 16x12
+ font 8: 12x16
+ font 9: 10x18
+ `GetNumberOfConsoleFonts` returns 10, and this table matches the raster font
+ sizes according to the console properties dialog.
+
+ * With a Japanese or Chinese locale, the initial font table appears to contain
+ the sizes applicable to both the East Asian raster font, as well as the
+ sizes for the CP437/CP1252 raster font.
+
+ * The index passed to `SetCurrentConsoleFontEx` apparently has no effect.
+ The undocumented `SetConsoleFont` API, however, accepts *only* a font index,
+ and on Windows 8 English, it switches between all 10 fonts, even font index
+ #0.
+
+ * If the index passed to `SetConsoleFont` identifies a Raster Font
+ incompatible with the current code page, then another Raster Font is
+ activated.
+
+ * Passing "Terminal" to `SetCurrentConsoleFontEx` seems to have no effect.
+ Perhaps relatedly, `SetCurrentConsoleFontEx` does not fail if it is given a
+ bogus `FaceName`. Some font is still chosen and activated. Passing a face
+ name and height seems to work reliably, modulo the CP936 issue described
+ below.
+
+
+Console fonts and code pages
+============================
+
+ * On an English Windows installation, the default code page is 437, and it
+ cannot be set to 932 (Shift-JIS). (The API call fails.) Changing the
+ system locale to "Japanese (Japan)" using the Region/Language dialog
+ changes the default CP to 932 and permits changing the console CP between
+ 437 and 932.
+
+ * A console has both an input code page and an output code page
+ (`{Get,Set}ConsoleCP` and `{Get,Set}ConsoleOutputCP`). I'm not going to
+ distinguish between the two for this document; presumably only the output
+ CP matters. The code page can change while the console is open, e.g.
+ by running `mode con: cp select={932,437,1252}` or by calling
+ `SetConsoleOutputCP`.
+
+ * The current code page restricts which TrueType fonts and which Raster Font
+ sizes are available in the console properties dialog. This can change
+ while the console is open.
+
+ * Changing the code page almost(?) always changes the current console font.
+ So far, I don't know how the new font is chosen.
+
+ * With a CP of 932, the only TrueType font available in the console properties
+ dialog is "MS Gothic", displayed as "MS ゴシック". It is still possible to
+ use the English-default TrueType console fonts, Lucida Console and Consolas,
+ via `SetCurrentConsoleFontEx`.
+
+ * When using a Raster Font and CP437 or CP1252, writing a UTF-16 codepoint not
+ representable in the code page instead writes a question mark ('?') to the
+ console. This conversion does not apply with a TrueType font, nor with the
+ Raster Font for CP932 or CP936.
+
+
+ReadConsoleOutput and double-width characters
+==============================================
+
+ * With a Raster Font active, when `ReadConsoleOutputW` reads two cells of a
+ double-width character, it fills only a single `CHAR_INFO` structure. The
+ unused trailing `CHAR_INFO` structures are zero-filled. With a TrueType
+ font active, `ReadConsoleOutputW` instead fills two `CHAR_INFO` structures,
+ the first marked with `COMMON_LVB_LEADING_BYTE` and the second marked with
+ `COMMON_LVB_TRAILING_BYTE`. The flag is a misnomer--there aren't two
+ *bytes*, but two cells, and they have equal `CHAR_INFO.Char.UnicodeChar`
+ values.
+
+ * `ReadConsoleOutputA`, on the other hand, reads two `CHAR_INFO` cells, and
+ if the UTF-16 value can be represented as two bytes in the ANSI/OEM CP, then
+ the two bytes are placed in the two `CHAR_INFO.Char.AsciiChar` values, and
+ the `COMMON_LVB_{LEADING,TRAILING}_BYTE` values are also used. If the
+ codepoint isn't representable, I don't remember what happens -- I think the
+ `AsciiChar` values take on an invalid marker.
+
+ * Reading only one cell of a double-width character reads a space (U+0020)
+ instead. Raster-vs-TrueType and wide-vs-ANSI do not matter.
+ - XXX: what about attributes? Can a double-width character have mismatched
+ color attributes?
+ - XXX: what happens when writing to just one cell of a double-width
+ character?
+
+
+Default Windows fonts for East Asian languages
+==============================================
+CP932 / Japanese: "MS ゴシック" (MS Gothic)
+CP936 / Chinese Simplified: "新宋体" (SimSun)
+
+
+Unreliable character width (half-width vs full-width)
+=====================================================
+
+The half-width vs full-width status of a codepoint depends on at least these variables:
+ * OS version (Win10 legacy and new modes are different versions)
+ * system locale (English vs Japanese vs Chinese Simplified vs Chinese Traditional, etc)
+ * code page (437 vs 932 vs 936, etc)
+ * raster vs TrueType (Terminal vs MS Gothic vs SimSun, etc)
+ * font size
+ * rendered-vs-model (rendered width can be larger or smaller than model width)
+
+Example 1: U+2014 (EM DASH): East_Asian_Width: Ambiguous
+--------------------------------------------------------
+ rendered modeled
+CP932: Win7/8 Raster Fonts half half
+CP932: Win7/8 Gothic 14/15px half full
+CP932: Win7/8 Consolas 14/15px half full
+CP932: Win7/8 Lucida Console 14px half full
+CP932: Win7/8 Lucida Console 15px half half
+CP932: Win10New Raster Fonts half half
+CP932: Win10New Gothic 14/15px half half
+CP932: Win10New Consolas 14/15px half half
+CP932: Win10New Lucida Console 14/15px half half
+
+CP936: Win7/8 Raster Fonts full full
+CP936: Win7/8 SimSun 14px full full
+CP936: Win7/8 SimSun 15px full half
+CP936: Win7/8 Consolas 14/15px half full
+CP936: Win10New Raster Fonts full full
+CP936: Win10New SimSum 14/15px full full
+CP936: Win10New Consolas 14/15px half half
+
+Example 2: U+3044 (HIRAGANA LETTER I): East_Asian_Width: Wide
+-------------------------------------------------------------
+ rendered modeled
+CP932: Win7/8/10N Raster Fonts full full
+CP932: Win7/8/10N Gothic 14/15px full full
+CP932: Win7/8/10N Consolas 14/15px half(*2) full
+CP932: Win7/8/10N Lucida Console 14/15px half(*3) full
+
+CP936: Win7/8/10N Raster Fonts full full
+CP936: Win7/8/10N SimSun 14/15px full full
+CP936: Win7/8/10N Consolas 14/15px full full
+
+Example 3: U+30FC (KATAKANA-HIRAGANA PROLONGED SOUND MARK): East_Asian_Width: Wide
+----------------------------------------------------------------------------------
+ rendered modeled
+CP932: Win7 Raster Fonts full full
+CP932: Win7 Gothic 14/15px full full
+CP932: Win7 Consolas 14/15px half(*2) full
+CP932: Win7 Lucida Console 14px half(*3) full
+CP932: Win7 Lucida Console 15px half(*3) half
+CP932: Win8 Raster Fonts full full
+CP932: Win8 Gothic 14px full half
+CP932: Win8 Gothic 15px full full
+CP932: Win8 Consolas 14/15px half(*2) full
+CP932: Win8 Lucida Console 14px half(*3) full
+CP932: Win8 Lucida Console 15px half(*3) half
+CP932: Win10New Raster Fonts full full
+CP932: Win10New Gothic 14/15px full full
+CP932: Win10New Consolas 14/15px half(*2) half
+CP932: Win10New Lucida Console 14/15px half(*2) half
+
+CP936: Win7/8 Raster Fonts full full
+CP936: Win7/8 SimSun 14px full full
+CP936: Win7/8 SimSun 15px full half
+CP936: Win7/8 Consolas 14px full full
+CP936: Win7/8 Consolas 15px full half
+CP936: Win10New Raster Fonts full full
+CP936: Win10New SimSum 14/15px full full
+CP936: Win10New Consolas 14/15px full full
+
+Example 4: U+4000 (CJK UNIFIED IDEOGRAPH-4000): East_Asian_Width: Wide
+----------------------------------------------------------------------
+ rendered modeled
+CP932: Win7 Raster Fonts half(*1) half
+CP932: Win7 Gothic 14/15px full full
+CP932: Win7 Consolas 14/15px half(*2) full
+CP932: Win7 Lucida Console 14px half(*3) full
+CP932: Win7 Lucida Console 15px half(*3) half
+CP932: Win8 Raster Fonts half(*1) half
+CP932: Win8 Gothic 14px full half
+CP932: Win8 Gothic 15px full full
+CP932: Win8 Consolas 14/15px half(*2) full
+CP932: Win8 Lucida Console 14px half(*3) full
+CP932: Win8 Lucida Console 15px half(*3) half
+CP932: Win10New Raster Fonts half(*1) half
+CP932: Win10New Gothic 14/15px full full
+CP932: Win10New Consolas 14/15px half(*2) half
+CP932: Win10New Lucida Console 14/15px half(*2) half
+
+CP936: Win7/8 Raster Fonts full full
+CP936: Win7/8 SimSun 14px full full
+CP936: Win7/8 SimSun 15px full half
+CP936: Win7/8 Consolas 14px full full
+CP936: Win7/8 Consolas 15px full half
+CP936: Win10New Raster Fonts full full
+CP936: Win10New SimSum 14/15px full full
+CP936: Win10New Consolas 14/15px full full
+
+(*1) Rendered as a half-width filled white box
+(*2) Rendered as a half-width box with a question mark inside
+(*3) Rendered as a half-width empty box
+(!!) One of the only places in Win10New where rendered and modeled width disagree
+
+
+Windows quirk: unreliable font heights with CP936 / Chinese Simplified
+======================================================================
+
+When I set the font to 新宋体 17px, using either the properties dialog or
+`SetCurrentConsoleFontEx`, the height reported by `GetCurrentConsoleFontEx` is
+not 17, but is instead 19. The same problem does not affect Raster Fonts,
+nor have I seen the problem in the English or Japanese locales. I observed
+this with Windows 7 and Windows 10 new mode.
+
+If I set the font using the facename, width, *and* height, then the
+`SetCurrentConsoleFontEx` and `GetCurrentConsoleFontEx` values agree. If I
+set the font using *only* the facename and height, then the two values
+disagree.
+
+
+Windows bug: GetCurrentConsoleFontEx is initially invalid
+=========================================================
+
+ - Assume there is no configured console font name in the registry. In this
+ case, the console defaults to a raster font.
+ - Open a new console and call the `GetCurrentConsoleFontEx` API.
+ - The `FaceName` field of the returned `CONSOLE_FONT_INFOEX` data
+ structure is incorrect. On Windows 7, 8, and 10, I observed that the
+ field was blank. On Windows 8, occasionally, it instead contained:
+ U+AE72 U+75BE U+0001
+ The other fields of the structure all appeared correct:
+ nFont=6 dwFontSize=(8,12) FontFamily=0x30 FontWeight=400
+ - The `FaceName` field becomes initialized easily:
+ - Open the console properties dialog and click OK. (Cancel is not
+ sufficient.)
+ - Call the undocumented `SetConsoleFont` with the current font table
+ index, which is 6 in the example above.
+ - It seems that the console uncritically accepts whatever string is
+ stored in the registry, including a blank string, and passes it on the
+ the `GetCurrentConsoleFontEx` caller. It is possible to get the console
+ to *write* a blank setting into the registry -- simply open the console
+ (default or app-specific) properties and click OK.
diff --git a/src/libs/3rdparty/winpty/misc/winbug-15048.cc b/src/libs/3rdparty/winpty/misc/winbug-15048.cc
new file mode 100644
index 00000000000..0e98d648c54
--- /dev/null
+++ b/src/libs/3rdparty/winpty/misc/winbug-15048.cc
@@ -0,0 +1,201 @@
+/*
+
+Test program demonstrating a problem in Windows 15048's ReadConsoleOutput API.
+
+To compile:
+
+ cl /nologo /EHsc winbug-15048.cc shell32.lib
+
+Example of regressed input:
+
+Case 1:
+
+ > chcp 932
+ > winbug-15048 -face-gothic 3044
+
+ Correct output:
+
+ 1**34 (nb: U+3044 replaced with '**' to avoid MSVC encoding warning)
+ 5678
+
+ ReadConsoleOutputW (both rows, 3 cols)
+ row 0: U+0031(0007) U+3044(0107) U+3044(0207) U+0033(0007)
+ row 1: U+0035(0007) U+0036(0007) U+0037(0007) U+0038(0007)
+
+ ReadConsoleOutputW (both rows, 4 cols)
+ row 0: U+0031(0007) U+3044(0107) U+3044(0207) U+0033(0007) U+0034(0007)
+ row 1: U+0035(0007) U+0036(0007) U+0037(0007) U+0038(0007) U+0020(0007)
+
+ ReadConsoleOutputW (second row)
+ row 1: U+0035(0007) U+0036(0007) U+0037(0007) U+0038(0007) U+0020(0007)
+
+ ...
+
+ Win10 15048 bad output:
+
+ 1**34
+ 5678
+
+ ReadConsoleOutputW (both rows, 3 cols)
+ row 0: U+0031(0007) U+3044(0007) U+0033(0007) U+0035(0007)
+ row 1: U+0036(0007) U+0037(0007) U+0038(0007) U+0000(0000)
+
+ ReadConsoleOutputW (both rows, 4 cols)
+ row 0: U+0031(0007) U+3044(0007) U+0033(0007) U+0034(0007) U+0035(0007)
+ row 1: U+0036(0007) U+0037(0007) U+0038(0007) U+0020(0007) U+0000(0000)
+
+ ReadConsoleOutputW (second row)
+ row 1: U+0035(0007) U+0036(0007) U+0037(0007) U+0038(0007) U+0020(0007)
+
+ ...
+
+ The U+3044 character (HIRAGANA LETTER I) occupies two columns, but it only
+ fills one record in the ReadConsoleOutput output buffer, which has the
+ effect of shifting the first cell of the second row into the last cell of
+ the first row. Ordinarily, the first and second cells would also have the
+ COMMON_LVB_LEADING_BYTE and COMMON_LVB_TRAILING_BYTE attributes set, which
+ allows winpty to detect the double-column character.
+
+Case 2:
+
+ > chcp 437
+ > winbug-15048 -face "Lucida Console" -h 4 221A
+
+ The same issue happens with U+221A (SQUARE ROOT), but only in certain
+ fonts. The console seems to think this character occupies two columns
+ if the font is sufficiently small. The Windows console properties dialog
+ doesn't allow fonts below 5 pt, but winpty tries to use 2pt and 4pt Lucida
+ Console to allow very large console windows.
+
+Case 3:
+
+ > chcp 437
+ > winbug-15048 -face "Lucida Console" -h 12 FF12
+
+ The console selection system thinks U+FF12 (FULLWIDTH DIGIT TWO) occupies
+ two columns, which happens to be correct, but it's displayed as a single
+ column unrecognized character. It otherwise behaves the same as the other
+ cases.
+
+*/
+
+#include <windows.h>
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <wchar.h>
+
+#include <string>
+
+#define COUNT_OF(array) (sizeof(array) / sizeof((array)[0]))
+
+// See https://en.wikipedia.org/wiki/List_of_CJK_fonts
+const wchar_t kMSGothic[] = { 0xff2d, 0xff33, 0x0020, 0x30b4, 0x30b7, 0x30c3, 0x30af, 0 }; // Japanese
+const wchar_t kNSimSun[] = { 0x65b0, 0x5b8b, 0x4f53, 0 }; // Simplified Chinese
+const wchar_t kMingLight[] = { 0x7d30, 0x660e, 0x9ad4, 0 }; // Traditional Chinese
+const wchar_t kGulimChe[] = { 0xad74, 0xb9bc, 0xccb4, 0 }; // Korean
+
+static void set_font(const wchar_t *name, int size) {
+ const HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE);
+ CONSOLE_FONT_INFOEX fontex {};
+ fontex.cbSize = sizeof(fontex);
+ fontex.dwFontSize.Y = size;
+ fontex.FontWeight = 400;
+ fontex.FontFamily = 0x36;
+ wcsncpy(fontex.FaceName, name, COUNT_OF(fontex.FaceName));
+ assert(SetCurrentConsoleFontEx(conout, FALSE, &fontex));
+}
+
+static void usage(const wchar_t *prog) {
+ printf("Usage: %ls [options]\n", prog);
+ printf(" -h HEIGHT\n");
+ printf(" -face FACENAME\n");
+ printf(" -face-{gothic|simsun|minglight|gulimche) [JP,CN-sim,CN-tra,KR]\n");
+ printf(" hhhh -- print U+hhhh\n");
+ exit(1);
+}
+
+static void dump_region(SMALL_RECT region, const char *name) {
+ const HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE);
+
+ CHAR_INFO buf[1000];
+ memset(buf, 0xcc, sizeof(buf));
+
+ const int w = region.Right - region.Left + 1;
+ const int h = region.Bottom - region.Top + 1;
+
+ assert(ReadConsoleOutputW(
+ conout, buf, { (short)w, (short)h }, { 0, 0 },
+ &region));
+
+ printf("\n");
+ printf("ReadConsoleOutputW (%s)\n", name);
+ for (int y = 0; y < h; ++y) {
+ printf("row %d: ", region.Top + y);
+ for (int i = 0; i < region.Left * 13; ++i) {
+ printf(" ");
+ }
+ for (int x = 0; x < w; ++x) {
+ const int i = y * w + x;
+ printf("U+%04x(%04x) ", buf[i].Char.UnicodeChar, buf[i].Attributes);
+ }
+ printf("\n");
+ }
+}
+
+int main() {
+ wchar_t *cmdline = GetCommandLineW();
+ int argc = 0;
+ wchar_t **argv = CommandLineToArgvW(cmdline, &argc);
+ const wchar_t *font_name = L"Lucida Console";
+ int font_height = 8;
+ int test_ch = 0xff12; // U+FF12 FULLWIDTH DIGIT TWO
+
+ for (int i = 1; i < argc; ++i) {
+ const std::wstring arg = argv[i];
+ const std::wstring next = i + 1 < argc ? argv[i + 1] : L"";
+ if (arg == L"-face" && i + 1 < argc) {
+ font_name = argv[i + 1];
+ i++;
+ } else if (arg == L"-face-gothic") {
+ font_name = kMSGothic;
+ } else if (arg == L"-face-simsun") {
+ font_name = kNSimSun;
+ } else if (arg == L"-face-minglight") {
+ font_name = kMingLight;
+ } else if (arg == L"-face-gulimche") {
+ font_name = kGulimChe;
+ } else if (arg == L"-h" && i + 1 < argc) {
+ font_height = _wtoi(next.c_str());
+ i++;
+ } else if (arg.c_str()[0] != '-') {
+ test_ch = wcstol(arg.c_str(), NULL, 16);
+ } else {
+ printf("Unrecognized argument: %ls\n", arg.c_str());
+ usage(argv[0]);
+ }
+ }
+
+ const HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE);
+
+ set_font(font_name, font_height);
+
+ system("cls");
+ DWORD actual = 0;
+ wchar_t output[] = L"1234\n5678\n";
+ output[1] = test_ch;
+ WriteConsoleW(conout, output, 10, &actual, nullptr);
+
+ dump_region({ 0, 0, 3, 1 }, "both rows, 3 cols");
+ dump_region({ 0, 0, 4, 1 }, "both rows, 4 cols");
+ dump_region({ 0, 1, 4, 1 }, "second row");
+ dump_region({ 0, 0, 4, 0 }, "first row");
+ dump_region({ 1, 0, 4, 0 }, "first row, skip 1");
+ dump_region({ 2, 0, 4, 0 }, "first row, skip 2");
+ dump_region({ 3, 0, 4, 0 }, "first row, skip 3");
+
+ set_font(font_name, 14);
+
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/ship/build-pty4j-libpty.bat b/src/libs/3rdparty/winpty/ship/build-pty4j-libpty.bat
new file mode 100644
index 00000000000..b6bca7b0793
--- /dev/null
+++ b/src/libs/3rdparty/winpty/ship/build-pty4j-libpty.bat
@@ -0,0 +1,36 @@
+@echo off
+
+setlocal
+cd %~dp0..
+set Path=C:\Python27;C:\Program Files\Git\cmd;%Path%
+
+call "%VS140COMNTOOLS%\VsDevCmd.bat" || goto :fail
+
+rmdir /s/q build-libpty 2>NUL
+mkdir build-libpty\win
+mkdir build-libpty\win\x86
+mkdir build-libpty\win\x86_64
+mkdir build-libpty\win\xp
+
+rmdir /s/q src\Release 2>NUL
+rmdir /s/q src\.vs 2>NUL
+del src\*.vcxproj src\*.vcxproj.filters src\*.sln src\*.sdf 2>NUL
+
+call vcbuild.bat --msvc-platform Win32 --gyp-msvs-version 2015 --toolset v140_xp || goto :fail
+copy src\Release\Win32\winpty.dll build-libpty\win\xp || goto :fail
+copy src\Release\Win32\winpty-agent.exe build-libpty\win\xp || goto :fail
+
+call vcbuild.bat --msvc-platform Win32 --gyp-msvs-version 2015 || goto :fail
+copy src\Release\Win32\winpty.dll build-libpty\win\x86 || goto :fail
+copy src\Release\Win32\winpty-agent.exe build-libpty\win\x86 || goto :fail
+
+call vcbuild.bat --msvc-platform x64 --gyp-msvs-version 2015 || goto :fail
+copy src\Release\x64\winpty.dll build-libpty\win\x86_64 || goto :fail
+copy src\Release\x64\winpty-agent.exe build-libpty\win\x86_64 || goto :fail
+
+echo success
+goto :EOF
+
+:fail
+echo error: build failed
+exit /b 1
diff --git a/src/libs/3rdparty/winpty/ship/common_ship.py b/src/libs/3rdparty/winpty/ship/common_ship.py
new file mode 100644
index 00000000000..b587ac7ce11
--- /dev/null
+++ b/src/libs/3rdparty/winpty/ship/common_ship.py
@@ -0,0 +1,89 @@
+import os
+import sys
+
+# These scripts need to continue using Python 2 rather than 3, because
+# make_msvc_package.py puts the current Python interpreter on the PATH for the
+# sake of gyp, and gyp doesn't work with Python 3 yet.
+# https://bugs.chromium.org/p/gyp/issues/detail?id=36
+if os.name != "nt":
+ sys.exit("Error: ship scripts require native Python 2.7. (wrong os.name)")
+if sys.version_info[0:2] != (2,7):
+ sys.exit("Error: ship scripts require native Python 2.7. (wrong version)")
+
+import glob
+import hashlib
+import shutil
+import subprocess
+from distutils.spawn import find_executable
+
+topDir = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
+
+with open(topDir + "/VERSION.txt", "rt") as f:
+ winptyVersion = f.read().strip()
+
+def rmrf(patterns):
+ for pattern in patterns:
+ for path in glob.glob(pattern):
+ if os.path.isdir(path) and not os.path.islink(path):
+ print("+ rm -r " + path)
+ sys.stdout.flush()
+ shutil.rmtree(path)
+ elif os.path.isfile(path):
+ print("+ rm " + path)
+ sys.stdout.flush()
+ os.remove(path)
+
+def mkdir(path):
+ if not os.path.isdir(path):
+ os.makedirs(path)
+
+def requireExe(name, guesses):
+ if find_executable(name) is None:
+ for guess in guesses:
+ if os.path.exists(guess):
+ newDir = os.path.dirname(guess)
+ print("Adding " + newDir + " to Path to provide " + name)
+ os.environ["Path"] = newDir + ";" + os.environ["Path"]
+ ret = find_executable(name)
+ if ret is None:
+ sys.exit("Error: required EXE is missing from Path: " + name)
+ return ret
+
+class ModifyEnv:
+ def __init__(self, **kwargs):
+ self._changes = dict(kwargs)
+ self._original = dict()
+
+ def __enter__(self):
+ for var, val in self._changes.items():
+ self._original[var] = os.environ[var]
+ os.environ[var] = val
+
+ def __exit__(self, type, value, traceback):
+ for var, val in self._original.items():
+ os.environ[var] = val
+
+def sha256(path):
+ with open(path, "rb") as fp:
+ return hashlib.sha256(fp.read()).hexdigest()
+
+def checkSha256(path, expected):
+ actual = sha256(path)
+ if actual != expected:
+ sys.exit("error: sha256 hash mismatch on {}: expected {}, found {}".format(
+ path, expected, actual))
+
+requireExe("git.exe", [
+ "C:\\Program Files\\Git\\cmd\\git.exe",
+ "C:\\Program Files (x86)\\Git\\cmd\\git.exe"
+])
+
+commitHash = subprocess.check_output(["git.exe", "rev-parse", "HEAD"]).strip()
+defaultPathEnviron = "C:\\Windows\\System32;C:\\Windows"
+
+ZIP_TOOL = requireExe("7z.exe", [
+ "C:\\Program Files\\7-Zip\\7z.exe",
+ "C:\\Program Files (x86)\\7-Zip\\7z.exe",
+])
+
+requireExe("curl.exe", [])
diff --git a/src/libs/3rdparty/winpty/ship/make_msvc_package.py b/src/libs/3rdparty/winpty/ship/make_msvc_package.py
new file mode 100644
index 00000000000..2d10aac7878
--- /dev/null
+++ b/src/libs/3rdparty/winpty/ship/make_msvc_package.py
@@ -0,0 +1,163 @@
+#!python
+
+# Copyright (c) 2016 Ryan Prichard
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+#
+# Run with native CPython 2.7.
+#
+# This script looks for MSVC using a version-specific environment variable,
+# such as VS140COMNTOOLS for MSVC 2015.
+#
+
+import common_ship
+
+import argparse
+import os
+import shutil
+import subprocess
+import sys
+
+os.chdir(common_ship.topDir)
+
+MSVC_VERSION_TABLE = {
+ "2015" : {
+ "package_name" : "msvc2015",
+ "gyp_version" : "2015",
+ "common_tools_env" : "VS140COMNTOOLS",
+ "xp_toolset" : "v140_xp",
+ },
+ "2013" : {
+ "package_name" : "msvc2013",
+ "gyp_version" : "2013",
+ "common_tools_env" : "VS120COMNTOOLS",
+ "xp_toolset" : "v120_xp",
+ },
+}
+
+ARCH_TABLE = {
+ "x64" : {
+ "msvc_platform" : "x64",
+ },
+ "ia32" : {
+ "msvc_platform" : "Win32",
+ },
+}
+
+def readArguments():
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--msvc-version", default="2015")
+ ret = parser.parse_args()
+ if ret.msvc_version not in MSVC_VERSION_TABLE:
+ sys.exit("Error: unrecognized version: " + ret.msvc_version + ". " +
+ "Versions: " + " ".join(sorted(MSVC_VERSION_TABLE.keys())))
+ return ret
+
+ARGS = readArguments()
+
+def checkoutGyp():
+ if os.path.isdir("build-gyp"):
+ return
+ subprocess.check_call([
+ "git.exe",
+ "clone",
+ "https://chromium.googlesource.com/external/gyp",
+ "build-gyp"
+ ])
+
+def cleanMsvc():
+ common_ship.rmrf("""
+ src/Release src/.vs src/gen
+ src/*.vcxproj src/*.vcxproj.filters src/*.sln src/*.sdf
+ """.split())
+
+def build(arch, packageDir, xp=False):
+ archInfo = ARCH_TABLE[arch]
+ versionInfo = MSVC_VERSION_TABLE[ARGS.msvc_version]
+
+ devCmdPath = os.path.join(os.environ[versionInfo["common_tools_env"]], "VsDevCmd.bat")
+ if not os.path.isfile(devCmdPath):
+ sys.exit("Error: MSVC environment script missing: " + devCmdPath)
+
+ toolsetArgument = " --toolset {}".format(versionInfo["xp_toolset"]) if xp else ""
+ newEnv = os.environ.copy()
+ newEnv["PATH"] = os.path.dirname(sys.executable) + ";" + common_ship.defaultPathEnviron
+ commandLine = (
+ '"' + devCmdPath + '" && ' +
+ " vcbuild.bat" +
+ " --gyp-msvs-version " + versionInfo["gyp_version"] +
+ " --msvc-platform " + archInfo["msvc_platform"] +
+ " --commit-hash " + common_ship.commitHash +
+ toolsetArgument
+ )
+
+ subprocess.check_call(commandLine, shell=True, env=newEnv)
+
+ archPackageDir = os.path.join(packageDir, arch)
+ if xp:
+ archPackageDir += "_xp"
+
+ common_ship.mkdir(archPackageDir + "/bin")
+ common_ship.mkdir(archPackageDir + "/lib")
+
+ binSrc = os.path.join(common_ship.topDir, "src/Release", archInfo["msvc_platform"])
+
+ shutil.copy(binSrc + "/winpty.dll", archPackageDir + "/bin")
+ shutil.copy(binSrc + "/winpty-agent.exe", archPackageDir + "/bin")
+ shutil.copy(binSrc + "/winpty-debugserver.exe", archPackageDir + "/bin")
+ shutil.copy(binSrc + "/winpty.lib", archPackageDir + "/lib")
+
+def buildPackage():
+ versionInfo = MSVC_VERSION_TABLE[ARGS.msvc_version]
+
+ packageName = "winpty-%s-%s" % (
+ common_ship.winptyVersion,
+ versionInfo["package_name"],
+ )
+
+ packageRoot = os.path.join(common_ship.topDir, "ship/packages")
+ packageDir = os.path.join(packageRoot, packageName)
+ packageFile = packageDir + ".zip"
+
+ common_ship.rmrf([packageDir])
+ common_ship.rmrf([packageFile])
+ common_ship.mkdir(packageDir)
+
+ checkoutGyp()
+ cleanMsvc()
+ build("ia32", packageDir, True)
+ build("x64", packageDir, True)
+ cleanMsvc()
+ build("ia32", packageDir)
+ build("x64", packageDir)
+
+ topDir = common_ship.topDir
+
+ common_ship.mkdir(packageDir + "/include")
+ shutil.copy(topDir + "/src/include/winpty.h", packageDir + "/include")
+ shutil.copy(topDir + "/src/include/winpty_constants.h", packageDir + "/include")
+ shutil.copy(topDir + "/LICENSE", packageDir)
+ shutil.copy(topDir + "/README.md", packageDir)
+ shutil.copy(topDir + "/RELEASES.md", packageDir)
+
+ subprocess.check_call([common_ship.ZIP_TOOL, "a", packageFile, "."], cwd=packageDir)
+
+if __name__ == "__main__":
+ buildPackage()
diff --git a/src/libs/3rdparty/winpty/ship/ship.py b/src/libs/3rdparty/winpty/ship/ship.py
new file mode 100644
index 00000000000..44f5862e3eb
--- /dev/null
+++ b/src/libs/3rdparty/winpty/ship/ship.py
@@ -0,0 +1,104 @@
+#!python
+
+# Copyright (c) 2015 Ryan Prichard
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+#
+# Run with native CPython 2.7 on a 64-bit computer.
+#
+
+import common_ship
+
+from optparse import OptionParser
+import multiprocessing
+import os
+import shutil
+import subprocess
+import sys
+
+
+def dllVersion(path):
+ version = subprocess.check_output(
+ ["powershell.exe",
+ "[System.Diagnostics.FileVersionInfo]::GetVersionInfo(\"" + path + "\").FileVersion"])
+ return version.strip()
+
+
+os.chdir(common_ship.topDir)
+
+
+# Determine other build parameters.
+BUILD_KINDS = {
+ "cygwin": {
+ "path": ["bin"],
+ "dll": "bin\\cygwin1.dll",
+ },
+ "msys2": {
+ "path": ["opt\\bin", "usr\\bin"],
+ "dll": "usr\\bin\\msys-2.0.dll",
+ },
+}
+
+
+def buildTarget(kind, syspath, arch):
+
+ binPaths = [os.path.join(syspath, p) for p in BUILD_KINDS[kind]["path"]]
+ binPaths += common_ship.defaultPathEnviron.split(";")
+ newPath = ";".join(binPaths)
+
+ dllver = dllVersion(os.path.join(syspath, BUILD_KINDS[kind]["dll"]))
+ packageName = "winpty-{}-{}-{}-{}".format(common_ship.winptyVersion, kind, dllver, arch)
+ if os.path.exists("ship\\packages\\" + packageName):
+ shutil.rmtree("ship\\packages\\" + packageName)
+
+ print("+ Setting PATH to: {}".format(newPath))
+ with common_ship.ModifyEnv(PATH=newPath):
+ subprocess.check_call(["sh.exe", "configure"])
+ subprocess.check_call(["make.exe", "clean"])
+ makeBaseCmd = [
+ "make.exe",
+ "COMMIT_HASH=" + common_ship.commitHash,
+ "PREFIX=ship/packages/" + packageName
+ ]
+ subprocess.check_call(makeBaseCmd + ["all", "tests", "-j%d" % multiprocessing.cpu_count()])
+ subprocess.check_call(["build\\trivial_test.exe"])
+ subprocess.check_call(makeBaseCmd + ["install"])
+ subprocess.check_call(["tar.exe", "cvfz",
+ packageName + ".tar.gz",
+ packageName], cwd=os.path.join(os.getcwd(), "ship", "packages"))
+
+
+def main():
+ parser = OptionParser()
+ parser.add_option("--kind", type="choice", choices=["cygwin", "msys2"])
+ parser.add_option("--syspath")
+ parser.add_option("--arch", type="choice", choices=["ia32", "x64"])
+ (args, extra) = parser.parse_args()
+
+ args.kind or parser.error("--kind must be specified")
+ args.arch or parser.error("--arch must be specified")
+ args.syspath or parser.error("--syspath must be specified")
+ extra and parser.error("unexpected positional argument(s)")
+
+ buildTarget(args.kind, args.syspath, args.arch)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/libs/3rdparty/winpty/src/CMakeLists.txt b/src/libs/3rdparty/winpty/src/CMakeLists.txt
new file mode 100644
index 00000000000..22b15111d4f
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/CMakeLists.txt
@@ -0,0 +1,111 @@
+if (MSVC)
+ add_compile_definitions(NOMINMAX UNICODE _UNICODE)
+endif()
+
+file(WRITE ${CMAKE_BINARY_DIR}/GenVersion.h.in [=[
+const char GenVersion_Version[] = "@VERSION@";
+const char GenVersion_Commit[] = "@COMMIT_HASH@";
+]=])
+
+file(READ ../VERSION.txt VERSION)
+string(REPLACE "\n" "" VERSION "${VERSION}")
+configure_file(${CMAKE_BINARY_DIR}/GenVersion.h.in ${CMAKE_BINARY_DIR}/GenVersion.h @ONLY)
+
+set(shared_sources
+ shared/AgentMsg.h
+ shared/BackgroundDesktop.h
+ shared/BackgroundDesktop.cc
+ shared/Buffer.h
+ shared/Buffer.cc
+ shared/DebugClient.h
+ shared/DebugClient.cc
+ shared/GenRandom.h
+ shared/GenRandom.cc
+ shared/OsModule.h
+ shared/OwnedHandle.h
+ shared/OwnedHandle.cc
+ shared/StringBuilder.h
+ shared/StringUtil.cc
+ shared/StringUtil.h
+ shared/UnixCtrlChars.h
+ shared/WindowsSecurity.cc
+ shared/WindowsSecurity.h
+ shared/WindowsVersion.h
+ shared/WindowsVersion.cc
+ shared/WinptyAssert.h
+ shared/WinptyAssert.cc
+ shared/WinptyException.h
+ shared/WinptyException.cc
+ shared/WinptyVersion.h
+ shared/WinptyVersion.cc
+ shared/winpty_snprintf.h
+)
+
+#
+# winpty-agent
+#
+
+add_qtc_executable(winpty-agent
+ INCLUDES
+ include ${CMAKE_BINARY_DIR}
+ DEFINES WINPTY_AGENT_ASSERT
+ PROPERTIES QT_COMPILE_OPTIONS_DISABLE_WARNINGS ON
+ SOURCES
+ agent/Agent.h
+ agent/Agent.cc
+ agent/AgentCreateDesktop.h
+ agent/AgentCreateDesktop.cc
+ agent/ConsoleFont.cc
+ agent/ConsoleFont.h
+ agent/ConsoleInput.cc
+ agent/ConsoleInput.h
+ agent/ConsoleInputReencoding.cc
+ agent/ConsoleInputReencoding.h
+ agent/ConsoleLine.cc
+ agent/ConsoleLine.h
+ agent/Coord.h
+ agent/DebugShowInput.h
+ agent/DebugShowInput.cc
+ agent/DefaultInputMap.h
+ agent/DefaultInputMap.cc
+ agent/DsrSender.h
+ agent/EventLoop.h
+ agent/EventLoop.cc
+ agent/InputMap.h
+ agent/InputMap.cc
+ agent/LargeConsoleRead.h
+ agent/LargeConsoleRead.cc
+ agent/NamedPipe.h
+ agent/NamedPipe.cc
+ agent/Scraper.h
+ agent/Scraper.cc
+ agent/SimplePool.h
+ agent/SmallRect.h
+ agent/Terminal.h
+ agent/Terminal.cc
+ agent/UnicodeEncoding.h
+ agent/Win32Console.cc
+ agent/Win32Console.h
+ agent/Win32ConsoleBuffer.cc
+ agent/Win32ConsoleBuffer.h
+ agent/main.cc
+ ${shared_sources}
+)
+
+#
+# libwinpty
+#
+
+add_qtc_library(winpty STATIC
+ INCLUDES ${CMAKE_BINARY_DIR}
+ PUBLIC_DEFINES COMPILING_WINPTY_DLL
+ PROPERTIES QT_COMPILE_OPTIONS_DISABLE_WARNINGS ON
+ SOURCES
+ libwinpty/AgentLocation.cc
+ libwinpty/AgentLocation.h
+ libwinpty/winpty.cc
+ ${shared_sources}
+)
+
+target_include_directories(winpty
+ PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>)
diff --git a/src/libs/3rdparty/winpty/src/agent/Agent.cc b/src/libs/3rdparty/winpty/src/agent/Agent.cc
new file mode 100644
index 00000000000..986edead133
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/Agent.cc
@@ -0,0 +1,612 @@
+// Copyright (c) 2011-2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include "Agent.h"
+
+#include <windows.h>
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <algorithm>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "../include/winpty_constants.h"
+
+#include "../shared/AgentMsg.h"
+#include "../shared/Buffer.h"
+#include "../shared/DebugClient.h"
+#include "../shared/GenRandom.h"
+#include "../shared/StringBuilder.h"
+#include "../shared/StringUtil.h"
+#include "../shared/WindowsVersion.h"
+#include "../shared/WinptyAssert.h"
+
+#include "ConsoleFont.h"
+#include "ConsoleInput.h"
+#include "NamedPipe.h"
+#include "Scraper.h"
+#include "Terminal.h"
+#include "Win32ConsoleBuffer.h"
+
+namespace {
+
+static BOOL WINAPI consoleCtrlHandler(DWORD dwCtrlType)
+{
+ if (dwCtrlType == CTRL_C_EVENT) {
+ // Do nothing and claim to have handled the event.
+ return TRUE;
+ }
+ return FALSE;
+}
+
+// We can detect the new Windows 10 console by observing the effect of the
+// Mark command. In older consoles, Mark temporarily moves the cursor to the
+// top-left of the console window. In the new console, the cursor isn't
+// initially moved.
+//
+// We might like to use Mark to freeze the console, but we can't, because when
+// the Mark command ends, the console moves the cursor back to its starting
+// point, even if the console application has moved it in the meantime.
+static void detectNewWindows10Console(
+ Win32Console &console, Win32ConsoleBuffer &buffer)
+{
+ if (!isAtLeastWindows8()) {
+ return;
+ }
+
+ ConsoleScreenBufferInfo info = buffer.bufferInfo();
+
+ // Make sure the window isn't 1x1. AFAIK, this should never happen
+ // accidentally. It is difficult to make it happen deliberately.
+ if (info.srWindow.Left == info.srWindow.Right &&
+ info.srWindow.Top == info.srWindow.Bottom) {
+ trace("detectNewWindows10Console: Initial console window was 1x1 -- "
+ "expanding for test");
+ setSmallFont(buffer.conout(), 400, false);
+ buffer.moveWindow(SmallRect(0, 0, 1, 1));
+ buffer.resizeBuffer(Coord(400, 1));
+ buffer.moveWindow(SmallRect(0, 0, 2, 1));
+ // This use of GetLargestConsoleWindowSize ought to be unnecessary
+ // given the behavior I've seen from moveWindow(0, 0, 1, 1), but
+ // I'd like to be especially sure, considering that this code will
+ // rarely be tested.
+ const auto largest = GetLargestConsoleWindowSize(buffer.conout());
+ buffer.moveWindow(
+ SmallRect(0, 0, std::min(largest.X, buffer.bufferSize().X), 1));
+ info = buffer.bufferInfo();
+ ASSERT(info.srWindow.Right > info.srWindow.Left &&
+ "Could not expand console window from 1x1");
+ }
+
+ // Test whether MARK moves the cursor.
+ const Coord initialPosition(info.srWindow.Right, info.srWindow.Bottom);
+ buffer.setCursorPosition(initialPosition);
+ ASSERT(!console.frozen());
+ console.setFreezeUsesMark(true);
+ console.setFrozen(true);
+ const bool isNewW10 = (buffer.cursorPosition() == initialPosition);
+ console.setFrozen(false);
+ buffer.setCursorPosition(Coord(0, 0));
+
+ trace("Attempting to detect new Windows 10 console using MARK: %s",
+ isNewW10 ? "detected" : "not detected");
+ console.setFreezeUsesMark(false);
+ console.setNewW10(isNewW10);
+}
+
+static inline WriteBuffer newPacket() {
+ WriteBuffer packet;
+ packet.putRawValue<uint64_t>(0); // Reserve space for size.
+ return packet;
+}
+
+static HANDLE duplicateHandle(HANDLE h) {
+ HANDLE ret = nullptr;
+ if (!DuplicateHandle(
+ GetCurrentProcess(), h,
+ GetCurrentProcess(), &ret,
+ 0, FALSE, DUPLICATE_SAME_ACCESS)) {
+ ASSERT(false && "DuplicateHandle failed!");
+ }
+ return ret;
+}
+
+// It's safe to truncate a handle from 64-bits to 32-bits, or to sign-extend it
+// back to 64-bits. See the MSDN article, "Interprocess Communication Between
+// 32-bit and 64-bit Applications".
+// https://msdn.microsoft.com/en-us/library/windows/desktop/aa384203.aspx
+static int64_t int64FromHandle(HANDLE h) {
+ return static_cast<int64_t>(reinterpret_cast<intptr_t>(h));
+}
+
+} // anonymous namespace
+
+Agent::Agent(LPCWSTR controlPipeName,
+ uint64_t agentFlags,
+ int mouseMode,
+ int initialCols,
+ int initialRows) :
+ m_useConerr((agentFlags & WINPTY_FLAG_CONERR) != 0),
+ m_plainMode((agentFlags & WINPTY_FLAG_PLAIN_OUTPUT) != 0),
+ m_mouseMode(mouseMode)
+{
+ trace("Agent::Agent entered");
+
+ ASSERT(initialCols >= 1 && initialRows >= 1);
+ initialCols = std::min(initialCols, MAX_CONSOLE_WIDTH);
+ initialRows = std::min(initialRows, MAX_CONSOLE_HEIGHT);
+
+ const bool outputColor =
+ !m_plainMode || (agentFlags & WINPTY_FLAG_COLOR_ESCAPES);
+ const Coord initialSize(initialCols, initialRows);
+
+ auto primaryBuffer = openPrimaryBuffer();
+ if (m_useConerr) {
+ m_errorBuffer = Win32ConsoleBuffer::createErrorBuffer();
+ }
+
+ detectNewWindows10Console(m_console, *primaryBuffer);
+
+ m_controlPipe = &connectToControlPipe(controlPipeName);
+ m_coninPipe = &createDataServerPipe(false, L"conin");
+ m_conoutPipe = &createDataServerPipe(true, L"conout");
+ if (m_useConerr) {
+ m_conerrPipe = &createDataServerPipe(true, L"conerr");
+ }
+
+ // Send an initial response packet to winpty.dll containing pipe names.
+ {
+ auto setupPacket = newPacket();
+ setupPacket.putWString(m_coninPipe->name());
+ setupPacket.putWString(m_conoutPipe->name());
+ if (m_useConerr) {
+ setupPacket.putWString(m_conerrPipe->name());
+ }
+ writePacket(setupPacket);
+ }
+
+ std::unique_ptr<Terminal> primaryTerminal;
+ primaryTerminal.reset(new Terminal(*m_conoutPipe,
+ m_plainMode,
+ outputColor));
+ m_primaryScraper.reset(new Scraper(m_console,
+ *primaryBuffer,
+ std::move(primaryTerminal),
+ initialSize));
+ if (m_useConerr) {
+ std::unique_ptr<Terminal> errorTerminal;
+ errorTerminal.reset(new Terminal(*m_conerrPipe,
+ m_plainMode,
+ outputColor));
+ m_errorScraper.reset(new Scraper(m_console,
+ *m_errorBuffer,
+ std::move(errorTerminal),
+ initialSize));
+ }
+
+ m_console.setTitle(m_currentTitle);
+
+ const HANDLE conin = GetStdHandle(STD_INPUT_HANDLE);
+ m_consoleInput.reset(
+ new ConsoleInput(conin, m_mouseMode, *this, m_console));
+
+ // Setup Ctrl-C handling. First restore default handling of Ctrl-C. This
+ // attribute is inherited by child processes. Then register a custom
+ // Ctrl-C handler that does nothing. The handler will be called when the
+ // agent calls GenerateConsoleCtrlEvent.
+ SetConsoleCtrlHandler(NULL, FALSE);
+ SetConsoleCtrlHandler(consoleCtrlHandler, TRUE);
+
+ setPollInterval(25);
+}
+
+Agent::~Agent()
+{
+ trace("Agent::~Agent entered");
+ agentShutdown();
+ if (m_childProcess != NULL) {
+ CloseHandle(m_childProcess);
+ }
+}
+
+// Write a "Device Status Report" command to the terminal. The terminal will
+// reply with a row+col escape sequence. Presumably, the DSR reply will not
+// split a keypress escape sequence, so it should be safe to assume that the
+// bytes before it are complete keypresses.
+void Agent::sendDsr()
+{
+ if (!m_plainMode && !m_conoutPipe->isClosed()) {
+ m_conoutPipe->write("\x1B[6n");
+ }
+}
+
+NamedPipe &Agent::connectToControlPipe(LPCWSTR pipeName)
+{
+ NamedPipe &pipe = createNamedPipe();
+ pipe.connectToServer(pipeName, NamedPipe::OpenMode::Duplex);
+ pipe.setReadBufferSize(64 * 1024);
+ return pipe;
+}
+
+// Returns a new server named pipe. It has not yet been connected.
+NamedPipe &Agent::createDataServerPipe(bool write, const wchar_t *kind)
+{
+ const auto name =
+ (WStringBuilder(128)
+ << L"\\\\.\\pipe\\winpty-"
+ << kind << L'-'
+ << GenRandom().uniqueName()).str_moved();
+ NamedPipe &pipe = createNamedPipe();
+ pipe.openServerPipe(
+ name.c_str(),
+ write ? NamedPipe::OpenMode::Writing
+ : NamedPipe::OpenMode::Reading,
+ write ? 8192 : 0,
+ write ? 0 : 256);
+ if (!write) {
+ pipe.setReadBufferSize(64 * 1024);
+ }
+ return pipe;
+}
+
+void Agent::onPipeIo(NamedPipe &namedPipe)
+{
+ if (&namedPipe == m_conoutPipe || &namedPipe == m_conerrPipe) {
+ autoClosePipesForShutdown();
+ } else if (&namedPipe == m_coninPipe) {
+ pollConinPipe();
+ } else if (&namedPipe == m_controlPipe) {
+ pollControlPipe();
+ }
+}
+
+void Agent::pollControlPipe()
+{
+ if (m_controlPipe->isClosed()) {
+ trace("Agent exiting (control pipe is closed)");
+ shutdown();
+ return;
+ }
+
+ while (true) {
+ uint64_t packetSize = 0;
+ const auto amt1 =
+ m_controlPipe->peek(&packetSize, sizeof(packetSize));
+ if (amt1 < sizeof(packetSize)) {
+ break;
+ }
+ ASSERT(packetSize >= sizeof(packetSize) && packetSize <= SIZE_MAX);
+ if (m_controlPipe->bytesAvailable() < packetSize) {
+ if (m_controlPipe->readBufferSize() < packetSize) {
+ m_controlPipe->setReadBufferSize(packetSize);
+ }
+ break;
+ }
+ std::vector<char> packetData;
+ packetData.resize(packetSize);
+ const auto amt2 = m_controlPipe->read(packetData.data(), packetSize);
+ ASSERT(amt2 == packetSize);
+ try {
+ ReadBuffer buffer(std::move(packetData));
+ buffer.getRawValue<uint64_t>(); // Discard the size.
+ handlePacket(buffer);
+ } catch (const ReadBuffer::DecodeError&) {
+ ASSERT(false && "Decode error");
+ }
+ }
+}
+
+void Agent::handlePacket(ReadBuffer &packet)
+{
+ const int type = packet.getInt32();
+ switch (type) {
+ case AgentMsg::StartProcess:
+ handleStartProcessPacket(packet);
+ break;
+ case AgentMsg::SetSize:
+ // TODO: I think it might make sense to collapse consecutive SetSize
+ // messages. i.e. The terminal process can probably generate SetSize
+ // messages faster than they can be processed, and some GUIs might
+ // generate a flood of them, so if we can read multiple SetSize packets
+ // at once, we can ignore the early ones.
+ handleSetSizePacket(packet);
+ break;
+ case AgentMsg::GetConsoleProcessList:
+ handleGetConsoleProcessListPacket(packet);
+ break;
+ default:
+ trace("Unrecognized message, id:%d", type);
+ }
+}
+
+void Agent::writePacket(WriteBuffer &packet)
+{
+ const auto &bytes = packet.buf();
+ packet.replaceRawValue<uint64_t>(0, bytes.size());
+ m_controlPipe->write(bytes.data(), bytes.size());
+}
+
+void Agent::handleStartProcessPacket(ReadBuffer &packet)
+{
+ ASSERT(m_childProcess == nullptr);
+ ASSERT(!m_closingOutputPipes);
+
+ const uint64_t spawnFlags = packet.getInt64();
+ const bool wantProcessHandle = packet.getInt32() != 0;
+ const bool wantThreadHandle = packet.getInt32() != 0;
+ const auto program = packet.getWString();
+ const auto cmdline = packet.getWString();
+ const auto cwd = packet.getWString();
+ const auto env = packet.getWString();
+ const auto desktop = packet.getWString();
+ packet.assertEof();
+
+ auto cmdlineV = vectorWithNulFromString(cmdline);
+ auto desktopV = vectorWithNulFromString(desktop);
+ auto envV = vectorFromString(env);
+
+ LPCWSTR programArg = program.empty() ? nullptr : program.c_str();
+ LPWSTR cmdlineArg = cmdline.empty() ? nullptr : cmdlineV.data();
+ LPCWSTR cwdArg = cwd.empty() ? nullptr : cwd.c_str();
+ LPWSTR envArg = env.empty() ? nullptr : envV.data();
+
+ STARTUPINFOW sui = {};
+ PROCESS_INFORMATION pi = {};
+ sui.cb = sizeof(sui);
+ sui.lpDesktop = desktop.empty() ? nullptr : desktopV.data();
+ BOOL inheritHandles = FALSE;
+ if (m_useConerr) {
+ inheritHandles = TRUE;
+ sui.dwFlags |= STARTF_USESTDHANDLES;
+ sui.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
+ sui.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
+ sui.hStdError = m_errorBuffer->conout();
+ }
+
+ const BOOL success =
+ CreateProcessW(programArg, cmdlineArg, nullptr, nullptr,
+ /*bInheritHandles=*/inheritHandles,
+ /*dwCreationFlags=*/CREATE_UNICODE_ENVIRONMENT,
+ envArg, cwdArg, &sui, &pi);
+ const int lastError = success ? 0 : GetLastError();
+
+ trace("CreateProcess: %s %u",
+ (success ? "success" : "fail"),
+ static_cast<unsigned int>(pi.dwProcessId));
+
+ auto reply = newPacket();
+ if (success) {
+ int64_t replyProcess = 0;
+ int64_t replyThread = 0;
+ if (wantProcessHandle) {
+ replyProcess = int64FromHandle(duplicateHandle(pi.hProcess));
+ }
+ if (wantThreadHandle) {
+ replyThread = int64FromHandle(duplicateHandle(pi.hThread));
+ }
+ CloseHandle(pi.hThread);
+ m_childProcess = pi.hProcess;
+ m_autoShutdown = (spawnFlags & WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN) != 0;
+ m_exitAfterShutdown = (spawnFlags & WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN) != 0;
+ reply.putInt32(static_cast<int32_t>(StartProcessResult::ProcessCreated));
+ reply.putInt64(replyProcess);
+ reply.putInt64(replyThread);
+ } else {
+ reply.putInt32(static_cast<int32_t>(StartProcessResult::CreateProcessFailed));
+ reply.putInt32(lastError);
+ }
+ writePacket(reply);
+}
+
+void Agent::handleSetSizePacket(ReadBuffer &packet)
+{
+ const int cols = packet.getInt32();
+ const int rows = packet.getInt32();
+ packet.assertEof();
+ resizeWindow(cols, rows);
+ auto reply = newPacket();
+ writePacket(reply);
+}
+
+void Agent::handleGetConsoleProcessListPacket(ReadBuffer &packet)
+{
+ packet.assertEof();
+
+ auto processList = std::vector<DWORD>(64);
+ auto processCount = GetConsoleProcessList(&processList[0], processList.size());
+
+ // The process list can change while we're trying to read it
+ while (processList.size() < processCount) {
+ // Multiplying by two caps the number of iterations
+ const auto newSize = std::max<DWORD>(processList.size() * 2, processCount);
+ processList.resize(newSize);
+ processCount = GetConsoleProcessList(&processList[0], processList.size());
+ }
+
+ if (processCount == 0) {
+ trace("GetConsoleProcessList failed");
+ }
+
+ auto reply = newPacket();
+ reply.putInt32(processCount);
+ for (DWORD i = 0; i < processCount; i++) {
+ reply.putInt32(processList[i]);
+ }
+ writePacket(reply);
+}
+
+void Agent::pollConinPipe()
+{
+ const std::string newData = m_coninPipe->readAllToString();
+ if (hasDebugFlag("input_separated_bytes")) {
+ // This debug flag is intended to help with testing incomplete escape
+ // sequences and multibyte UTF-8 encodings. (I wonder if the normal
+ // code path ought to advance a state machine one byte at a time.)
+ for (size_t i = 0; i < newData.size(); ++i) {
+ m_consoleInput->writeInput(newData.substr(i, 1));
+ }
+ } else {
+ m_consoleInput->writeInput(newData);
+ }
+}
+
+void Agent::onPollTimeout()
+{
+ m_consoleInput->updateInputFlags();
+ const bool enableMouseMode = m_consoleInput->shouldActivateTerminalMouse();
+
+ // Give the ConsoleInput object a chance to flush input from an incomplete
+ // escape sequence (e.g. pressing ESC).
+ m_consoleInput->flushIncompleteEscapeCode();
+
+ const bool shouldScrapeContent = !m_closingOutputPipes;
+
+ // Check if the child process has exited.
+ if (m_autoShutdown &&
+ m_childProcess != nullptr &&
+ WaitForSingleObject(m_childProcess, 0) == WAIT_OBJECT_0) {
+ CloseHandle(m_childProcess);
+ m_childProcess = nullptr;
+
+ // Close the data socket to signal to the client that the child
+ // process has exited. If there's any data left to send, send it
+ // before closing the socket.
+ m_closingOutputPipes = true;
+ }
+
+ // Scrape for output *after* the above exit-check to ensure that we collect
+ // the child process's final output.
+ if (shouldScrapeContent) {
+ syncConsoleTitle();
+ scrapeBuffers();
+ }
+
+ // We must ensure that we disable mouse mode before closing the CONOUT
+ // pipe, so update the mouse mode here.
+ m_primaryScraper->terminal().enableMouseMode(
+ enableMouseMode && !m_closingOutputPipes);
+
+ autoClosePipesForShutdown();
+}
+
+void Agent::autoClosePipesForShutdown()
+{
+ if (m_closingOutputPipes) {
+ // We don't want to close a pipe before it's connected! If we do, the
+ // libwinpty client may try to connect to a non-existent pipe. This
+ // case is important for short-lived programs.
+ if (m_conoutPipe->isConnected() &&
+ m_conoutPipe->bytesToSend() == 0) {
+ trace("Closing CONOUT pipe (auto-shutdown)");
+ m_conoutPipe->closePipe();
+ }
+ if (m_conerrPipe != nullptr &&
+ m_conerrPipe->isConnected() &&
+ m_conerrPipe->bytesToSend() == 0) {
+ trace("Closing CONERR pipe (auto-shutdown)");
+ m_conerrPipe->closePipe();
+ }
+ if (m_exitAfterShutdown &&
+ m_conoutPipe->isClosed() &&
+ (m_conerrPipe == nullptr || m_conerrPipe->isClosed())) {
+ trace("Agent exiting (exit-after-shutdown)");
+ shutdown();
+ }
+ }
+}
+
+std::unique_ptr<Win32ConsoleBuffer> Agent::openPrimaryBuffer()
+{
+ // If we're using a separate buffer for stderr, and a program were to
+ // activate the stderr buffer, then we could accidentally scrape the same
+ // buffer twice. That probably shouldn't happen in ordinary use, but it
+ // can be avoided anyway by using the original console screen buffer in
+ // that mode.
+ if (!m_useConerr) {
+ return Win32ConsoleBuffer::openConout();
+ } else {
+ return Win32ConsoleBuffer::openStdout();
+ }
+}
+
+void Agent::resizeWindow(int cols, int rows)
+{
+ ASSERT(cols >= 1 && rows >= 1);
+ cols = std::min(cols, MAX_CONSOLE_WIDTH);
+ rows = std::min(rows, MAX_CONSOLE_HEIGHT);
+
+ Win32Console::FreezeGuard guard(m_console, m_console.frozen());
+ const Coord newSize(cols, rows);
+ ConsoleScreenBufferInfo info;
+ auto primaryBuffer = openPrimaryBuffer();
+ m_primaryScraper->resizeWindow(*primaryBuffer, newSize, info);
+ m_consoleInput->setMouseWindowRect(info.windowRect());
+ if (m_errorScraper) {
+ m_errorScraper->resizeWindow(*m_errorBuffer, newSize, info);
+ }
+
+ // Synthesize a WINDOW_BUFFER_SIZE_EVENT event. Normally, Windows
+ // generates this event only when the buffer size changes, not when the
+ // window size changes. This behavior is undesirable in two ways:
+ // - When winpty expands the window horizontally, it must expand the
+ // buffer first, then the window. At least some programs (e.g. the WSL
+ // bash.exe wrapper) use the window width rather than the buffer width,
+ // so there is a short timespan during which they can read the wrong
+ // value.
+ // - If the window's vertical size is changed, no event is generated,
+ // even though a typical well-behaved console program cares about the
+ // *window* height, not the *buffer* height.
+ // This synthesization works around a design flaw in the console. It's probably
+ // harmless. See https://github.com/rprichard/winpty/issues/110.
+ INPUT_RECORD sizeEvent {};
+ sizeEvent.EventType = WINDOW_BUFFER_SIZE_EVENT;
+ sizeEvent.Event.WindowBufferSizeEvent.dwSize = primaryBuffer->bufferSize();
+ DWORD actual {};
+ WriteConsoleInputW(GetStdHandle(STD_INPUT_HANDLE), &sizeEvent, 1, &actual);
+}
+
+void Agent::scrapeBuffers()
+{
+ Win32Console::FreezeGuard guard(m_console, m_console.frozen());
+ ConsoleScreenBufferInfo info;
+ m_primaryScraper->scrapeBuffer(*openPrimaryBuffer(), info);
+ m_consoleInput->setMouseWindowRect(info.windowRect());
+ if (m_errorScraper) {
+ m_errorScraper->scrapeBuffer(*m_errorBuffer, info);
+ }
+}
+
+void Agent::syncConsoleTitle()
+{
+ std::wstring newTitle = m_console.title();
+ if (newTitle != m_currentTitle) {
+ if (!m_plainMode && !m_conoutPipe->isClosed()) {
+ std::string command = std::string("\x1b]0;") +
+ utf8FromWide(newTitle) + "\x07";
+ m_conoutPipe->write(command.c_str());
+ }
+ m_currentTitle = newTitle;
+ }
+}
diff --git a/src/libs/3rdparty/winpty/src/agent/Agent.h b/src/libs/3rdparty/winpty/src/agent/Agent.h
new file mode 100644
index 00000000000..1dde48fe4ab
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/Agent.h
@@ -0,0 +1,103 @@
+// Copyright (c) 2011-2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef AGENT_H
+#define AGENT_H
+
+#include <windows.h>
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include "DsrSender.h"
+#include "EventLoop.h"
+#include "Win32Console.h"
+
+class ConsoleInput;
+class NamedPipe;
+class ReadBuffer;
+class Scraper;
+class WriteBuffer;
+class Win32ConsoleBuffer;
+
+class Agent : public EventLoop, public DsrSender
+{
+public:
+ Agent(LPCWSTR controlPipeName,
+ uint64_t agentFlags,
+ int mouseMode,
+ int initialCols,
+ int initialRows);
+ virtual ~Agent();
+ void sendDsr() override;
+
+private:
+ NamedPipe &connectToControlPipe(LPCWSTR pipeName);
+ NamedPipe &createDataServerPipe(bool write, const wchar_t *kind);
+
+private:
+ void pollControlPipe();
+ void handlePacket(ReadBuffer &packet);
+ void writePacket(WriteBuffer &packet);
+ void handleStartProcessPacket(ReadBuffer &packet);
+ void handleSetSizePacket(ReadBuffer &packet);
+ void handleGetConsoleProcessListPacket(ReadBuffer &packet);
+ void pollConinPipe();
+
+protected:
+ virtual void onPollTimeout() override;
+ virtual void onPipeIo(NamedPipe &namedPipe) override;
+
+private:
+ void autoClosePipesForShutdown();
+ std::unique_ptr<Win32ConsoleBuffer> openPrimaryBuffer();
+ void resizeWindow(int cols, int rows);
+ void scrapeBuffers();
+ void syncConsoleTitle();
+
+private:
+ const bool m_useConerr;
+ const bool m_plainMode;
+ const int m_mouseMode;
+ Win32Console m_console;
+ std::unique_ptr<Scraper> m_primaryScraper;
+ std::unique_ptr<Scraper> m_errorScraper;
+ std::unique_ptr<Win32ConsoleBuffer> m_errorBuffer;
+ NamedPipe *m_controlPipe = nullptr;
+ NamedPipe *m_coninPipe = nullptr;
+ NamedPipe *m_conoutPipe = nullptr;
+ NamedPipe *m_conerrPipe = nullptr;
+ bool m_autoShutdown = false;
+ bool m_exitAfterShutdown = false;
+ bool m_closingOutputPipes = false;
+ std::unique_ptr<ConsoleInput> m_consoleInput;
+ HANDLE m_childProcess = nullptr;
+
+ // If the title is initialized to the empty string, then cmd.exe will
+ // sometimes print this error:
+ // Not enough storage is available to process this command.
+ // It happens on Windows 7 when logged into a Cygwin SSH session, for
+ // example. Using a title of a single space character avoids the problem.
+ // See https://github.com/rprichard/winpty/issues/74.
+ std::wstring m_currentTitle = L" ";
+};
+
+#endif // AGENT_H
diff --git a/src/libs/3rdparty/winpty/src/agent/AgentCreateDesktop.cc b/src/libs/3rdparty/winpty/src/agent/AgentCreateDesktop.cc
new file mode 100644
index 00000000000..9ad6503b1c5
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/AgentCreateDesktop.cc
@@ -0,0 +1,84 @@
+// Copyright (c) 2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include "AgentCreateDesktop.h"
+
+#include "../shared/BackgroundDesktop.h"
+#include "../shared/Buffer.h"
+#include "../shared/DebugClient.h"
+#include "../shared/StringUtil.h"
+
+#include "EventLoop.h"
+#include "NamedPipe.h"
+
+namespace {
+
+static inline WriteBuffer newPacket() {
+ WriteBuffer packet;
+ packet.putRawValue<uint64_t>(0); // Reserve space for size.
+ return packet;
+}
+
+class CreateDesktopLoop : public EventLoop {
+public:
+ CreateDesktopLoop(LPCWSTR controlPipeName);
+
+protected:
+ virtual void onPipeIo(NamedPipe &namedPipe) override;
+
+private:
+ void writePacket(WriteBuffer &packet);
+
+ BackgroundDesktop m_desktop;
+ NamedPipe &m_pipe;
+};
+
+CreateDesktopLoop::CreateDesktopLoop(LPCWSTR controlPipeName) :
+ m_pipe(createNamedPipe()) {
+ m_pipe.connectToServer(controlPipeName, NamedPipe::OpenMode::Duplex);
+ auto packet = newPacket();
+ packet.putWString(m_desktop.desktopName());
+ writePacket(packet);
+}
+
+void CreateDesktopLoop::writePacket(WriteBuffer &packet) {
+ const auto &bytes = packet.buf();
+ packet.replaceRawValue<uint64_t>(0, bytes.size());
+ m_pipe.write(bytes.data(), bytes.size());
+}
+
+void CreateDesktopLoop::onPipeIo(NamedPipe &namedPipe) {
+ if (m_pipe.isClosed()) {
+ shutdown();
+ }
+}
+
+} // anonymous namespace
+
+void handleCreateDesktop(LPCWSTR controlPipeName) {
+ try {
+ CreateDesktopLoop loop(controlPipeName);
+ loop.run();
+ trace("Agent exiting...");
+ } catch (const WinptyException &e) {
+ trace("handleCreateDesktop: internal error: %s",
+ utf8FromWide(e.what()).c_str());
+ }
+}
diff --git a/src/libs/3rdparty/winpty/src/agent/AgentCreateDesktop.h b/src/libs/3rdparty/winpty/src/agent/AgentCreateDesktop.h
new file mode 100644
index 00000000000..2ae539c7faa
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/AgentCreateDesktop.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef AGENT_CREATE_DESKTOP_H
+#define AGENT_CREATE_DESKTOP_H
+
+#include <windows.h>
+
+void handleCreateDesktop(LPCWSTR controlPipeName);
+
+#endif // AGENT_CREATE_DESKTOP_H
diff --git a/src/libs/3rdparty/winpty/src/agent/ConsoleFont.cc b/src/libs/3rdparty/winpty/src/agent/ConsoleFont.cc
new file mode 100644
index 00000000000..aa1f7876d38
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/ConsoleFont.cc
@@ -0,0 +1,698 @@
+// Copyright (c) 2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include "ConsoleFont.h"
+
+#include <windows.h>
+#include <stdio.h>
+#include <string.h>
+#include <wchar.h>
+
+#include <algorithm>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "../shared/DebugClient.h"
+#include "../shared/OsModule.h"
+#include "../shared/StringUtil.h"
+#include "../shared/WindowsVersion.h"
+#include "../shared/WinptyAssert.h"
+#include "../shared/winpty_snprintf.h"
+
+namespace {
+
+#define COUNT_OF(x) (sizeof(x) / sizeof((x)[0]))
+
+// See https://en.wikipedia.org/wiki/List_of_CJK_fonts
+const wchar_t kLucidaConsole[] = L"Lucida Console";
+const wchar_t kMSGothic[] = { 0xff2d, 0xff33, 0x0020, 0x30b4, 0x30b7, 0x30c3, 0x30af, 0 }; // 932, Japanese
+const wchar_t kNSimSun[] = { 0x65b0, 0x5b8b, 0x4f53, 0 }; // 936, Chinese Simplified
+const wchar_t kGulimChe[] = { 0xad74, 0xb9bc, 0xccb4, 0 }; // 949, Korean
+const wchar_t kMingLight[] = { 0x7d30, 0x660e, 0x9ad4, 0 }; // 950, Chinese Traditional
+
+struct FontSize {
+ short size;
+ int width;
+};
+
+struct Font {
+ const wchar_t *faceName;
+ unsigned int family;
+ short size;
+};
+
+// Ideographs in East Asian languages take two columns rather than one.
+// In the console screen buffer, a "full-width" character will occupy two
+// cells of the buffer, the first with attribute 0x100 and the second with
+// attribute 0x200.
+//
+// Windows does not correctly identify code points as double-width in all
+// configurations. It depends heavily on the code page, the font facename,
+// and (somehow) even the font size. In the 437 code page (MS-DOS), for
+// example, no codepoints are interpreted as double-width. When the console
+// is in an East Asian code page (932, 936, 949, or 950), then sometimes
+// selecting a "Western" facename like "Lucida Console" or "Consolas" doesn't
+// register, or if the font *can* be chosen, then the console doesn't handle
+// double-width correctly. I tested the double-width handling by writing
+// several code points with WriteConsole and checking whether one or two cells
+// were filled.
+//
+// In the Japanese code page (932), Microsoft's default font is MS Gothic.
+// MS Gothic double-width handling seems to be broken with console versions
+// prior to Windows 10 (including Windows 10's legacy mode), and it's
+// especially broken in Windows 8 and 8.1.
+//
+// Test by running: misc/Utf16Echo A2 A3 2014 3044 30FC 4000
+//
+// The first three codepoints are always rendered as half-width with the
+// Windows Japanese fonts. (Of these, the first two must be half-width,
+// but U+2014 could be either.) The last three are rendered as full-width,
+// and they are East_Asian_Width=Wide.
+//
+// Windows 7 fails by modeling all codepoints as full-width with font
+// sizes 22 and above.
+//
+// Windows 8 gets U+00A2, U+00A3, U+2014, U+30FC, and U+4000 wrong, but
+// using a point size not listed in the console properties dialog
+// (e.g. "9") is less wrong:
+//
+// | code point |
+// font | 00A2 00A3 2014 3044 30FC 4000 | cell size
+// ------------+---------------------------------+----------
+// 8 | F F F F H H | 4x8
+// 9 | F F F F F F | 5x9
+// 16 | F F F F H H | 8x16
+// raster 6x13 | H H H F F H(*) | 6x13
+//
+// (*) The Raster Font renders U+4000 as a white box (i.e. an unsupported
+// character).
+//
+
+// See:
+// - misc/Font-Report-June2016 directory for per-size details
+// - misc/font-notes.txt
+// - misc/Utf16Echo.cc, misc/FontSurvey.cc, misc/SetFont.cc, misc/GetFont.cc
+
+const FontSize kLucidaFontSizes[] = {
+ { 5, 3 },
+ { 6, 4 },
+ { 8, 5 },
+ { 10, 6 },
+ { 12, 7 },
+ { 14, 8 },
+ { 16, 10 },
+ { 18, 11 },
+ { 20, 12 },
+ { 36, 22 },
+ { 48, 29 },
+ { 60, 36 },
+ { 72, 43 },
+};
+
+// Japanese. Used on Vista and Windows 7.
+const FontSize k932GothicVista[] = {
+ { 6, 3 },
+ { 8, 4 },
+ { 10, 5 },
+ { 12, 6 },
+ { 13, 7 },
+ { 15, 8 },
+ { 17, 9 },
+ { 19, 10 },
+ { 21, 11 },
+ // All larger fonts are more broken w.r.t. full-size East Asian characters.
+};
+
+// Japanese. Used on Windows 8, 8.1, and the legacy 10 console.
+const FontSize k932GothicWin8[] = {
+ // All of these characters are broken w.r.t. full-size East Asian
+ // characters, but they're equally broken.
+ { 5, 3 },
+ { 7, 4 },
+ { 9, 5 },
+ { 11, 6 },
+ { 13, 7 },
+ { 15, 8 },
+ { 17, 9 },
+ { 20, 10 },
+ { 22, 11 },
+ { 24, 12 },
+ // include extra-large fonts for small terminals
+ { 36, 18 },
+ { 48, 24 },
+ { 60, 30 },
+ { 72, 36 },
+};
+
+// Japanese. Used on the new Windows 10 console.
+const FontSize k932GothicWin10[] = {
+ { 6, 3 },
+ { 8, 4 },
+ { 10, 5 },
+ { 12, 6 },
+ { 14, 7 },
+ { 16, 8 },
+ { 18, 9 },
+ { 20, 10 },
+ { 22, 11 },
+ { 24, 12 },
+ // include extra-large fonts for small terminals
+ { 36, 18 },
+ { 48, 24 },
+ { 60, 30 },
+ { 72, 36 },
+};
+
+// Chinese Simplified.
+const FontSize k936SimSun[] = {
+ { 6, 3 },
+ { 8, 4 },
+ { 10, 5 },
+ { 12, 6 },
+ { 14, 7 },
+ { 16, 8 },
+ { 18, 9 },
+ { 20, 10 },
+ { 22, 11 },
+ { 24, 12 },
+ // include extra-large fonts for small terminals
+ { 36, 18 },
+ { 48, 24 },
+ { 60, 30 },
+ { 72, 36 },
+};
+
+// Korean.
+const FontSize k949GulimChe[] = {
+ { 6, 3 },
+ { 8, 4 },
+ { 10, 5 },
+ { 12, 6 },
+ { 14, 7 },
+ { 16, 8 },
+ { 18, 9 },
+ { 20, 10 },
+ { 22, 11 },
+ { 24, 12 },
+ // include extra-large fonts for small terminals
+ { 36, 18 },
+ { 48, 24 },
+ { 60, 30 },
+ { 72, 36 },
+};
+
+// Chinese Traditional.
+const FontSize k950MingLight[] = {
+ { 6, 3 },
+ { 8, 4 },
+ { 10, 5 },
+ { 12, 6 },
+ { 14, 7 },
+ { 16, 8 },
+ { 18, 9 },
+ { 20, 10 },
+ { 22, 11 },
+ { 24, 12 },
+ // include extra-large fonts for small terminals
+ { 36, 18 },
+ { 48, 24 },
+ { 60, 30 },
+ { 72, 36 },
+};
+
+// Some of these types and functions are missing from the MinGW headers.
+// Others are undocumented.
+
+struct AGENT_CONSOLE_FONT_INFO {
+ DWORD nFont;
+ COORD dwFontSize;
+};
+
+struct AGENT_CONSOLE_FONT_INFOEX {
+ ULONG cbSize;
+ DWORD nFont;
+ COORD dwFontSize;
+ UINT FontFamily;
+ UINT FontWeight;
+ WCHAR FaceName[LF_FACESIZE];
+};
+
+// undocumented XP API
+typedef BOOL WINAPI SetConsoleFont_t(
+ HANDLE hOutput,
+ DWORD dwFontIndex);
+
+// undocumented XP API
+typedef DWORD WINAPI GetNumberOfConsoleFonts_t();
+
+// XP and up
+typedef BOOL WINAPI GetCurrentConsoleFont_t(
+ HANDLE hOutput,
+ BOOL bMaximumWindow,
+ AGENT_CONSOLE_FONT_INFO *lpConsoleCurrentFont);
+
+// XP and up
+typedef COORD WINAPI GetConsoleFontSize_t(
+ HANDLE hConsoleOutput,
+ DWORD nFont);
+
+// Vista and up
+typedef BOOL WINAPI GetCurrentConsoleFontEx_t(
+ HANDLE hConsoleOutput,
+ BOOL bMaximumWindow,
+ AGENT_CONSOLE_FONT_INFOEX *lpConsoleCurrentFontEx);
+
+// Vista and up
+typedef BOOL WINAPI SetCurrentConsoleFontEx_t(
+ HANDLE hConsoleOutput,
+ BOOL bMaximumWindow,
+ AGENT_CONSOLE_FONT_INFOEX *lpConsoleCurrentFontEx);
+
+#define GET_MODULE_PROC(mod, funcName) \
+ m_##funcName = reinterpret_cast<funcName##_t*>((mod).proc(#funcName)); \
+
+#define DEFINE_ACCESSOR(funcName) \
+ funcName##_t &funcName() const { \
+ ASSERT(valid()); \
+ return *m_##funcName; \
+ }
+
+class XPFontAPI {
+public:
+ XPFontAPI() : m_kernel32(L"kernel32.dll") {
+ GET_MODULE_PROC(m_kernel32, GetCurrentConsoleFont);
+ GET_MODULE_PROC(m_kernel32, GetConsoleFontSize);
+ }
+
+ bool valid() const {
+ return m_GetCurrentConsoleFont != NULL &&
+ m_GetConsoleFontSize != NULL;
+ }
+
+ DEFINE_ACCESSOR(GetCurrentConsoleFont)
+ DEFINE_ACCESSOR(GetConsoleFontSize)
+
+private:
+ OsModule m_kernel32;
+ GetCurrentConsoleFont_t *m_GetCurrentConsoleFont;
+ GetConsoleFontSize_t *m_GetConsoleFontSize;
+};
+
+class UndocumentedXPFontAPI : public XPFontAPI {
+public:
+ UndocumentedXPFontAPI() : m_kernel32(L"kernel32.dll") {
+ GET_MODULE_PROC(m_kernel32, SetConsoleFont);
+ GET_MODULE_PROC(m_kernel32, GetNumberOfConsoleFonts);
+ }
+
+ bool valid() const {
+ return this->XPFontAPI::valid() &&
+ m_SetConsoleFont != NULL &&
+ m_GetNumberOfConsoleFonts != NULL;
+ }
+
+ DEFINE_ACCESSOR(SetConsoleFont)
+ DEFINE_ACCESSOR(GetNumberOfConsoleFonts)
+
+private:
+ OsModule m_kernel32;
+ SetConsoleFont_t *m_SetConsoleFont;
+ GetNumberOfConsoleFonts_t *m_GetNumberOfConsoleFonts;
+};
+
+class VistaFontAPI : public XPFontAPI {
+public:
+ VistaFontAPI() : m_kernel32(L"kernel32.dll") {
+ GET_MODULE_PROC(m_kernel32, GetCurrentConsoleFontEx);
+ GET_MODULE_PROC(m_kernel32, SetCurrentConsoleFontEx);
+ }
+
+ bool valid() const {
+ return this->XPFontAPI::valid() &&
+ m_GetCurrentConsoleFontEx != NULL &&
+ m_SetCurrentConsoleFontEx != NULL;
+ }
+
+ DEFINE_ACCESSOR(GetCurrentConsoleFontEx)
+ DEFINE_ACCESSOR(SetCurrentConsoleFontEx)
+
+private:
+ OsModule m_kernel32;
+ GetCurrentConsoleFontEx_t *m_GetCurrentConsoleFontEx;
+ SetCurrentConsoleFontEx_t *m_SetCurrentConsoleFontEx;
+};
+
+static std::vector<std::pair<DWORD, COORD> > readFontTable(
+ XPFontAPI &api, HANDLE conout, DWORD maxCount) {
+ std::vector<std::pair<DWORD, COORD> > ret;
+ for (DWORD i = 0; i < maxCount; ++i) {
+ COORD size = api.GetConsoleFontSize()(conout, i);
+ if (size.X == 0 && size.Y == 0) {
+ break;
+ }
+ ret.push_back(std::make_pair(i, size));
+ }
+ return ret;
+}
+
+static void dumpFontTable(HANDLE conout, const char *prefix) {
+ const int kMaxCount = 1000;
+ if (!isTracingEnabled()) {
+ return;
+ }
+ XPFontAPI api;
+ if (!api.valid()) {
+ trace("dumpFontTable: cannot dump font table -- missing APIs");
+ return;
+ }
+ std::vector<std::pair<DWORD, COORD> > table =
+ readFontTable(api, conout, kMaxCount);
+ std::string line;
+ char tmp[128];
+ size_t first = 0;
+ while (first < table.size()) {
+ size_t last = std::min(table.size() - 1, first + 10 - 1);
+ winpty_snprintf(tmp, "%sfonts %02u-%02u:",
+ prefix, static_cast<unsigned>(first), static_cast<unsigned>(last));
+ line = tmp;
+ for (size_t i = first; i <= last; ++i) {
+ if (i % 10 == 5) {
+ line += " - ";
+ }
+ winpty_snprintf(tmp, " %2dx%-2d",
+ table[i].second.X, table[i].second.Y);
+ line += tmp;
+ }
+ trace("%s", line.c_str());
+ first = last + 1;
+ }
+ if (table.size() == kMaxCount) {
+ trace("%sfonts: ... stopped reading at %d fonts ...",
+ prefix, kMaxCount);
+ }
+}
+
+static std::string stringToCodePoints(const std::wstring &str) {
+ std::string ret = "(";
+ for (size_t i = 0; i < str.size(); ++i) {
+ char tmp[32];
+ winpty_snprintf(tmp, "%X", str[i]);
+ if (ret.size() > 1) {
+ ret.push_back(' ');
+ }
+ ret += tmp;
+ }
+ ret.push_back(')');
+ return ret;
+}
+
+static void dumpFontInfoEx(
+ const AGENT_CONSOLE_FONT_INFOEX &infoex,
+ const char *prefix) {
+ if (!isTracingEnabled()) {
+ return;
+ }
+ std::wstring faceName(infoex.FaceName,
+ winpty_wcsnlen(infoex.FaceName, COUNT_OF(infoex.FaceName)));
+ trace("%snFont=%u dwFontSize=(%d,%d) "
+ "FontFamily=0x%x FontWeight=%u FaceName=%s %s",
+ prefix,
+ static_cast<unsigned>(infoex.nFont),
+ infoex.dwFontSize.X, infoex.dwFontSize.Y,
+ infoex.FontFamily, infoex.FontWeight, utf8FromWide(faceName).c_str(),
+ stringToCodePoints(faceName).c_str());
+}
+
+static void dumpVistaFont(VistaFontAPI &api, HANDLE conout, const char *prefix) {
+ if (!isTracingEnabled()) {
+ return;
+ }
+ AGENT_CONSOLE_FONT_INFOEX infoex = {0};
+ infoex.cbSize = sizeof(infoex);
+ if (!api.GetCurrentConsoleFontEx()(conout, FALSE, &infoex)) {
+ trace("GetCurrentConsoleFontEx call failed");
+ return;
+ }
+ dumpFontInfoEx(infoex, prefix);
+}
+
+static void dumpXPFont(XPFontAPI &api, HANDLE conout, const char *prefix) {
+ if (!isTracingEnabled()) {
+ return;
+ }
+ AGENT_CONSOLE_FONT_INFO info = {0};
+ if (!api.GetCurrentConsoleFont()(conout, FALSE, &info)) {
+ trace("GetCurrentConsoleFont call failed");
+ return;
+ }
+ trace("%snFont=%u dwFontSize=(%d,%d)",
+ prefix,
+ static_cast<unsigned>(info.nFont),
+ info.dwFontSize.X, info.dwFontSize.Y);
+}
+
+static bool setFontVista(
+ VistaFontAPI &api,
+ HANDLE conout,
+ const Font &font) {
+ AGENT_CONSOLE_FONT_INFOEX infoex = {};
+ infoex.cbSize = sizeof(AGENT_CONSOLE_FONT_INFOEX);
+ infoex.dwFontSize.Y = font.size;
+ infoex.FontFamily = font.family;
+ infoex.FontWeight = 400;
+ winpty_wcsncpy_nul(infoex.FaceName, font.faceName);
+ dumpFontInfoEx(infoex, "setFontVista: setting font to: ");
+ if (!api.SetCurrentConsoleFontEx()(conout, FALSE, &infoex)) {
+ trace("setFontVista: SetCurrentConsoleFontEx call failed");
+ return false;
+ }
+ memset(&infoex, 0, sizeof(infoex));
+ infoex.cbSize = sizeof(infoex);
+ if (!api.GetCurrentConsoleFontEx()(conout, FALSE, &infoex)) {
+ trace("setFontVista: GetCurrentConsoleFontEx call failed");
+ return false;
+ }
+ if (wcsncmp(infoex.FaceName, font.faceName,
+ COUNT_OF(infoex.FaceName)) != 0) {
+ trace("setFontVista: face name was not set");
+ dumpFontInfoEx(infoex, "setFontVista: post-call font: ");
+ return false;
+ }
+ // We'd like to verify that the new font size is correct, but we can't
+ // predict what it will be, even though we just set it to `pxSize` through
+ // an apprently symmetric interface. For the Chinese and Korean fonts, the
+ // new `infoex.dwFontSize.Y` value can be slightly larger than the height
+ // we specified.
+ return true;
+}
+
+static Font selectSmallFont(int codePage, int columns, bool isNewW10) {
+ // Iterate over a set of font sizes according to the code page, and select
+ // one.
+
+ const wchar_t *faceName = nullptr;
+ unsigned int fontFamily = 0;
+ const FontSize *table = nullptr;
+ size_t tableSize = 0;
+
+ switch (codePage) {
+ case 932: // Japanese
+ faceName = kMSGothic;
+ fontFamily = 0x36;
+ if (isNewW10) {
+ table = k932GothicWin10;
+ tableSize = COUNT_OF(k932GothicWin10);
+ } else if (isAtLeastWindows8()) {
+ table = k932GothicWin8;
+ tableSize = COUNT_OF(k932GothicWin8);
+ } else {
+ table = k932GothicVista;
+ tableSize = COUNT_OF(k932GothicVista);
+ }
+ break;
+ case 936: // Chinese Simplified
+ faceName = kNSimSun;
+ fontFamily = 0x36;
+ table = k936SimSun;
+ tableSize = COUNT_OF(k936SimSun);
+ break;
+ case 949: // Korean
+ faceName = kGulimChe;
+ fontFamily = 0x36;
+ table = k949GulimChe;
+ tableSize = COUNT_OF(k949GulimChe);
+ break;
+ case 950: // Chinese Traditional
+ faceName = kMingLight;
+ fontFamily = 0x36;
+ table = k950MingLight;
+ tableSize = COUNT_OF(k950MingLight);
+ break;
+ default:
+ faceName = kLucidaConsole;
+ fontFamily = 0x36;
+ table = kLucidaFontSizes;
+ tableSize = COUNT_OF(kLucidaFontSizes);
+ break;
+ }
+
+ size_t bestIndex = static_cast<size_t>(-1);
+ std::tuple<int, int> bestScore = std::make_tuple(-1, -1);
+
+ // We might want to pick the smallest possible font, because we don't know
+ // how large the monitor is (and the monitor size can change). We might
+ // want to pick a larger font to accommodate console programs that resize
+ // the console on their own, like DOS edit.com, which tends to resize the
+ // console to 80 columns.
+
+ for (size_t i = 0; i < tableSize; ++i) {
+ const int width = table[i].width * columns;
+
+ // In general, we'd like to pick a font size where cutting the number
+ // of columns in half doesn't immediately violate the minimum width
+ // constraint. (e.g. To run DOS edit.com, a user might resize their
+ // terminal to ~100 columns so it's big enough to show the 80 columns
+ // post-resize.) To achieve this, give priority to fonts that allow
+ // this halving. We don't want to encourage *very* large fonts,
+ // though, so disable the effect as the number of columns scales from
+ // 80 to 40.
+ const int halfColumns = std::min(columns, std::max(40, columns / 2));
+ const int halfWidth = table[i].width * halfColumns;
+
+ std::tuple<int, int> thisScore = std::make_tuple(-1, -1);
+ if (width >= 160 && halfWidth >= 160) {
+ // Both sizes are good. Prefer the smaller fonts.
+ thisScore = std::make_tuple(2, -width);
+ } else if (width >= 160) {
+ // Prefer the smaller fonts.
+ thisScore = std::make_tuple(1, -width);
+ } else {
+ // Otherwise, prefer the largest font in our table.
+ thisScore = std::make_tuple(0, width);
+ }
+ if (thisScore > bestScore) {
+ bestIndex = i;
+ bestScore = thisScore;
+ }
+ }
+
+ ASSERT(bestIndex != static_cast<size_t>(-1));
+ return Font { faceName, fontFamily, table[bestIndex].size };
+}
+
+static void setSmallFontVista(VistaFontAPI &api, HANDLE conout,
+ int columns, bool isNewW10) {
+ int codePage = GetConsoleOutputCP();
+ const auto font = selectSmallFont(codePage, columns, isNewW10);
+ if (setFontVista(api, conout, font)) {
+ trace("setSmallFontVista: success");
+ return;
+ }
+ if (codePage == 932 || codePage == 936 ||
+ codePage == 949 || codePage == 950) {
+ trace("setSmallFontVista: falling back to default codepage font instead");
+ const auto fontFB = selectSmallFont(0, columns, isNewW10);
+ if (setFontVista(api, conout, fontFB)) {
+ trace("setSmallFontVista: fallback was successful");
+ return;
+ }
+ }
+ trace("setSmallFontVista: failure");
+}
+
+struct FontSizeComparator {
+ bool operator()(const std::pair<DWORD, COORD> &obj1,
+ const std::pair<DWORD, COORD> &obj2) const {
+ int score1 = obj1.second.X + obj1.second.Y;
+ int score2 = obj2.second.X + obj2.second.Y;
+ return score1 < score2;
+ }
+};
+
+static void setSmallFontXP(UndocumentedXPFontAPI &api, HANDLE conout) {
+ // Read the console font table and sort it from smallest to largest.
+ const DWORD fontCount = api.GetNumberOfConsoleFonts()();
+ trace("setSmallFontXP: number of console fonts: %u",
+ static_cast<unsigned>(fontCount));
+ std::vector<std::pair<DWORD, COORD> > table =
+ readFontTable(api, conout, fontCount);
+ std::sort(table.begin(), table.end(), FontSizeComparator());
+ for (size_t i = 0; i < table.size(); ++i) {
+ // Skip especially narrow fonts to permit narrower terminals.
+ if (table[i].second.X < 4) {
+ continue;
+ }
+ trace("setSmallFontXP: setting font to %u",
+ static_cast<unsigned>(table[i].first));
+ if (!api.SetConsoleFont()(conout, table[i].first)) {
+ trace("setSmallFontXP: SetConsoleFont call failed");
+ continue;
+ }
+ AGENT_CONSOLE_FONT_INFO info;
+ if (!api.GetCurrentConsoleFont()(conout, FALSE, &info)) {
+ trace("setSmallFontXP: GetCurrentConsoleFont call failed");
+ return;
+ }
+ if (info.nFont != table[i].first) {
+ trace("setSmallFontXP: font was not set");
+ dumpXPFont(api, conout, "setSmallFontXP: post-call font: ");
+ continue;
+ }
+ trace("setSmallFontXP: success");
+ return;
+ }
+ trace("setSmallFontXP: failure");
+}
+
+} // anonymous namespace
+
+// A Windows console window can never be larger than the desktop window. To
+// maximize the possible size of the console in rows*cols, try to configure
+// the console with a small font. Unfortunately, we cannot make the font *too*
+// small, because there is also a minimum window size in pixels.
+void setSmallFont(HANDLE conout, int columns, bool isNewW10) {
+ trace("setSmallFont: attempting to set a small font for %d columns "
+ "(CP=%u OutputCP=%u)",
+ columns,
+ static_cast<unsigned>(GetConsoleCP()),
+ static_cast<unsigned>(GetConsoleOutputCP()));
+ VistaFontAPI vista;
+ if (vista.valid()) {
+ dumpVistaFont(vista, conout, "previous font: ");
+ dumpFontTable(conout, "previous font table: ");
+ setSmallFontVista(vista, conout, columns, isNewW10);
+ dumpVistaFont(vista, conout, "new font: ");
+ dumpFontTable(conout, "new font table: ");
+ return;
+ }
+ UndocumentedXPFontAPI xp;
+ if (xp.valid()) {
+ dumpXPFont(xp, conout, "previous font: ");
+ dumpFontTable(conout, "previous font table: ");
+ setSmallFontXP(xp, conout);
+ dumpXPFont(xp, conout, "new font: ");
+ dumpFontTable(conout, "new font table: ");
+ return;
+ }
+ trace("setSmallFont: neither Vista nor XP APIs detected -- giving up");
+ dumpFontTable(conout, "font table: ");
+}
diff --git a/src/libs/3rdparty/winpty/src/agent/ConsoleFont.h b/src/libs/3rdparty/winpty/src/agent/ConsoleFont.h
new file mode 100644
index 00000000000..99cb10698d9
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/ConsoleFont.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef CONSOLEFONT_H
+#define CONSOLEFONT_H
+
+#include <windows.h>
+
+void setSmallFont(HANDLE conout, int columns, bool isNewW10);
+
+#endif // CONSOLEFONT_H
diff --git a/src/libs/3rdparty/winpty/src/agent/ConsoleInput.cc b/src/libs/3rdparty/winpty/src/agent/ConsoleInput.cc
new file mode 100644
index 00000000000..192cac2a298
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/ConsoleInput.cc
@@ -0,0 +1,852 @@
+// Copyright (c) 2011-2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include "ConsoleInput.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include <algorithm>
+#include <string>
+
+#include "../include/winpty_constants.h"
+
+#include "../shared/DebugClient.h"
+#include "../shared/StringBuilder.h"
+#include "../shared/UnixCtrlChars.h"
+
+#include "ConsoleInputReencoding.h"
+#include "DebugShowInput.h"
+#include "DefaultInputMap.h"
+#include "DsrSender.h"
+#include "UnicodeEncoding.h"
+#include "Win32Console.h"
+
+// MAPVK_VK_TO_VSC isn't defined by the old MinGW.
+#ifndef MAPVK_VK_TO_VSC
+#define MAPVK_VK_TO_VSC 0
+#endif
+
+namespace {
+
+struct MouseRecord {
+ bool release;
+ int flags;
+ COORD coord;
+
+ std::string toString() const;
+};
+
+std::string MouseRecord::toString() const {
+ StringBuilder sb(40);
+ sb << "pos=" << coord.X << ',' << coord.Y
+ << " flags=0x" << hexOfInt(flags);
+ if (release) {
+ sb << " release";
+ }
+ return sb.str_moved();
+}
+
+const unsigned int kIncompleteEscapeTimeoutMs = 1000u;
+
+#define CHECK(cond) \
+ do { \
+ if (!(cond)) { return 0; } \
+ } while(0)
+
+#define ADVANCE() \
+ do { \
+ pch++; \
+ if (pch == stop) { return -1; } \
+ } while(0)
+
+#define SCAN_INT(out, maxLen) \
+ do { \
+ (out) = 0; \
+ CHECK(isdigit(*pch)); \
+ const char *begin = pch; \
+ do { \
+ CHECK(pch - begin + 1 < maxLen); \
+ (out) = (out) * 10 + *pch - '0'; \
+ ADVANCE(); \
+ } while (isdigit(*pch)); \
+ } while(0)
+
+#define SCAN_SIGNED_INT(out, maxLen) \
+ do { \
+ bool negative = false; \
+ if (*pch == '-') { \
+ negative = true; \
+ ADVANCE(); \
+ } \
+ SCAN_INT(out, maxLen); \
+ if (negative) { \
+ (out) = -(out); \
+ } \
+ } while(0)
+
+// Match the Device Status Report console input: ESC [ nn ; mm R
+// Returns:
+// 0 no match
+// >0 match, returns length of match
+// -1 incomplete match
+static int matchDsr(const char *input, int inputSize)
+{
+ int32_t dummy = 0;
+ const char *pch = input;
+ const char *stop = input + inputSize;
+ CHECK(*pch == '\x1B'); ADVANCE();
+ CHECK(*pch == '['); ADVANCE();
+ SCAN_INT(dummy, 8);
+ CHECK(*pch == ';'); ADVANCE();
+ SCAN_INT(dummy, 8);
+ CHECK(*pch == 'R');
+ return pch - input + 1;
+}
+
+static int matchMouseDefault(const char *input, int inputSize,
+ MouseRecord &out)
+{
+ const char *pch = input;
+ const char *stop = input + inputSize;
+ CHECK(*pch == '\x1B'); ADVANCE();
+ CHECK(*pch == '['); ADVANCE();
+ CHECK(*pch == 'M'); ADVANCE();
+ out.flags = (*pch - 32) & 0xFF; ADVANCE();
+ out.coord.X = (*pch - '!') & 0xFF;
+ ADVANCE();
+ out.coord.Y = (*pch - '!') & 0xFF;
+ out.release = false;
+ return pch - input + 1;
+}
+
+static int matchMouse1006(const char *input, int inputSize, MouseRecord &out)
+{
+ const char *pch = input;
+ const char *stop = input + inputSize;
+ int32_t temp;
+ CHECK(*pch == '\x1B'); ADVANCE();
+ CHECK(*pch == '['); ADVANCE();
+ CHECK(*pch == '<'); ADVANCE();
+ SCAN_INT(out.flags, 8);
+ CHECK(*pch == ';'); ADVANCE();
+ SCAN_SIGNED_INT(temp, 8); out.coord.X = temp - 1;
+ CHECK(*pch == ';'); ADVANCE();
+ SCAN_SIGNED_INT(temp, 8); out.coord.Y = temp - 1;
+ CHECK(*pch == 'M' || *pch == 'm');
+ out.release = (*pch == 'm');
+ return pch - input + 1;
+}
+
+static int matchMouse1015(const char *input, int inputSize, MouseRecord &out)
+{
+ const char *pch = input;
+ const char *stop = input + inputSize;
+ int32_t temp;
+ CHECK(*pch == '\x1B'); ADVANCE();
+ CHECK(*pch == '['); ADVANCE();
+ SCAN_INT(out.flags, 8); out.flags -= 32;
+ CHECK(*pch == ';'); ADVANCE();
+ SCAN_SIGNED_INT(temp, 8); out.coord.X = temp - 1;
+ CHECK(*pch == ';'); ADVANCE();
+ SCAN_SIGNED_INT(temp, 8); out.coord.Y = temp - 1;
+ CHECK(*pch == 'M');
+ out.release = false;
+ return pch - input + 1;
+}
+
+// Match a mouse input escape sequence of any kind.
+// 0 no match
+// >0 match, returns length of match
+// -1 incomplete match
+static int matchMouseRecord(const char *input, int inputSize, MouseRecord &out)
+{
+ memset(&out, 0, sizeof(out));
+ int ret;
+ if ((ret = matchMouse1006(input, inputSize, out)) != 0) { return ret; }
+ if ((ret = matchMouse1015(input, inputSize, out)) != 0) { return ret; }
+ if ((ret = matchMouseDefault(input, inputSize, out)) != 0) { return ret; }
+ return 0;
+}
+
+#undef CHECK
+#undef ADVANCE
+#undef SCAN_INT
+
+} // anonymous namespace
+
+ConsoleInput::ConsoleInput(HANDLE conin, int mouseMode, DsrSender &dsrSender,
+ Win32Console &console) :
+ m_console(console),
+ m_conin(conin),
+ m_mouseMode(mouseMode),
+ m_dsrSender(dsrSender)
+{
+ addDefaultEntriesToInputMap(m_inputMap);
+ if (hasDebugFlag("dump_input_map")) {
+ m_inputMap.dumpInputMap();
+ }
+
+ // Configure Quick Edit mode according to the mouse mode. Enable
+ // InsertMode for two reasons:
+ // - If it's OFF, it's difficult for the user to turn it ON. The
+ // properties dialog is inaccesible. winpty still faithfully handles
+ // the Insert key, which toggles between the insertion and overwrite
+ // modes.
+ // - When we modify the QuickEdit setting, if ExtendedFlags is OFF,
+ // then we must choose the InsertMode setting. I don't *think* this
+ // case happens, though, because a new console always has ExtendedFlags
+ // ON.
+ // See misc/EnableExtendedFlags.txt.
+ DWORD mode = 0;
+ if (!GetConsoleMode(conin, &mode)) {
+ trace("Agent startup: GetConsoleMode failed");
+ } else {
+ mode |= ENABLE_EXTENDED_FLAGS;
+ mode |= ENABLE_INSERT_MODE;
+ if (m_mouseMode == WINPTY_MOUSE_MODE_AUTO) {
+ mode |= ENABLE_QUICK_EDIT_MODE;
+ } else {
+ mode &= ~ENABLE_QUICK_EDIT_MODE;
+ }
+ if (!SetConsoleMode(conin, mode)) {
+ trace("Agent startup: SetConsoleMode failed");
+ }
+ }
+
+ updateInputFlags(true);
+}
+
+void ConsoleInput::writeInput(const std::string &input)
+{
+ if (input.size() == 0) {
+ return;
+ }
+
+ if (isTracingEnabled()) {
+ static bool debugInput = hasDebugFlag("input");
+ if (debugInput) {
+ std::string dumpString;
+ for (size_t i = 0; i < input.size(); ++i) {
+ const char ch = input[i];
+ const char ctrl = decodeUnixCtrlChar(ch);
+ if (ctrl != '\0') {
+ dumpString += '^';
+ dumpString += ctrl;
+ } else {
+ dumpString += ch;
+ }
+ }
+ dumpString += " (";
+ for (size_t i = 0; i < input.size(); ++i) {
+ if (i > 0) {
+ dumpString += ' ';
+ }
+ const unsigned char uch = input[i];
+ char buf[32];
+ winpty_snprintf(buf, "%02X", uch);
+ dumpString += buf;
+ }
+ dumpString += ')';
+ trace("input chars: %s", dumpString.c_str());
+ }
+ }
+
+ m_byteQueue.append(input);
+ doWrite(false);
+ if (!m_byteQueue.empty() && !m_dsrSent) {
+ trace("send DSR");
+ m_dsrSender.sendDsr();
+ m_dsrSent = true;
+ }
+ m_lastWriteTick = GetTickCount();
+}
+
+void ConsoleInput::flushIncompleteEscapeCode()
+{
+ if (!m_byteQueue.empty() &&
+ (GetTickCount() - m_lastWriteTick) > kIncompleteEscapeTimeoutMs) {
+ doWrite(true);
+ m_byteQueue.clear();
+ }
+}
+
+void ConsoleInput::updateInputFlags(bool forceTrace)
+{
+ const DWORD mode = inputConsoleMode();
+ const bool newFlagEE = (mode & ENABLE_EXTENDED_FLAGS) != 0;
+ const bool newFlagMI = (mode & ENABLE_MOUSE_INPUT) != 0;
+ const bool newFlagQE = (mode & ENABLE_QUICK_EDIT_MODE) != 0;
+ const bool newFlagEI = (mode & 0x200) != 0;
+ if (forceTrace ||
+ newFlagEE != m_enableExtendedEnabled ||
+ newFlagMI != m_mouseInputEnabled ||
+ newFlagQE != m_quickEditEnabled ||
+ newFlagEI != m_escapeInputEnabled) {
+ trace("CONIN modes: Extended=%s, MouseInput=%s QuickEdit=%s EscapeInput=%s",
+ newFlagEE ? "on" : "off",
+ newFlagMI ? "on" : "off",
+ newFlagQE ? "on" : "off",
+ newFlagEI ? "on" : "off");
+ }
+ m_enableExtendedEnabled = newFlagEE;
+ m_mouseInputEnabled = newFlagMI;
+ m_quickEditEnabled = newFlagQE;
+ m_escapeInputEnabled = newFlagEI;
+}
+
+bool ConsoleInput::shouldActivateTerminalMouse()
+{
+ // Return whether the agent should activate the terminal's mouse mode.
+ if (m_mouseMode == WINPTY_MOUSE_MODE_AUTO) {
+ // Some programs (e.g. Cygwin command-line programs like bash.exe and
+ // python2.7.exe) turn off ENABLE_EXTENDED_FLAGS and turn on
+ // ENABLE_MOUSE_INPUT, but do not turn off QuickEdit mode and do not
+ // actually care about mouse input. Only enable the terminal mouse
+ // mode if ENABLE_EXTENDED_FLAGS is on. See
+ // misc/EnableExtendedFlags.txt.
+ return m_mouseInputEnabled && !m_quickEditEnabled &&
+ m_enableExtendedEnabled;
+ } else if (m_mouseMode == WINPTY_MOUSE_MODE_FORCE) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void ConsoleInput::doWrite(bool isEof)
+{
+ const char *data = m_byteQueue.c_str();
+ std::vector<INPUT_RECORD> records;
+ size_t idx = 0;
+ while (idx < m_byteQueue.size()) {
+ int charSize = scanInput(records, &data[idx], m_byteQueue.size() - idx, isEof);
+ if (charSize == -1)
+ break;
+ idx += charSize;
+ }
+ m_byteQueue.erase(0, idx);
+ flushInputRecords(records);
+}
+
+void ConsoleInput::flushInputRecords(std::vector<INPUT_RECORD> &records)
+{
+ if (records.size() == 0) {
+ return;
+ }
+ DWORD actual = 0;
+ if (!WriteConsoleInputW(m_conin, records.data(), records.size(), &actual)) {
+ trace("WriteConsoleInputW failed");
+ }
+ records.clear();
+}
+
+// This behavior isn't strictly correct, because the keypresses (probably?)
+// adopt the keyboard state (e.g. Ctrl/Alt/Shift modifiers) of the current
+// window station's keyboard, which has no necessary relationship to the winpty
+// instance. It's unlikely to be an issue in practice, but it's conceivable.
+// (Imagine a foreground SSH server, where the local user holds down Ctrl,
+// while the remote user tries to use WSL navigation keys.) I suspect using
+// the BackgroundDesktop mechanism in winpty would fix the problem.
+//
+// https://github.com/rprichard/winpty/issues/116
+static void sendKeyMessage(HWND hwnd, bool isKeyDown, uint16_t virtualKey)
+{
+ uint32_t scanCode = MapVirtualKey(virtualKey, MAPVK_VK_TO_VSC);
+ if (scanCode > 255) {
+ scanCode = 0;
+ }
+ SendMessage(hwnd, isKeyDown ? WM_KEYDOWN : WM_KEYUP, virtualKey,
+ (scanCode << 16) | 1u | (isKeyDown ? 0u : 0xc0000000u));
+}
+
+int ConsoleInput::scanInput(std::vector<INPUT_RECORD> &records,
+ const char *input,
+ int inputSize,
+ bool isEof)
+{
+ ASSERT(inputSize >= 1);
+
+ // Ctrl-C.
+ //
+ // In processed mode, use GenerateConsoleCtrlEvent so that Ctrl-C handlers
+ // are called. GenerateConsoleCtrlEvent unfortunately doesn't interrupt
+ // ReadConsole calls[1]. Using WM_KEYDOWN/UP fixes the ReadConsole
+ // problem, but breaks in background window stations/desktops.
+ //
+ // In unprocessed mode, there's an entry for Ctrl-C in the SimpleEncoding
+ // table in DefaultInputMap.
+ //
+ // [1] https://github.com/rprichard/winpty/issues/116
+ if (input[0] == '\x03' && (inputConsoleMode() & ENABLE_PROCESSED_INPUT)) {
+ flushInputRecords(records);
+ trace("Ctrl-C");
+ const BOOL ret = GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0);
+ trace("GenerateConsoleCtrlEvent: %d", ret);
+ return 1;
+ }
+
+ if (input[0] == '\x1B') {
+ // Attempt to match the Device Status Report (DSR) reply.
+ int dsrLen = matchDsr(input, inputSize);
+ if (dsrLen > 0) {
+ trace("Received a DSR reply");
+ m_dsrSent = false;
+ return dsrLen;
+ } else if (!isEof && dsrLen == -1) {
+ // Incomplete DSR match.
+ trace("Incomplete DSR match");
+ return -1;
+ }
+
+ int mouseLen = scanMouseInput(records, input, inputSize);
+ if (mouseLen > 0 || (!isEof && mouseLen == -1)) {
+ return mouseLen;
+ }
+ }
+
+ // Search the input map.
+ InputMap::Key match;
+ bool incomplete;
+ int matchLen = m_inputMap.lookupKey(input, inputSize, match, incomplete);
+ if (!isEof && incomplete) {
+ // Incomplete match -- need more characters (or wait for a
+ // timeout to signify flushed input).
+ trace("Incomplete escape sequence");
+ return -1;
+ } else if (matchLen > 0) {
+ uint32_t winCodePointDn = match.unicodeChar;
+ if ((match.keyState & LEFT_CTRL_PRESSED) && (match.keyState & LEFT_ALT_PRESSED)) {
+ winCodePointDn = '\0';
+ }
+ uint32_t winCodePointUp = winCodePointDn;
+ if (match.keyState & LEFT_ALT_PRESSED) {
+ winCodePointUp = '\0';
+ }
+ appendKeyPress(records, match.virtualKey,
+ winCodePointDn, winCodePointUp, match.keyState,
+ match.unicodeChar, match.keyState);
+ return matchLen;
+ }
+
+ // Recognize Alt-<character>.
+ //
+ // This code doesn't match Alt-ESC, which is encoded as `ESC ESC`, but
+ // maybe it should. I was concerned that pressing ESC rapidly enough could
+ // accidentally trigger Alt-ESC. (e.g. The user would have to be faster
+ // than the DSR flushing mechanism or use a decrepit terminal. The user
+ // might be on a slow network connection.)
+ if (input[0] == '\x1B' && inputSize >= 2 && input[1] != '\x1B') {
+ const int len = utf8CharLength(input[1]);
+ if (len > 0) {
+ if (1 + len > inputSize) {
+ // Incomplete character.
+ trace("Incomplete UTF-8 character in Alt-<Char>");
+ return -1;
+ }
+ appendUtf8Char(records, &input[1], len, true);
+ return 1 + len;
+ }
+ }
+
+ // A UTF-8 character.
+ const int len = utf8CharLength(input[0]);
+ if (len == 0) {
+ static bool debugInput = isTracingEnabled() && hasDebugFlag("input");
+ if (debugInput) {
+ trace("Discarding invalid input byte: %02X",
+ static_cast<unsigned char>(input[0]));
+ }
+ return 1;
+ }
+ if (len > inputSize) {
+ // Incomplete character.
+ trace("Incomplete UTF-8 character");
+ return -1;
+ }
+ appendUtf8Char(records, &input[0], len, false);
+ return len;
+}
+
+int ConsoleInput::scanMouseInput(std::vector<INPUT_RECORD> &records,
+ const char *input,
+ int inputSize)
+{
+ MouseRecord record;
+ const int len = matchMouseRecord(input, inputSize, record);
+ if (len <= 0) {
+ return len;
+ }
+
+ if (isTracingEnabled()) {
+ static bool debugInput = hasDebugFlag("input");
+ if (debugInput) {
+ trace("mouse input: %s", record.toString().c_str());
+ }
+ }
+
+ const int button = record.flags & 0x03;
+ INPUT_RECORD newRecord = {0};
+ newRecord.EventType = MOUSE_EVENT;
+ MOUSE_EVENT_RECORD &mer = newRecord.Event.MouseEvent;
+
+ mer.dwMousePosition.X =
+ m_mouseWindowRect.Left +
+ std::max(0, std::min<int>(record.coord.X,
+ m_mouseWindowRect.width() - 1));
+
+ mer.dwMousePosition.Y =
+ m_mouseWindowRect.Top +
+ std::max(0, std::min<int>(record.coord.Y,
+ m_mouseWindowRect.height() - 1));
+
+ // The modifier state is neatly independent of everything else.
+ if (record.flags & 0x04) { mer.dwControlKeyState |= SHIFT_PRESSED; }
+ if (record.flags & 0x08) { mer.dwControlKeyState |= LEFT_ALT_PRESSED; }
+ if (record.flags & 0x10) { mer.dwControlKeyState |= LEFT_CTRL_PRESSED; }
+
+ if (record.flags & 0x40) {
+ // Mouse wheel
+ mer.dwEventFlags |= MOUSE_WHEELED;
+ if (button == 0) {
+ // up
+ mer.dwButtonState |= 0x00780000;
+ } else if (button == 1) {
+ // down
+ mer.dwButtonState |= 0xff880000;
+ } else {
+ // Invalid -- do nothing
+ return len;
+ }
+ } else {
+ // Ordinary mouse event
+ if (record.flags & 0x20) { mer.dwEventFlags |= MOUSE_MOVED; }
+ if (button == 3) {
+ m_mouseButtonState = 0;
+ // Potentially advance double-click detection.
+ m_doubleClick.released = true;
+ } else {
+ const DWORD relevantFlag =
+ (button == 0) ? FROM_LEFT_1ST_BUTTON_PRESSED :
+ (button == 1) ? FROM_LEFT_2ND_BUTTON_PRESSED :
+ (button == 2) ? RIGHTMOST_BUTTON_PRESSED :
+ 0;
+ ASSERT(relevantFlag != 0);
+ if (record.release) {
+ m_mouseButtonState &= ~relevantFlag;
+ if (relevantFlag == m_doubleClick.button) {
+ // Potentially advance double-click detection.
+ m_doubleClick.released = true;
+ } else {
+ // End double-click detection.
+ m_doubleClick = DoubleClickDetection();
+ }
+ } else if ((m_mouseButtonState & relevantFlag) == 0) {
+ // The button has been newly pressed.
+ m_mouseButtonState |= relevantFlag;
+ // Detect a double-click. This code looks for an exact
+ // coordinate match, which is stricter than what Windows does,
+ // but Windows has pixel coordinates, and we only have terminal
+ // coordinates.
+ if (m_doubleClick.button == relevantFlag &&
+ m_doubleClick.pos == record.coord &&
+ (GetTickCount() - m_doubleClick.tick <
+ GetDoubleClickTime())) {
+ // Record a double-click and end double-click detection.
+ mer.dwEventFlags |= DOUBLE_CLICK;
+ m_doubleClick = DoubleClickDetection();
+ } else {
+ // Begin double-click detection.
+ m_doubleClick.button = relevantFlag;
+ m_doubleClick.pos = record.coord;
+ m_doubleClick.tick = GetTickCount();
+ }
+ }
+ }
+ }
+
+ mer.dwButtonState |= m_mouseButtonState;
+
+ if (m_mouseInputEnabled && !m_quickEditEnabled) {
+ if (isTracingEnabled()) {
+ static bool debugInput = hasDebugFlag("input");
+ if (debugInput) {
+ trace("mouse event: %s", mouseEventToString(mer).c_str());
+ }
+ }
+
+ records.push_back(newRecord);
+ }
+
+ return len;
+}
+
+void ConsoleInput::appendUtf8Char(std::vector<INPUT_RECORD> &records,
+ const char *charBuffer,
+ const int charLen,
+ const bool terminalAltEscape)
+{
+ const uint32_t codePoint = decodeUtf8(charBuffer);
+ if (codePoint == static_cast<uint32_t>(-1)) {
+ static bool debugInput = isTracingEnabled() && hasDebugFlag("input");
+ if (debugInput) {
+ StringBuilder error(64);
+ error << "Discarding invalid UTF-8 sequence:";
+ for (int i = 0; i < charLen; ++i) {
+ error << ' ';
+ error << hexOfInt<true, uint8_t>(charBuffer[i]);
+ }
+ trace("%s", error.c_str());
+ }
+ return;
+ }
+
+ const short charScan = codePoint > 0xFFFF ? -1 : VkKeyScan(codePoint);
+ uint16_t virtualKey = 0;
+ uint16_t winKeyState = 0;
+ uint32_t winCodePointDn = codePoint;
+ uint32_t winCodePointUp = codePoint;
+ uint16_t vtKeyState = 0;
+
+ if (charScan != -1) {
+ virtualKey = charScan & 0xFF;
+ if (charScan & 0x100) {
+ winKeyState |= SHIFT_PRESSED;
+ }
+ if (charScan & 0x200) {
+ winKeyState |= LEFT_CTRL_PRESSED;
+ }
+ if (charScan & 0x400) {
+ winKeyState |= RIGHT_ALT_PRESSED;
+ }
+ if (terminalAltEscape && (winKeyState & LEFT_CTRL_PRESSED)) {
+ // If the terminal escapes a Ctrl-<Key> with Alt, then set the
+ // codepoint to 0. On the other hand, if a character requires
+ // AltGr (like U+00B2 on a German layout), then VkKeyScan will
+ // report both Ctrl and Alt pressed, and we should keep the
+ // codepoint. See https://github.com/rprichard/winpty/issues/109.
+ winCodePointDn = 0;
+ winCodePointUp = 0;
+ }
+ }
+ if (terminalAltEscape) {
+ winCodePointUp = 0;
+ winKeyState |= LEFT_ALT_PRESSED;
+ vtKeyState |= LEFT_ALT_PRESSED;
+ }
+
+ appendKeyPress(records, virtualKey,
+ winCodePointDn, winCodePointUp, winKeyState,
+ codePoint, vtKeyState);
+}
+
+void ConsoleInput::appendKeyPress(std::vector<INPUT_RECORD> &records,
+ const uint16_t virtualKey,
+ const uint32_t winCodePointDn,
+ const uint32_t winCodePointUp,
+ const uint16_t winKeyState,
+ const uint32_t vtCodePoint,
+ const uint16_t vtKeyState)
+{
+ const bool ctrl = (winKeyState & LEFT_CTRL_PRESSED) != 0;
+ const bool leftAlt = (winKeyState & LEFT_ALT_PRESSED) != 0;
+ const bool rightAlt = (winKeyState & RIGHT_ALT_PRESSED) != 0;
+ const bool shift = (winKeyState & SHIFT_PRESSED) != 0;
+ const bool enhanced = (winKeyState & ENHANCED_KEY) != 0;
+ bool hasDebugInput = false;
+
+ if (isTracingEnabled()) {
+ static bool debugInput = hasDebugFlag("input");
+ if (debugInput) {
+ hasDebugInput = true;
+ InputMap::Key key = { virtualKey, winCodePointDn, winKeyState };
+ trace("keypress: %s", key.toString().c_str());
+ }
+ }
+
+ if (m_escapeInputEnabled &&
+ (virtualKey == VK_UP ||
+ virtualKey == VK_DOWN ||
+ virtualKey == VK_LEFT ||
+ virtualKey == VK_RIGHT ||
+ virtualKey == VK_HOME ||
+ virtualKey == VK_END) &&
+ !ctrl && !leftAlt && !rightAlt && !shift) {
+ flushInputRecords(records);
+ if (hasDebugInput) {
+ trace("sending keypress to console HWND");
+ }
+ sendKeyMessage(m_console.hwnd(), true, virtualKey);
+ sendKeyMessage(m_console.hwnd(), false, virtualKey);
+ return;
+ }
+
+ uint16_t stepKeyState = 0;
+ if (ctrl) {
+ stepKeyState |= LEFT_CTRL_PRESSED;
+ appendInputRecord(records, TRUE, VK_CONTROL, 0, stepKeyState);
+ }
+ if (leftAlt) {
+ stepKeyState |= LEFT_ALT_PRESSED;
+ appendInputRecord(records, TRUE, VK_MENU, 0, stepKeyState);
+ }
+ if (rightAlt) {
+ stepKeyState |= RIGHT_ALT_PRESSED;
+ appendInputRecord(records, TRUE, VK_MENU, 0, stepKeyState | ENHANCED_KEY);
+ }
+ if (shift) {
+ stepKeyState |= SHIFT_PRESSED;
+ appendInputRecord(records, TRUE, VK_SHIFT, 0, stepKeyState);
+ }
+ if (enhanced) {
+ stepKeyState |= ENHANCED_KEY;
+ }
+ if (m_escapeInputEnabled) {
+ reencodeEscapedKeyPress(records, virtualKey, vtCodePoint, vtKeyState);
+ } else {
+ appendCPInputRecords(records, TRUE, virtualKey, winCodePointDn, stepKeyState);
+ }
+ appendCPInputRecords(records, FALSE, virtualKey, winCodePointUp, stepKeyState);
+ if (enhanced) {
+ stepKeyState &= ~ENHANCED_KEY;
+ }
+ if (shift) {
+ stepKeyState &= ~SHIFT_PRESSED;
+ appendInputRecord(records, FALSE, VK_SHIFT, 0, stepKeyState);
+ }
+ if (rightAlt) {
+ stepKeyState &= ~RIGHT_ALT_PRESSED;
+ appendInputRecord(records, FALSE, VK_MENU, 0, stepKeyState | ENHANCED_KEY);
+ }
+ if (leftAlt) {
+ stepKeyState &= ~LEFT_ALT_PRESSED;
+ appendInputRecord(records, FALSE, VK_MENU, 0, stepKeyState);
+ }
+ if (ctrl) {
+ stepKeyState &= ~LEFT_CTRL_PRESSED;
+ appendInputRecord(records, FALSE, VK_CONTROL, 0, stepKeyState);
+ }
+}
+
+void ConsoleInput::appendCPInputRecords(std::vector<INPUT_RECORD> &records,
+ BOOL keyDown,
+ uint16_t virtualKey,
+ uint32_t codePoint,
+ uint16_t keyState)
+{
+ // This behavior really doesn't match that of the Windows console (in
+ // normal, non-escape-mode). Judging by the copy-and-paste behavior,
+ // Windows apparently handles everything outside of the keyboard layout by
+ // first sending a sequence of Alt+KeyPad events, then finally a key-up
+ // event whose UnicodeChar has the appropriate value. For U+00A2 (CENT
+ // SIGN):
+ //
+ // key: dn rpt=1 scn=56 LAlt-MENU ch=0
+ // key: dn rpt=1 scn=79 LAlt-NUMPAD1 ch=0
+ // key: up rpt=1 scn=79 LAlt-NUMPAD1 ch=0
+ // key: dn rpt=1 scn=76 LAlt-NUMPAD5 ch=0
+ // key: up rpt=1 scn=76 LAlt-NUMPAD5 ch=0
+ // key: dn rpt=1 scn=76 LAlt-NUMPAD5 ch=0
+ // key: up rpt=1 scn=76 LAlt-NUMPAD5 ch=0
+ // key: up rpt=1 scn=56 MENU ch=0xa2
+ //
+ // The Alt+155 value matches the encoding of U+00A2 in CP-437. Curiously,
+ // if I use "chcp 1252" to change the encoding, then copy-and-pasting
+ // produces Alt+162 instead. (U+00A2 is 162 in CP-1252.) However, typing
+ // Alt+155 or Alt+162 produce the same characters regardless of console
+ // code page. (That is, they use CP-437 and yield U+00A2 and U+00F3.)
+ //
+ // For characters outside the BMP, Windows repeats the process for both
+ // UTF-16 code units, e.g, for U+1F300 (CYCLONE):
+ //
+ // key: dn rpt=1 scn=56 LAlt-MENU ch=0
+ // key: dn rpt=1 scn=77 LAlt-NUMPAD6 ch=0
+ // key: up rpt=1 scn=77 LAlt-NUMPAD6 ch=0
+ // key: dn rpt=1 scn=81 LAlt-NUMPAD3 ch=0
+ // key: up rpt=1 scn=81 LAlt-NUMPAD3 ch=0
+ // key: up rpt=1 scn=56 MENU ch=0xd83c
+ // key: dn rpt=1 scn=56 LAlt-MENU ch=0
+ // key: dn rpt=1 scn=77 LAlt-NUMPAD6 ch=0
+ // key: up rpt=1 scn=77 LAlt-NUMPAD6 ch=0
+ // key: dn rpt=1 scn=81 LAlt-NUMPAD3 ch=0
+ // key: up rpt=1 scn=81 LAlt-NUMPAD3 ch=0
+ // key: up rpt=1 scn=56 MENU ch=0xdf00
+ //
+ // In this case, it sends Alt+63 twice, which signifies '?'. Apparently
+ // CMD and Cygwin bash are both able to decode this.
+ //
+ // Also note that typing Alt+NNN still works if NumLock is off, e.g.:
+ //
+ // key: dn rpt=1 scn=56 LAlt-MENU ch=0
+ // key: dn rpt=1 scn=79 LAlt-END ch=0
+ // key: up rpt=1 scn=79 LAlt-END ch=0
+ // key: dn rpt=1 scn=76 LAlt-CLEAR ch=0
+ // key: up rpt=1 scn=76 LAlt-CLEAR ch=0
+ // key: dn rpt=1 scn=76 LAlt-CLEAR ch=0
+ // key: up rpt=1 scn=76 LAlt-CLEAR ch=0
+ // key: up rpt=1 scn=56 MENU ch=0xa2
+ //
+ // Evidently, the Alt+NNN key events are not intended to be decoded to a
+ // character. Maybe programs are looking for a key-up ALT/MENU event with
+ // a non-zero character?
+
+ wchar_t ws[2];
+ const int wslen = encodeUtf16(ws, codePoint);
+
+ if (wslen == 1) {
+ appendInputRecord(records, keyDown, virtualKey, ws[0], keyState);
+ } else if (wslen == 2) {
+ appendInputRecord(records, keyDown, virtualKey, ws[0], keyState);
+ appendInputRecord(records, keyDown, virtualKey, ws[1], keyState);
+ } else {
+ // This situation isn't that bad, but it should never happen,
+ // because invalid codepoints shouldn't reach this point.
+ trace("INTERNAL ERROR: appendInputRecordCP: invalid codePoint: "
+ "U+%04X", codePoint);
+ }
+}
+
+void ConsoleInput::appendInputRecord(std::vector<INPUT_RECORD> &records,
+ BOOL keyDown,
+ uint16_t virtualKey,
+ wchar_t utf16Char,
+ uint16_t keyState)
+{
+ INPUT_RECORD ir = {};
+ ir.EventType = KEY_EVENT;
+ ir.Event.KeyEvent.bKeyDown = keyDown;
+ ir.Event.KeyEvent.wRepeatCount = 1;
+ ir.Event.KeyEvent.wVirtualKeyCode = virtualKey;
+ ir.Event.KeyEvent.wVirtualScanCode =
+ MapVirtualKey(virtualKey, MAPVK_VK_TO_VSC);
+ ir.Event.KeyEvent.uChar.UnicodeChar = utf16Char;
+ ir.Event.KeyEvent.dwControlKeyState = keyState;
+ records.push_back(ir);
+}
+
+DWORD ConsoleInput::inputConsoleMode()
+{
+ DWORD mode = 0;
+ if (!GetConsoleMode(m_conin, &mode)) {
+ trace("GetConsoleMode failed");
+ return 0;
+ }
+ return mode;
+}
diff --git a/src/libs/3rdparty/winpty/src/agent/ConsoleInput.h b/src/libs/3rdparty/winpty/src/agent/ConsoleInput.h
new file mode 100644
index 00000000000..e807d973ba5
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/ConsoleInput.h
@@ -0,0 +1,109 @@
+// Copyright (c) 2011-2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef CONSOLEINPUT_H
+#define CONSOLEINPUT_H
+
+#include <windows.h>
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "Coord.h"
+#include "InputMap.h"
+#include "SmallRect.h"
+
+class Win32Console;
+class DsrSender;
+
+class ConsoleInput
+{
+public:
+ ConsoleInput(HANDLE conin, int mouseMode, DsrSender &dsrSender,
+ Win32Console &console);
+ void writeInput(const std::string &input);
+ void flushIncompleteEscapeCode();
+ void setMouseWindowRect(SmallRect val) { m_mouseWindowRect = val; }
+ void updateInputFlags(bool forceTrace=false);
+ bool shouldActivateTerminalMouse();
+
+private:
+ void doWrite(bool isEof);
+ void flushInputRecords(std::vector<INPUT_RECORD> &records);
+ int scanInput(std::vector<INPUT_RECORD> &records,
+ const char *input,
+ int inputSize,
+ bool isEof);
+ int scanMouseInput(std::vector<INPUT_RECORD> &records,
+ const char *input,
+ int inputSize);
+ void appendUtf8Char(std::vector<INPUT_RECORD> &records,
+ const char *charBuffer,
+ int charLen,
+ bool terminalAltEscape);
+ void appendKeyPress(std::vector<INPUT_RECORD> &records,
+ uint16_t virtualKey,
+ uint32_t winCodePointDn,
+ uint32_t winCodePointUp,
+ uint16_t winKeyState,
+ uint32_t vtCodePoint,
+ uint16_t vtKeyState);
+
+public:
+ static void appendCPInputRecords(std::vector<INPUT_RECORD> &records,
+ BOOL keyDown,
+ uint16_t virtualKey,
+ uint32_t codePoint,
+ uint16_t keyState);
+ static void appendInputRecord(std::vector<INPUT_RECORD> &records,
+ BOOL keyDown,
+ uint16_t virtualKey,
+ wchar_t utf16Char,
+ uint16_t keyState);
+
+private:
+ DWORD inputConsoleMode();
+
+private:
+ Win32Console &m_console;
+ HANDLE m_conin = nullptr;
+ int m_mouseMode = 0;
+ DsrSender &m_dsrSender;
+ bool m_dsrSent = false;
+ std::string m_byteQueue;
+ InputMap m_inputMap;
+ DWORD m_lastWriteTick = 0;
+ DWORD m_mouseButtonState = 0;
+ struct DoubleClickDetection {
+ DWORD button = 0;
+ Coord pos;
+ DWORD tick = 0;
+ bool released = false;
+ } m_doubleClick;
+ bool m_enableExtendedEnabled = false;
+ bool m_mouseInputEnabled = false;
+ bool m_quickEditEnabled = false;
+ bool m_escapeInputEnabled = false;
+ SmallRect m_mouseWindowRect;
+};
+
+#endif // CONSOLEINPUT_H
diff --git a/src/libs/3rdparty/winpty/src/agent/ConsoleInputReencoding.cc b/src/libs/3rdparty/winpty/src/agent/ConsoleInputReencoding.cc
new file mode 100644
index 00000000000..b79545eea9c
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/ConsoleInputReencoding.cc
@@ -0,0 +1,121 @@
+// Copyright (c) 2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include "ConsoleInputReencoding.h"
+
+#include "ConsoleInput.h"
+
+namespace {
+
+static void outch(std::vector<INPUT_RECORD> &out, wchar_t ch) {
+ ConsoleInput::appendInputRecord(out, TRUE, 0, ch, 0);
+}
+
+} // anonymous namespace
+
+void reencodeEscapedKeyPress(
+ std::vector<INPUT_RECORD> &out,
+ uint16_t virtualKey,
+ uint32_t codePoint,
+ uint16_t keyState) {
+
+ struct EscapedKey {
+ enum { None, Numeric, Letter } kind;
+ wchar_t content[2];
+ };
+
+ EscapedKey escapeCode = {};
+ switch (virtualKey) {
+ case VK_UP: escapeCode = { EscapedKey::Letter, {'A'} }; break;
+ case VK_DOWN: escapeCode = { EscapedKey::Letter, {'B'} }; break;
+ case VK_RIGHT: escapeCode = { EscapedKey::Letter, {'C'} }; break;
+ case VK_LEFT: escapeCode = { EscapedKey::Letter, {'D'} }; break;
+ case VK_CLEAR: escapeCode = { EscapedKey::Letter, {'E'} }; break;
+ case VK_F1: escapeCode = { EscapedKey::Numeric, {'1', '1'} }; break;
+ case VK_F2: escapeCode = { EscapedKey::Numeric, {'1', '2'} }; break;
+ case VK_F3: escapeCode = { EscapedKey::Numeric, {'1', '3'} }; break;
+ case VK_F4: escapeCode = { EscapedKey::Numeric, {'1', '4'} }; break;
+ case VK_F5: escapeCode = { EscapedKey::Numeric, {'1', '5'} }; break;
+ case VK_F6: escapeCode = { EscapedKey::Numeric, {'1', '7'} }; break;
+ case VK_F7: escapeCode = { EscapedKey::Numeric, {'1', '8'} }; break;
+ case VK_F8: escapeCode = { EscapedKey::Numeric, {'1', '9'} }; break;
+ case VK_F9: escapeCode = { EscapedKey::Numeric, {'2', '0'} }; break;
+ case VK_F10: escapeCode = { EscapedKey::Numeric, {'2', '1'} }; break;
+ case VK_F11: escapeCode = { EscapedKey::Numeric, {'2', '3'} }; break;
+ case VK_F12: escapeCode = { EscapedKey::Numeric, {'2', '4'} }; break;
+ case VK_HOME: escapeCode = { EscapedKey::Letter, {'H'} }; break;
+ case VK_INSERT: escapeCode = { EscapedKey::Numeric, {'2'} }; break;
+ case VK_DELETE: escapeCode = { EscapedKey::Numeric, {'3'} }; break;
+ case VK_END: escapeCode = { EscapedKey::Letter, {'F'} }; break;
+ case VK_PRIOR: escapeCode = { EscapedKey::Numeric, {'5'} }; break;
+ case VK_NEXT: escapeCode = { EscapedKey::Numeric, {'6'} }; break;
+ }
+ if (escapeCode.kind != EscapedKey::None) {
+ int flags = 0;
+ if (keyState & SHIFT_PRESSED) { flags |= 0x1; }
+ if (keyState & LEFT_ALT_PRESSED) { flags |= 0x2; }
+ if (keyState & LEFT_CTRL_PRESSED) { flags |= 0x4; }
+ outch(out, L'\x1b');
+ outch(out, L'[');
+ if (escapeCode.kind == EscapedKey::Numeric) {
+ for (wchar_t ch : escapeCode.content) {
+ if (ch != L'\0') {
+ outch(out, ch);
+ }
+ }
+ } else if (flags != 0) {
+ outch(out, L'1');
+ }
+ if (flags != 0) {
+ outch(out, L';');
+ outch(out, L'1' + flags);
+ }
+ if (escapeCode.kind == EscapedKey::Numeric) {
+ outch(out, L'~');
+ } else {
+ outch(out, escapeCode.content[0]);
+ }
+ return;
+ }
+
+ switch (virtualKey) {
+ case VK_BACK:
+ if (keyState & LEFT_ALT_PRESSED) {
+ outch(out, L'\x1b');
+ }
+ outch(out, L'\x7f');
+ return;
+ case VK_TAB:
+ if (keyState & SHIFT_PRESSED) {
+ outch(out, L'\x1b');
+ outch(out, L'[');
+ outch(out, L'Z');
+ return;
+ }
+ break;
+ }
+
+ if (codePoint != 0) {
+ if (keyState & LEFT_ALT_PRESSED) {
+ outch(out, L'\x1b');
+ }
+ ConsoleInput::appendCPInputRecords(out, TRUE, 0, codePoint, 0);
+ }
+}
diff --git a/src/libs/3rdparty/winpty/src/agent/ConsoleInputReencoding.h b/src/libs/3rdparty/winpty/src/agent/ConsoleInputReencoding.h
new file mode 100644
index 00000000000..63bc006b5a7
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/ConsoleInputReencoding.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef AGENT_CONSOLE_INPUT_REENCODING_H
+#define AGENT_CONSOLE_INPUT_REENCODING_H
+
+#include <windows.h>
+
+#include <stdint.h>
+
+#include <vector>
+
+void reencodeEscapedKeyPress(
+ std::vector<INPUT_RECORD> &records,
+ uint16_t virtualKey,
+ uint32_t codePoint,
+ uint16_t keyState);
+
+#endif // AGENT_CONSOLE_INPUT_REENCODING_H
diff --git a/src/libs/3rdparty/winpty/src/agent/ConsoleLine.cc b/src/libs/3rdparty/winpty/src/agent/ConsoleLine.cc
new file mode 100644
index 00000000000..1d2bcb76859
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/ConsoleLine.cc
@@ -0,0 +1,152 @@
+// Copyright (c) 2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+//
+// ConsoleLine
+//
+// This data structure keep tracks of the previous CHAR_INFO content of an
+// output line and determines when a line has changed. Detecting line changes
+// is made complicated by terminal resizing.
+//
+
+#include "ConsoleLine.h"
+
+#include <algorithm>
+
+#include "../shared/WinptyAssert.h"
+
+static CHAR_INFO blankChar(WORD attributes)
+{
+ // N.B.: As long as we write to UnicodeChar rather than AsciiChar, there
+ // are no padding bytes that could contain uninitialized bytes. This fact
+ // is important for efficient comparison.
+ CHAR_INFO ret;
+ ret.Attributes = attributes;
+ ret.Char.UnicodeChar = L' ';
+ return ret;
+}
+
+static bool isLineBlank(const CHAR_INFO *line, int length, WORD attributes)
+{
+ for (int col = 0; col < length; ++col) {
+ if (line[col].Attributes != attributes ||
+ line[col].Char.UnicodeChar != L' ') {
+ return false;
+ }
+ }
+ return true;
+}
+
+static inline bool areLinesEqual(
+ const CHAR_INFO *line1,
+ const CHAR_INFO *line2,
+ int length)
+{
+ return memcmp(line1, line2, sizeof(CHAR_INFO) * length) == 0;
+}
+
+ConsoleLine::ConsoleLine() : m_prevLength(0)
+{
+}
+
+void ConsoleLine::reset()
+{
+ m_prevLength = 0;
+ m_prevData.clear();
+}
+
+// Determines whether the given line is sufficiently different from the
+// previously seen line as to justify reoutputting the line. The function
+// also sets the `ConsoleLine` to the given line, exactly as if `setLine` had
+// been called.
+bool ConsoleLine::detectChangeAndSetLine(const CHAR_INFO *const line, const int newLength)
+{
+ ASSERT(newLength >= 1);
+ ASSERT(m_prevLength <= static_cast<int>(m_prevData.size()));
+
+ if (newLength == m_prevLength) {
+ bool equalLines = areLinesEqual(m_prevData.data(), line, newLength);
+ if (!equalLines) {
+ setLine(line, newLength);
+ }
+ return !equalLines;
+ } else {
+ if (m_prevLength == 0) {
+ setLine(line, newLength);
+ return true;
+ }
+
+ ASSERT(m_prevLength >= 1);
+ const WORD prevBlank = m_prevData[m_prevLength - 1].Attributes;
+ const WORD newBlank = line[newLength - 1].Attributes;
+
+ bool equalLines = false;
+ if (newLength < m_prevLength) {
+ // The line has become shorter. The lines are equal if the common
+ // part is equal, and if the newly truncated characters were blank.
+ equalLines =
+ areLinesEqual(m_prevData.data(), line, newLength) &&
+ isLineBlank(m_prevData.data() + newLength,
+ m_prevLength - newLength,
+ newBlank);
+ } else {
+ //
+ // The line has become longer. The lines are equal if the common
+ // part is equal, and if both the extra characters and any
+ // potentially reexposed characters are blank.
+ //
+ // Two of the most relevant terminals for winpty--mintty and
+ // jediterm--don't (currently) erase the obscured content when a
+ // line is cleared, so we should anticipate its existence when
+ // making a terminal wider and reoutput the line. See:
+ //
+ // * https://github.com/mintty/mintty/issues/480
+ // * https://github.com/JetBrains/jediterm/issues/118
+ //
+ ASSERT(newLength > m_prevLength);
+ equalLines =
+ areLinesEqual(m_prevData.data(), line, m_prevLength) &&
+ isLineBlank(m_prevData.data() + m_prevLength,
+ std::min<int>(m_prevData.size(), newLength) - m_prevLength,
+ prevBlank) &&
+ isLineBlank(line + m_prevLength,
+ newLength - m_prevLength,
+ prevBlank);
+ }
+ setLine(line, newLength);
+ return !equalLines;
+ }
+}
+
+void ConsoleLine::setLine(const CHAR_INFO *const line, const int newLength)
+{
+ if (static_cast<int>(m_prevData.size()) < newLength) {
+ m_prevData.resize(newLength);
+ }
+ memcpy(m_prevData.data(), line, sizeof(CHAR_INFO) * newLength);
+ m_prevLength = newLength;
+}
+
+void ConsoleLine::blank(WORD attributes)
+{
+ m_prevData.resize(1);
+ m_prevData[0] = blankChar(attributes);
+ m_prevLength = 1;
+}
diff --git a/src/libs/3rdparty/winpty/src/agent/ConsoleLine.h b/src/libs/3rdparty/winpty/src/agent/ConsoleLine.h
new file mode 100644
index 00000000000..802c189c756
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/ConsoleLine.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef CONSOLE_LINE_H
+#define CONSOLE_LINE_H
+
+#include <windows.h>
+
+#include <vector>
+
+class ConsoleLine
+{
+public:
+ ConsoleLine();
+ void reset();
+ bool detectChangeAndSetLine(const CHAR_INFO *line, int newLength);
+ void setLine(const CHAR_INFO *line, int newLength);
+ void blank(WORD attributes);
+private:
+ int m_prevLength;
+ std::vector<CHAR_INFO> m_prevData;
+};
+
+#endif // CONSOLE_LINE_H
diff --git a/src/libs/3rdparty/winpty/src/agent/Coord.h b/src/libs/3rdparty/winpty/src/agent/Coord.h
new file mode 100644
index 00000000000..74c98addac6
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/Coord.h
@@ -0,0 +1,87 @@
+// Copyright (c) 2011-2012 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef COORD_H
+#define COORD_H
+
+#include <windows.h>
+
+#include <string>
+
+#include "../shared/winpty_snprintf.h"
+
+struct Coord : COORD {
+ Coord()
+ {
+ X = 0;
+ Y = 0;
+ }
+
+ Coord(SHORT x, SHORT y)
+ {
+ X = x;
+ Y = y;
+ }
+
+ Coord(COORD other)
+ {
+ *(COORD*)this = other;
+ }
+
+ Coord(const Coord &other)
+ {
+ *(COORD*)this = *(const COORD*)&other;
+ }
+
+ Coord &operator=(const Coord &other)
+ {
+ *(COORD*)this = *(const COORD*)&other;
+ return *this;
+ }
+
+ bool operator==(const Coord &other) const
+ {
+ return X == other.X && Y == other.Y;
+ }
+
+ bool operator!=(const Coord &other) const
+ {
+ return !(*this == other);
+ }
+
+ Coord operator+(const Coord &other) const
+ {
+ return Coord(X + other.X, Y + other.Y);
+ }
+
+ bool isEmpty() const
+ {
+ return X <= 0 || Y <= 0;
+ }
+
+ std::string toString() const
+ {
+ char ret[32];
+ winpty_snprintf(ret, "(%d,%d)", X, Y);
+ return std::string(ret);
+ }
+};
+
+#endif // COORD_H
diff --git a/src/libs/3rdparty/winpty/src/agent/DebugShowInput.cc b/src/libs/3rdparty/winpty/src/agent/DebugShowInput.cc
new file mode 100644
index 00000000000..191b2e1466a
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/DebugShowInput.cc
@@ -0,0 +1,239 @@
+// Copyright (c) 2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include "DebugShowInput.h"
+
+#include <windows.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <string>
+
+#include "../shared/StringBuilder.h"
+#include "InputMap.h"
+
+namespace {
+
+struct Flag {
+ DWORD value;
+ const char *text;
+};
+
+static const Flag kButtonStates[] = {
+ { FROM_LEFT_1ST_BUTTON_PRESSED, "1" },
+ { FROM_LEFT_2ND_BUTTON_PRESSED, "2" },
+ { FROM_LEFT_3RD_BUTTON_PRESSED, "3" },
+ { FROM_LEFT_4TH_BUTTON_PRESSED, "4" },
+ { RIGHTMOST_BUTTON_PRESSED, "R" },
+};
+
+static const Flag kControlKeyStates[] = {
+ { CAPSLOCK_ON, "CapsLock" },
+ { ENHANCED_KEY, "Enhanced" },
+ { LEFT_ALT_PRESSED, "LAlt" },
+ { LEFT_CTRL_PRESSED, "LCtrl" },
+ { NUMLOCK_ON, "NumLock" },
+ { RIGHT_ALT_PRESSED, "RAlt" },
+ { RIGHT_CTRL_PRESSED, "RCtrl" },
+ { SCROLLLOCK_ON, "ScrollLock" },
+ { SHIFT_PRESSED, "Shift" },
+};
+
+static const Flag kMouseEventFlags[] = {
+ { DOUBLE_CLICK, "Double" },
+ { 8/*MOUSE_HWHEELED*/, "HWheel" },
+ { MOUSE_MOVED, "Move" },
+ { MOUSE_WHEELED, "Wheel" },
+};
+
+static void writeFlags(StringBuilder &out, DWORD flags,
+ const char *remainderName,
+ const Flag *table, size_t tableSize,
+ char pre, char sep, char post) {
+ DWORD remaining = flags;
+ bool wroteSomething = false;
+ for (size_t i = 0; i < tableSize; ++i) {
+ const Flag &f = table[i];
+ if ((f.value & flags) == f.value) {
+ if (!wroteSomething && pre != '\0') {
+ out << pre;
+ } else if (wroteSomething && sep != '\0') {
+ out << sep;
+ }
+ out << f.text;
+ wroteSomething = true;
+ remaining &= ~f.value;
+ }
+ }
+ if (remaining != 0) {
+ if (!wroteSomething && pre != '\0') {
+ out << pre;
+ } else if (wroteSomething && sep != '\0') {
+ out << sep;
+ }
+ out << remainderName << "(0x" << hexOfInt(remaining) << ')';
+ wroteSomething = true;
+ }
+ if (wroteSomething && post != '\0') {
+ out << post;
+ }
+}
+
+template <size_t n>
+static void writeFlags(StringBuilder &out, DWORD flags,
+ const char *remainderName,
+ const Flag (&table)[n],
+ char pre, char sep, char post) {
+ writeFlags(out, flags, remainderName, table, n, pre, sep, post);
+}
+
+} // anonymous namespace
+
+std::string controlKeyStatePrefix(DWORD controlKeyState) {
+ StringBuilder sb;
+ writeFlags(sb, controlKeyState,
+ "keyState", kControlKeyStates, '\0', '-', '-');
+ return sb.str_moved();
+}
+
+std::string mouseEventToString(const MOUSE_EVENT_RECORD &mer) {
+ const uint16_t buttons = mer.dwButtonState & 0xFFFF;
+ const int16_t wheel = mer.dwButtonState >> 16;
+ StringBuilder sb;
+ sb << "pos=" << mer.dwMousePosition.X << ','
+ << mer.dwMousePosition.Y;
+ writeFlags(sb, mer.dwControlKeyState, "keyState", kControlKeyStates, ' ', ' ', '\0');
+ writeFlags(sb, mer.dwEventFlags, "flags", kMouseEventFlags, ' ', ' ', '\0');
+ writeFlags(sb, buttons, "buttons", kButtonStates, ' ', ' ', '\0');
+ if (wheel != 0) {
+ sb << " wheel=" << wheel;
+ }
+ return sb.str_moved();
+}
+
+void debugShowInput(bool enableMouse, bool escapeInput) {
+ HANDLE conin = GetStdHandle(STD_INPUT_HANDLE);
+ DWORD origConsoleMode = 0;
+ if (!GetConsoleMode(conin, &origConsoleMode)) {
+ fprintf(stderr, "Error: could not read console mode -- "
+ "is STDIN a console handle?\n");
+ exit(1);
+ }
+ DWORD restoreConsoleMode = origConsoleMode;
+ if (enableMouse && !(restoreConsoleMode & ENABLE_EXTENDED_FLAGS)) {
+ // We need to disable QuickEdit mode, because it blocks mouse events.
+ // If ENABLE_EXTENDED_FLAGS wasn't originally in the console mode, then
+ // we have no way of knowning whether QuickEdit or InsertMode are
+ // currently enabled. Enable them both (eventually), because they're
+ // sensible defaults. This case shouldn't happen typically. See
+ // misc/EnableExtendedFlags.txt.
+ restoreConsoleMode |= ENABLE_EXTENDED_FLAGS;
+ restoreConsoleMode |= ENABLE_QUICK_EDIT_MODE;
+ restoreConsoleMode |= ENABLE_INSERT_MODE;
+ }
+ DWORD newConsoleMode = restoreConsoleMode;
+ newConsoleMode &= ~ENABLE_PROCESSED_INPUT;
+ newConsoleMode &= ~ENABLE_LINE_INPUT;
+ newConsoleMode &= ~ENABLE_ECHO_INPUT;
+ newConsoleMode |= ENABLE_WINDOW_INPUT;
+ if (enableMouse) {
+ newConsoleMode |= ENABLE_MOUSE_INPUT;
+ newConsoleMode &= ~ENABLE_QUICK_EDIT_MODE;
+ } else {
+ newConsoleMode &= ~ENABLE_MOUSE_INPUT;
+ }
+ if (escapeInput) {
+ // As of this writing (2016-06-05), Microsoft has shipped two preview
+ // builds of Windows 10 (14316 and 14342) that include a new "Windows
+ // Subsystem for Linux" that runs Ubuntu in a new subsystem. Running
+ // bash in this subsystem requires the non-legacy console mode, and the
+ // console input buffer is put into a special mode where escape
+ // sequences are written into the console input buffer. This mode is
+ // enabled with the 0x200 flag, which is as-yet undocumented.
+ // See https://github.com/rprichard/winpty/issues/82.
+ newConsoleMode |= 0x200;
+ }
+ if (!SetConsoleMode(conin, newConsoleMode)) {
+ fprintf(stderr, "Error: could not set console mode "
+ "(0x%x -> 0x%x -> 0x%x)\n",
+ static_cast<unsigned int>(origConsoleMode),
+ static_cast<unsigned int>(newConsoleMode),
+ static_cast<unsigned int>(restoreConsoleMode));
+ exit(1);
+ }
+ printf("\nPress any keys -- Ctrl-D exits\n\n");
+ INPUT_RECORD records[32];
+ DWORD actual = 0;
+ bool finished = false;
+ while (!finished &&
+ ReadConsoleInputW(conin, records, 32, &actual) && actual >= 1) {
+ StringBuilder sb;
+ for (DWORD i = 0; i < actual; ++i) {
+ const INPUT_RECORD &record = records[i];
+ if (record.EventType == KEY_EVENT) {
+ const KEY_EVENT_RECORD &ker = record.Event.KeyEvent;
+ InputMap::Key key = {
+ ker.wVirtualKeyCode,
+ ker.uChar.UnicodeChar,
+ static_cast<uint16_t>(ker.dwControlKeyState),
+ };
+ sb << "key: " << (ker.bKeyDown ? "dn" : "up")
+ << " rpt=" << ker.wRepeatCount
+ << " scn=" << (ker.wVirtualScanCode ? "0x" : "") << hexOfInt(ker.wVirtualScanCode)
+ << ' ' << key.toString() << '\n';
+ if ((ker.dwControlKeyState &
+ (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) &&
+ ker.wVirtualKeyCode == 'D') {
+ finished = true;
+ break;
+ } else if (ker.wVirtualKeyCode == 0 &&
+ ker.wVirtualScanCode == 0 &&
+ ker.uChar.UnicodeChar == 4) {
+ // Also look for a zeroed-out Ctrl-D record generated for
+ // ENABLE_VIRTUAL_TERMINAL_INPUT.
+ finished = true;
+ break;
+ }
+ } else if (record.EventType == MOUSE_EVENT) {
+ const MOUSE_EVENT_RECORD &mer = record.Event.MouseEvent;
+ sb << "mouse: " << mouseEventToString(mer) << '\n';
+ } else if (record.EventType == WINDOW_BUFFER_SIZE_EVENT) {
+ const WINDOW_BUFFER_SIZE_RECORD &wbsr =
+ record.Event.WindowBufferSizeEvent;
+ sb << "buffer-resized: dwSize=("
+ << wbsr.dwSize.X << ','
+ << wbsr.dwSize.Y << ")\n";
+ } else if (record.EventType == MENU_EVENT) {
+ const MENU_EVENT_RECORD &mer = record.Event.MenuEvent;
+ sb << "menu-event: commandId=0x"
+ << hexOfInt(mer.dwCommandId) << '\n';
+ } else if (record.EventType == FOCUS_EVENT) {
+ const FOCUS_EVENT_RECORD &fer = record.Event.FocusEvent;
+ sb << "focus: " << (fer.bSetFocus ? "gained" : "lost") << '\n';
+ }
+ }
+
+ const auto str = sb.str_moved();
+ fwrite(str.data(), 1, str.size(), stdout);
+ fflush(stdout);
+ }
+ SetConsoleMode(conin, restoreConsoleMode);
+}
diff --git a/src/libs/3rdparty/winpty/src/agent/DebugShowInput.h b/src/libs/3rdparty/winpty/src/agent/DebugShowInput.h
new file mode 100644
index 00000000000..4fa13604bd4
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/DebugShowInput.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef AGENT_DEBUG_SHOW_INPUT_H
+#define AGENT_DEBUG_SHOW_INPUT_H
+
+#include <windows.h>
+
+#include <string>
+
+std::string controlKeyStatePrefix(DWORD controlKeyState);
+std::string mouseEventToString(const MOUSE_EVENT_RECORD &mer);
+void debugShowInput(bool enableMouse, bool escapeInput);
+
+#endif // AGENT_DEBUG_SHOW_INPUT_H
diff --git a/src/libs/3rdparty/winpty/src/agent/DefaultInputMap.cc b/src/libs/3rdparty/winpty/src/agent/DefaultInputMap.cc
new file mode 100644
index 00000000000..5e29d98e4ec
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/DefaultInputMap.cc
@@ -0,0 +1,422 @@
+// Copyright (c) 2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include "DefaultInputMap.h"
+
+#include <windows.h>
+#include <string.h>
+
+#include <algorithm>
+
+#include "../shared/StringBuilder.h"
+#include "../shared/WinptyAssert.h"
+#include "InputMap.h"
+
+#define ESC "\x1B"
+#define DIM(x) (sizeof(x) / sizeof((x)[0]))
+
+namespace {
+
+struct EscapeEncoding {
+ bool alt_prefix_allowed;
+ char prefix;
+ char id;
+ int modifiers;
+ InputMap::Key key;
+};
+
+// Modifiers. A "modifier" is an integer from 2 to 8 that conveys the status
+// of Shift(1), Alt(2), and Ctrl(4). The value is constructed by OR'ing the
+// appropriate value for each active modifier, then adding 1.
+//
+// Details:
+// - kBare: expands to: ESC <prefix> <suffix>
+// - kSemiMod: expands to: ESC <prefix> <numid> ; <mod> <suffix>
+// - kBareMod: expands to: ESC <prefix> <mod> <suffix>
+const int kBare = 0x01;
+const int kSemiMod = 0x02;
+const int kBareMod = 0x04;
+
+// Numeric escape sequences suffixes:
+// - with no flag: accept: ~
+// - kSuffixCtrl: accept: ~ ^
+// - kSuffixShift: accept: ~ $
+// - kSuffixBoth: accept: ~ ^ $ @
+const int kSuffixCtrl = 0x08;
+const int kSuffixShift = 0x10;
+const int kSuffixBoth = kSuffixCtrl | kSuffixShift;
+
+static const EscapeEncoding escapeLetterEncodings[] = {
+ // Conventional arrow keys
+ // kBareMod: Ubuntu /etc/inputrc and IntelliJ/JediTerm use escapes like: ESC [ n ABCD
+ { true, '[', 'A', kBare | kBareMod | kSemiMod, { VK_UP, '\0', 0 } },
+ { true, '[', 'B', kBare | kBareMod | kSemiMod, { VK_DOWN, '\0', 0 } },
+ { true, '[', 'C', kBare | kBareMod | kSemiMod, { VK_RIGHT, '\0', 0 } },
+ { true, '[', 'D', kBare | kBareMod | kSemiMod, { VK_LEFT, '\0', 0 } },
+
+ // putty. putty uses this sequence for Ctrl-Arrow, Shift-Arrow, and
+ // Ctrl-Shift-Arrow, but I can only decode to one choice, so I'm just
+ // leaving the modifier off altogether.
+ { true, 'O', 'A', kBare, { VK_UP, '\0', 0 } },
+ { true, 'O', 'B', kBare, { VK_DOWN, '\0', 0 } },
+ { true, 'O', 'C', kBare, { VK_RIGHT, '\0', 0 } },
+ { true, 'O', 'D', kBare, { VK_LEFT, '\0', 0 } },
+
+ // rxvt, rxvt-unicode
+ // Shift-Ctrl-Arrow can't be identified. It's the same as Shift-Arrow.
+ { true, '[', 'a', kBare, { VK_UP, '\0', SHIFT_PRESSED } },
+ { true, '[', 'b', kBare, { VK_DOWN, '\0', SHIFT_PRESSED } },
+ { true, '[', 'c', kBare, { VK_RIGHT, '\0', SHIFT_PRESSED } },
+ { true, '[', 'd', kBare, { VK_LEFT, '\0', SHIFT_PRESSED } },
+ { true, 'O', 'a', kBare, { VK_UP, '\0', LEFT_CTRL_PRESSED } },
+ { true, 'O', 'b', kBare, { VK_DOWN, '\0', LEFT_CTRL_PRESSED } },
+ { true, 'O', 'c', kBare, { VK_RIGHT, '\0', LEFT_CTRL_PRESSED } },
+ { true, 'O', 'd', kBare, { VK_LEFT, '\0', LEFT_CTRL_PRESSED } },
+
+ // Numpad 5 with NumLock off
+ // * xterm, mintty, and gnome-terminal use `ESC [ E`.
+ // * putty, TERM=cygwin, TERM=linux all use `ESC [ G` for 5
+ // * putty uses `ESC O G` for Ctrl-5 and Shift-5. Omit the modifier
+ // as with putty's arrow keys.
+ // * I never saw modifiers inserted into these escapes, but I think
+ // it should be completely OK with the CSI escapes.
+ { true, '[', 'E', kBare | kSemiMod, { VK_CLEAR, '\0', 0 } },
+ { true, '[', 'G', kBare | kSemiMod, { VK_CLEAR, '\0', 0 } },
+ { true, 'O', 'G', kBare, { VK_CLEAR, '\0', 0 } },
+
+ // Home/End, letter version
+ // * gnome-terminal uses `ESC O [HF]`. I never saw it modified.
+ // kBareMod: IntelliJ/JediTerm uses escapes like: ESC [ n HF
+ { true, '[', 'H', kBare | kBareMod | kSemiMod, { VK_HOME, '\0', 0 } },
+ { true, '[', 'F', kBare | kBareMod | kSemiMod, { VK_END, '\0', 0 } },
+ { true, 'O', 'H', kBare, { VK_HOME, '\0', 0 } },
+ { true, 'O', 'F', kBare, { VK_END, '\0', 0 } },
+
+ // F1-F4, letter version (xterm, VTE, konsole)
+ { true, '[', 'P', kSemiMod, { VK_F1, '\0', 0 } },
+ { true, '[', 'Q', kSemiMod, { VK_F2, '\0', 0 } },
+ { true, '[', 'R', kSemiMod, { VK_F3, '\0', 0 } },
+ { true, '[', 'S', kSemiMod, { VK_F4, '\0', 0 } },
+
+ // GNOME VTE and Konsole have special encodings for modified F1-F4:
+ // * [VTE] ESC O 1 ; n [PQRS]
+ // * [Konsole] ESC O n [PQRS]
+ { false, 'O', 'P', kBare | kBareMod | kSemiMod, { VK_F1, '\0', 0 } },
+ { false, 'O', 'Q', kBare | kBareMod | kSemiMod, { VK_F2, '\0', 0 } },
+ { false, 'O', 'R', kBare | kBareMod | kSemiMod, { VK_F3, '\0', 0 } },
+ { false, 'O', 'S', kBare | kBareMod | kSemiMod, { VK_F4, '\0', 0 } },
+
+ // Handle the "application numpad" escape sequences.
+ //
+ // Terminals output these codes under various circumstances:
+ // * rxvt-unicode: numpad, hold down SHIFT
+ // * rxvt: numpad, by default
+ // * xterm: numpad, after enabling app-mode using DECPAM (`ESC =`). xterm
+ // generates `ESC O <mod> <letter>` for modified numpad presses,
+ // necessitating kBareMod.
+ // * mintty: by combining Ctrl with various keys such as '1' or ','.
+ // Handling those keys is difficult, because mintty is generating the
+ // same sequence for Ctrl-1 and Ctrl-NumPadEnd -- should the virtualKey
+ // be '1' or VK_HOME?
+
+ { true, 'O', 'M', kBare | kBareMod, { VK_RETURN, '\r', 0 } },
+ { true, 'O', 'j', kBare | kBareMod, { VK_MULTIPLY, '*', 0 } },
+ { true, 'O', 'k', kBare | kBareMod, { VK_ADD, '+', 0 } },
+ { true, 'O', 'm', kBare | kBareMod, { VK_SUBTRACT, '-', 0 } },
+ { true, 'O', 'n', kBare | kBareMod, { VK_DELETE, '\0', 0 } },
+ { true, 'O', 'o', kBare | kBareMod, { VK_DIVIDE, '/', 0 } },
+ { true, 'O', 'p', kBare | kBareMod, { VK_INSERT, '\0', 0 } },
+ { true, 'O', 'q', kBare | kBareMod, { VK_END, '\0', 0 } },
+ { true, 'O', 'r', kBare | kBareMod, { VK_DOWN, '\0', 0 } },
+ { true, 'O', 's', kBare | kBareMod, { VK_NEXT, '\0', 0 } },
+ { true, 'O', 't', kBare | kBareMod, { VK_LEFT, '\0', 0 } },
+ { true, 'O', 'u', kBare | kBareMod, { VK_CLEAR, '\0', 0 } },
+ { true, 'O', 'v', kBare | kBareMod, { VK_RIGHT, '\0', 0 } },
+ { true, 'O', 'w', kBare | kBareMod, { VK_HOME, '\0', 0 } },
+ { true, 'O', 'x', kBare | kBareMod, { VK_UP, '\0', 0 } },
+ { true, 'O', 'y', kBare | kBareMod, { VK_PRIOR, '\0', 0 } },
+
+ { true, '[', 'M', kBare | kSemiMod, { VK_RETURN, '\r', 0 } },
+ { true, '[', 'j', kBare | kSemiMod, { VK_MULTIPLY, '*', 0 } },
+ { true, '[', 'k', kBare | kSemiMod, { VK_ADD, '+', 0 } },
+ { true, '[', 'm', kBare | kSemiMod, { VK_SUBTRACT, '-', 0 } },
+ { true, '[', 'n', kBare | kSemiMod, { VK_DELETE, '\0', 0 } },
+ { true, '[', 'o', kBare | kSemiMod, { VK_DIVIDE, '/', 0 } },
+ { true, '[', 'p', kBare | kSemiMod, { VK_INSERT, '\0', 0 } },
+ { true, '[', 'q', kBare | kSemiMod, { VK_END, '\0', 0 } },
+ { true, '[', 'r', kBare | kSemiMod, { VK_DOWN, '\0', 0 } },
+ { true, '[', 's', kBare | kSemiMod, { VK_NEXT, '\0', 0 } },
+ { true, '[', 't', kBare | kSemiMod, { VK_LEFT, '\0', 0 } },
+ { true, '[', 'u', kBare | kSemiMod, { VK_CLEAR, '\0', 0 } },
+ { true, '[', 'v', kBare | kSemiMod, { VK_RIGHT, '\0', 0 } },
+ { true, '[', 'w', kBare | kSemiMod, { VK_HOME, '\0', 0 } },
+ { true, '[', 'x', kBare | kSemiMod, { VK_UP, '\0', 0 } },
+ { true, '[', 'y', kBare | kSemiMod, { VK_PRIOR, '\0', 0 } },
+
+ { false, '[', 'Z', kBare, { VK_TAB, '\t', SHIFT_PRESSED } },
+};
+
+static const EscapeEncoding escapeNumericEncodings[] = {
+ { true, '[', 1, kBare | kSemiMod | kSuffixBoth, { VK_HOME, '\0', 0 } },
+ { true, '[', 2, kBare | kSemiMod | kSuffixBoth, { VK_INSERT, '\0', 0 } },
+ { true, '[', 3, kBare | kSemiMod | kSuffixBoth, { VK_DELETE, '\0', 0 } },
+ { true, '[', 4, kBare | kSemiMod | kSuffixBoth, { VK_END, '\0', 0 } },
+ { true, '[', 5, kBare | kSemiMod | kSuffixBoth, { VK_PRIOR, '\0', 0 } },
+ { true, '[', 6, kBare | kSemiMod | kSuffixBoth, { VK_NEXT, '\0', 0 } },
+ { true, '[', 7, kBare | kSemiMod | kSuffixBoth, { VK_HOME, '\0', 0 } },
+ { true, '[', 8, kBare | kSemiMod | kSuffixBoth, { VK_END, '\0', 0 } },
+ { true, '[', 11, kBare | kSemiMod | kSuffixBoth, { VK_F1, '\0', 0 } },
+ { true, '[', 12, kBare | kSemiMod | kSuffixBoth, { VK_F2, '\0', 0 } },
+ { true, '[', 13, kBare | kSemiMod | kSuffixBoth, { VK_F3, '\0', 0 } },
+ { true, '[', 14, kBare | kSemiMod | kSuffixBoth, { VK_F4, '\0', 0 } },
+ { true, '[', 15, kBare | kSemiMod | kSuffixBoth, { VK_F5, '\0', 0 } },
+ { true, '[', 17, kBare | kSemiMod | kSuffixBoth, { VK_F6, '\0', 0 } },
+ { true, '[', 18, kBare | kSemiMod | kSuffixBoth, { VK_F7, '\0', 0 } },
+ { true, '[', 19, kBare | kSemiMod | kSuffixBoth, { VK_F8, '\0', 0 } },
+ { true, '[', 20, kBare | kSemiMod | kSuffixBoth, { VK_F9, '\0', 0 } },
+ { true, '[', 21, kBare | kSemiMod | kSuffixBoth, { VK_F10, '\0', 0 } },
+ { true, '[', 23, kBare | kSemiMod | kSuffixBoth, { VK_F11, '\0', 0 } },
+ { true, '[', 24, kBare | kSemiMod | kSuffixBoth, { VK_F12, '\0', 0 } },
+ { true, '[', 25, kBare | kSemiMod | kSuffixBoth, { VK_F3, '\0', SHIFT_PRESSED } },
+ { true, '[', 26, kBare | kSemiMod | kSuffixBoth, { VK_F4, '\0', SHIFT_PRESSED } },
+ { true, '[', 28, kBare | kSemiMod | kSuffixBoth, { VK_F5, '\0', SHIFT_PRESSED } },
+ { true, '[', 29, kBare | kSemiMod | kSuffixBoth, { VK_F6, '\0', SHIFT_PRESSED } },
+ { true, '[', 31, kBare | kSemiMod | kSuffixBoth, { VK_F7, '\0', SHIFT_PRESSED } },
+ { true, '[', 32, kBare | kSemiMod | kSuffixBoth, { VK_F8, '\0', SHIFT_PRESSED } },
+ { true, '[', 33, kBare | kSemiMod | kSuffixBoth, { VK_F9, '\0', SHIFT_PRESSED } },
+ { true, '[', 34, kBare | kSemiMod | kSuffixBoth, { VK_F10, '\0', SHIFT_PRESSED } },
+};
+
+const int kCsiShiftModifier = 1;
+const int kCsiAltModifier = 2;
+const int kCsiCtrlModifier = 4;
+
+static inline bool useEnhancedForVirtualKey(uint16_t vk) {
+ switch (vk) {
+ case VK_UP:
+ case VK_DOWN:
+ case VK_LEFT:
+ case VK_RIGHT:
+ case VK_INSERT:
+ case VK_DELETE:
+ case VK_HOME:
+ case VK_END:
+ case VK_PRIOR:
+ case VK_NEXT:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static void addSimpleEntries(InputMap &inputMap) {
+ struct SimpleEncoding {
+ const char *encoding;
+ InputMap::Key key;
+ };
+
+ static const SimpleEncoding simpleEncodings[] = {
+ // Ctrl-<letter/digit> seems to be handled OK by the default code path.
+
+ { "\x7F", { VK_BACK, '\x08', 0, } },
+ { ESC "\x7F", { VK_BACK, '\x08', LEFT_ALT_PRESSED, } },
+ { "\x03", { 'C', '\x03', LEFT_CTRL_PRESSED, } },
+
+ // Handle special F1-F5 for TERM=linux and TERM=cygwin.
+ { ESC "[[A", { VK_F1, '\0', 0 } },
+ { ESC "[[B", { VK_F2, '\0', 0 } },
+ { ESC "[[C", { VK_F3, '\0', 0 } },
+ { ESC "[[D", { VK_F4, '\0', 0 } },
+ { ESC "[[E", { VK_F5, '\0', 0 } },
+
+ { ESC ESC "[[A", { VK_F1, '\0', LEFT_ALT_PRESSED } },
+ { ESC ESC "[[B", { VK_F2, '\0', LEFT_ALT_PRESSED } },
+ { ESC ESC "[[C", { VK_F3, '\0', LEFT_ALT_PRESSED } },
+ { ESC ESC "[[D", { VK_F4, '\0', LEFT_ALT_PRESSED } },
+ { ESC ESC "[[E", { VK_F5, '\0', LEFT_ALT_PRESSED } },
+ };
+
+ for (size_t i = 0; i < DIM(simpleEncodings); ++i) {
+ auto k = simpleEncodings[i].key;
+ if (useEnhancedForVirtualKey(k.virtualKey)) {
+ k.keyState |= ENHANCED_KEY;
+ }
+ inputMap.set(simpleEncodings[i].encoding,
+ strlen(simpleEncodings[i].encoding),
+ k);
+ }
+}
+
+struct ExpandContext {
+ InputMap &inputMap;
+ const EscapeEncoding &e;
+ char *buffer;
+ char *bufferEnd;
+};
+
+static inline void setEncoding(const ExpandContext &ctx, char *end,
+ uint16_t extraKeyState) {
+ InputMap::Key k = ctx.e.key;
+ k.keyState |= extraKeyState;
+ if (k.keyState & LEFT_CTRL_PRESSED) {
+ switch (k.virtualKey) {
+ case VK_ADD:
+ case VK_DIVIDE:
+ case VK_MULTIPLY:
+ case VK_SUBTRACT:
+ k.unicodeChar = '\0';
+ break;
+ case VK_RETURN:
+ k.unicodeChar = '\n';
+ break;
+ }
+ }
+ if (useEnhancedForVirtualKey(k.virtualKey)) {
+ k.keyState |= ENHANCED_KEY;
+ }
+ ctx.inputMap.set(ctx.buffer, end - ctx.buffer, k);
+}
+
+static inline uint16_t keyStateForMod(int mod) {
+ int ret = 0;
+ if ((mod - 1) & kCsiShiftModifier) ret |= SHIFT_PRESSED;
+ if ((mod - 1) & kCsiAltModifier) ret |= LEFT_ALT_PRESSED;
+ if ((mod - 1) & kCsiCtrlModifier) ret |= LEFT_CTRL_PRESSED;
+ return ret;
+}
+
+static void expandNumericEncodingSuffix(const ExpandContext &ctx, char *p,
+ uint16_t extraKeyState) {
+ ASSERT(p <= ctx.bufferEnd - 1);
+ {
+ char *q = p;
+ *q++ = '~';
+ setEncoding(ctx, q, extraKeyState);
+ }
+ if (ctx.e.modifiers & kSuffixShift) {
+ char *q = p;
+ *q++ = '$';
+ setEncoding(ctx, q, extraKeyState | SHIFT_PRESSED);
+ }
+ if (ctx.e.modifiers & kSuffixCtrl) {
+ char *q = p;
+ *q++ = '^';
+ setEncoding(ctx, q, extraKeyState | LEFT_CTRL_PRESSED);
+ }
+ if (ctx.e.modifiers & (kSuffixCtrl | kSuffixShift)) {
+ char *q = p;
+ *q++ = '@';
+ setEncoding(ctx, q, extraKeyState | SHIFT_PRESSED | LEFT_CTRL_PRESSED);
+ }
+}
+
+template <bool is_numeric>
+static inline void expandEncodingAfterAltPrefix(
+ const ExpandContext &ctx, char *p, uint16_t extraKeyState) {
+ auto appendId = [&](char *&ptr) {
+ const auto idstr = decOfInt(ctx.e.id);
+ ASSERT(ptr <= ctx.bufferEnd - idstr.size());
+ std::copy(idstr.data(), idstr.data() + idstr.size(), ptr);
+ ptr += idstr.size();
+ };
+ ASSERT(p <= ctx.bufferEnd - 2);
+ *p++ = '\x1b';
+ *p++ = ctx.e.prefix;
+ if (ctx.e.modifiers & kBare) {
+ char *q = p;
+ if (is_numeric) {
+ appendId(q);
+ expandNumericEncodingSuffix(ctx, q, extraKeyState);
+ } else {
+ ASSERT(q <= ctx.bufferEnd - 1);
+ *q++ = ctx.e.id;
+ setEncoding(ctx, q, extraKeyState);
+ }
+ }
+ if (ctx.e.modifiers & kBareMod) {
+ ASSERT(!is_numeric && "kBareMod is invalid with numeric sequences");
+ for (int mod = 2; mod <= 8; ++mod) {
+ char *q = p;
+ ASSERT(q <= ctx.bufferEnd - 2);
+ *q++ = '0' + mod;
+ *q++ = ctx.e.id;
+ setEncoding(ctx, q, extraKeyState | keyStateForMod(mod));
+ }
+ }
+ if (ctx.e.modifiers & kSemiMod) {
+ for (int mod = 2; mod <= 8; ++mod) {
+ char *q = p;
+ if (is_numeric) {
+ appendId(q);
+ ASSERT(q <= ctx.bufferEnd - 2);
+ *q++ = ';';
+ *q++ = '0' + mod;
+ expandNumericEncodingSuffix(
+ ctx, q, extraKeyState | keyStateForMod(mod));
+ } else {
+ ASSERT(q <= ctx.bufferEnd - 4);
+ *q++ = '1';
+ *q++ = ';';
+ *q++ = '0' + mod;
+ *q++ = ctx.e.id;
+ setEncoding(ctx, q, extraKeyState | keyStateForMod(mod));
+ }
+ }
+ }
+}
+
+template <bool is_numeric>
+static inline void expandEncoding(const ExpandContext &ctx) {
+ if (ctx.e.alt_prefix_allowed) {
+ // For better or for worse, this code expands all of:
+ // * ESC [ <key> -- <key>
+ // * ESC ESC [ <key> -- Alt-<key>
+ // * ESC [ 1 ; 3 <key> -- Alt-<key>
+ // * ESC ESC [ 1 ; 3 <key> -- Alt-<key> specified twice
+ // I suspect no terminal actually emits the last one (i.e. specifying
+ // the Alt modifier using both methods), but I have seen a terminal
+ // that emitted a prefix ESC for Alt and a non-Alt modifier.
+ char *p = ctx.buffer;
+ ASSERT(p <= ctx.bufferEnd - 1);
+ *p++ = '\x1b';
+ expandEncodingAfterAltPrefix<is_numeric>(ctx, p, LEFT_ALT_PRESSED);
+ }
+ expandEncodingAfterAltPrefix<is_numeric>(ctx, ctx.buffer, 0);
+}
+
+template <bool is_numeric, size_t N>
+static void addEscapes(InputMap &inputMap, const EscapeEncoding (&encodings)[N]) {
+ char buffer[32];
+ for (size_t i = 0; i < DIM(encodings); ++i) {
+ ExpandContext ctx = {
+ inputMap, encodings[i],
+ buffer, buffer + sizeof(buffer)
+ };
+ expandEncoding<is_numeric>(ctx);
+ }
+}
+
+} // anonymous namespace
+
+void addDefaultEntriesToInputMap(InputMap &inputMap) {
+ addEscapes<false>(inputMap, escapeLetterEncodings);
+ addEscapes<true>(inputMap, escapeNumericEncodings);
+ addSimpleEntries(inputMap);
+}
diff --git a/src/libs/3rdparty/winpty/src/agent/DefaultInputMap.h b/src/libs/3rdparty/winpty/src/agent/DefaultInputMap.h
new file mode 100644
index 00000000000..c4b90836788
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/DefaultInputMap.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef DEFAULT_INPUT_MAP_H
+#define DEFAULT_INPUT_MAP_H
+
+class InputMap;
+
+void addDefaultEntriesToInputMap(InputMap &inputMap);
+
+#endif // DEFAULT_INPUT_MAP_H
diff --git a/src/libs/3rdparty/winpty/src/agent/DsrSender.h b/src/libs/3rdparty/winpty/src/agent/DsrSender.h
new file mode 100644
index 00000000000..1ec0a97d2e2
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/DsrSender.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2011-2012 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef DSRSENDER_H
+#define DSRSENDER_H
+
+class DsrSender
+{
+public:
+ virtual void sendDsr() = 0;
+};
+
+#endif // DSRSENDER_H
diff --git a/src/libs/3rdparty/winpty/src/agent/EventLoop.cc b/src/libs/3rdparty/winpty/src/agent/EventLoop.cc
new file mode 100644
index 00000000000..ba5cf18cc8a
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/EventLoop.cc
@@ -0,0 +1,99 @@
+// Copyright (c) 2011-2012 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include "EventLoop.h"
+
+#include <algorithm>
+
+#include "NamedPipe.h"
+#include "../shared/DebugClient.h"
+#include "../shared/WinptyAssert.h"
+
+EventLoop::~EventLoop() {
+ for (NamedPipe *pipe : m_pipes) {
+ delete pipe;
+ }
+ m_pipes.clear();
+}
+
+// Enter the event loop. Runs until the I/O or timeout handler calls exit().
+void EventLoop::run()
+{
+ std::vector<HANDLE> waitHandles;
+ DWORD lastTime = GetTickCount();
+ while (!m_exiting) {
+ bool didSomething = false;
+
+ // Attempt to make progress with the pipes.
+ waitHandles.clear();
+ for (size_t i = 0; i < m_pipes.size(); ++i) {
+ if (m_pipes[i]->serviceIo(&waitHandles)) {
+ onPipeIo(*m_pipes[i]);
+ didSomething = true;
+ }
+ }
+
+ // Call the timeout if enough time has elapsed.
+ if (m_pollInterval > 0) {
+ int elapsed = GetTickCount() - lastTime;
+ if (elapsed >= m_pollInterval) {
+ onPollTimeout();
+ lastTime = GetTickCount();
+ didSomething = true;
+ }
+ }
+
+ if (didSomething)
+ continue;
+
+ // If there's nothing to do, wait.
+ DWORD timeout = INFINITE;
+ if (m_pollInterval > 0)
+ timeout = std::max(0, (int)(lastTime + m_pollInterval - GetTickCount()));
+ if (waitHandles.size() == 0) {
+ ASSERT(timeout != INFINITE);
+ if (timeout > 0)
+ Sleep(timeout);
+ } else {
+ DWORD result = WaitForMultipleObjects(waitHandles.size(),
+ waitHandles.data(),
+ FALSE,
+ timeout);
+ ASSERT(result != WAIT_FAILED);
+ }
+ }
+}
+
+NamedPipe &EventLoop::createNamedPipe()
+{
+ NamedPipe *ret = new NamedPipe();
+ m_pipes.push_back(ret);
+ return *ret;
+}
+
+void EventLoop::setPollInterval(int ms)
+{
+ m_pollInterval = ms;
+}
+
+void EventLoop::shutdown()
+{
+ m_exiting = true;
+}
diff --git a/src/libs/3rdparty/winpty/src/agent/EventLoop.h b/src/libs/3rdparty/winpty/src/agent/EventLoop.h
new file mode 100644
index 00000000000..eddb0f62679
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/EventLoop.h
@@ -0,0 +1,47 @@
+// Copyright (c) 2011-2012 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef EVENTLOOP_H
+#define EVENTLOOP_H
+
+#include <vector>
+
+class NamedPipe;
+
+class EventLoop
+{
+public:
+ virtual ~EventLoop();
+ void run();
+
+protected:
+ NamedPipe &createNamedPipe();
+ void setPollInterval(int ms);
+ void shutdown();
+ virtual void onPollTimeout() {}
+ virtual void onPipeIo(NamedPipe &namedPipe) {}
+
+private:
+ bool m_exiting = false;
+ std::vector<NamedPipe*> m_pipes;
+ int m_pollInterval = 0;
+};
+
+#endif // EVENTLOOP_H
diff --git a/src/libs/3rdparty/winpty/src/agent/InputMap.cc b/src/libs/3rdparty/winpty/src/agent/InputMap.cc
new file mode 100644
index 00000000000..b1fbfc2e306
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/InputMap.cc
@@ -0,0 +1,246 @@
+// Copyright (c) 2011-2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include "InputMap.h"
+
+#include <windows.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "DebugShowInput.h"
+#include "SimplePool.h"
+#include "../shared/DebugClient.h"
+#include "../shared/UnixCtrlChars.h"
+#include "../shared/WinptyAssert.h"
+#include "../shared/winpty_snprintf.h"
+
+namespace {
+
+static const char *getVirtualKeyString(int virtualKey)
+{
+ switch (virtualKey) {
+#define WINPTY_GVKS_KEY(x) case VK_##x: return #x;
+ WINPTY_GVKS_KEY(RBUTTON) WINPTY_GVKS_KEY(F9)
+ WINPTY_GVKS_KEY(CANCEL) WINPTY_GVKS_KEY(F10)
+ WINPTY_GVKS_KEY(MBUTTON) WINPTY_GVKS_KEY(F11)
+ WINPTY_GVKS_KEY(XBUTTON1) WINPTY_GVKS_KEY(F12)
+ WINPTY_GVKS_KEY(XBUTTON2) WINPTY_GVKS_KEY(F13)
+ WINPTY_GVKS_KEY(BACK) WINPTY_GVKS_KEY(F14)
+ WINPTY_GVKS_KEY(TAB) WINPTY_GVKS_KEY(F15)
+ WINPTY_GVKS_KEY(CLEAR) WINPTY_GVKS_KEY(F16)
+ WINPTY_GVKS_KEY(RETURN) WINPTY_GVKS_KEY(F17)
+ WINPTY_GVKS_KEY(SHIFT) WINPTY_GVKS_KEY(F18)
+ WINPTY_GVKS_KEY(CONTROL) WINPTY_GVKS_KEY(F19)
+ WINPTY_GVKS_KEY(MENU) WINPTY_GVKS_KEY(F20)
+ WINPTY_GVKS_KEY(PAUSE) WINPTY_GVKS_KEY(F21)
+ WINPTY_GVKS_KEY(CAPITAL) WINPTY_GVKS_KEY(F22)
+ WINPTY_GVKS_KEY(HANGUL) WINPTY_GVKS_KEY(F23)
+ WINPTY_GVKS_KEY(JUNJA) WINPTY_GVKS_KEY(F24)
+ WINPTY_GVKS_KEY(FINAL) WINPTY_GVKS_KEY(NUMLOCK)
+ WINPTY_GVKS_KEY(KANJI) WINPTY_GVKS_KEY(SCROLL)
+ WINPTY_GVKS_KEY(ESCAPE) WINPTY_GVKS_KEY(LSHIFT)
+ WINPTY_GVKS_KEY(CONVERT) WINPTY_GVKS_KEY(RSHIFT)
+ WINPTY_GVKS_KEY(NONCONVERT) WINPTY_GVKS_KEY(LCONTROL)
+ WINPTY_GVKS_KEY(ACCEPT) WINPTY_GVKS_KEY(RCONTROL)
+ WINPTY_GVKS_KEY(MODECHANGE) WINPTY_GVKS_KEY(LMENU)
+ WINPTY_GVKS_KEY(SPACE) WINPTY_GVKS_KEY(RMENU)
+ WINPTY_GVKS_KEY(PRIOR) WINPTY_GVKS_KEY(BROWSER_BACK)
+ WINPTY_GVKS_KEY(NEXT) WINPTY_GVKS_KEY(BROWSER_FORWARD)
+ WINPTY_GVKS_KEY(END) WINPTY_GVKS_KEY(BROWSER_REFRESH)
+ WINPTY_GVKS_KEY(HOME) WINPTY_GVKS_KEY(BROWSER_STOP)
+ WINPTY_GVKS_KEY(LEFT) WINPTY_GVKS_KEY(BROWSER_SEARCH)
+ WINPTY_GVKS_KEY(UP) WINPTY_GVKS_KEY(BROWSER_FAVORITES)
+ WINPTY_GVKS_KEY(RIGHT) WINPTY_GVKS_KEY(BROWSER_HOME)
+ WINPTY_GVKS_KEY(DOWN) WINPTY_GVKS_KEY(VOLUME_MUTE)
+ WINPTY_GVKS_KEY(SELECT) WINPTY_GVKS_KEY(VOLUME_DOWN)
+ WINPTY_GVKS_KEY(PRINT) WINPTY_GVKS_KEY(VOLUME_UP)
+ WINPTY_GVKS_KEY(EXECUTE) WINPTY_GVKS_KEY(MEDIA_NEXT_TRACK)
+ WINPTY_GVKS_KEY(SNAPSHOT) WINPTY_GVKS_KEY(MEDIA_PREV_TRACK)
+ WINPTY_GVKS_KEY(INSERT) WINPTY_GVKS_KEY(MEDIA_STOP)
+ WINPTY_GVKS_KEY(DELETE) WINPTY_GVKS_KEY(MEDIA_PLAY_PAUSE)
+ WINPTY_GVKS_KEY(HELP) WINPTY_GVKS_KEY(LAUNCH_MAIL)
+ WINPTY_GVKS_KEY(LWIN) WINPTY_GVKS_KEY(LAUNCH_MEDIA_SELECT)
+ WINPTY_GVKS_KEY(RWIN) WINPTY_GVKS_KEY(LAUNCH_APP1)
+ WINPTY_GVKS_KEY(APPS) WINPTY_GVKS_KEY(LAUNCH_APP2)
+ WINPTY_GVKS_KEY(SLEEP) WINPTY_GVKS_KEY(OEM_1)
+ WINPTY_GVKS_KEY(NUMPAD0) WINPTY_GVKS_KEY(OEM_PLUS)
+ WINPTY_GVKS_KEY(NUMPAD1) WINPTY_GVKS_KEY(OEM_COMMA)
+ WINPTY_GVKS_KEY(NUMPAD2) WINPTY_GVKS_KEY(OEM_MINUS)
+ WINPTY_GVKS_KEY(NUMPAD3) WINPTY_GVKS_KEY(OEM_PERIOD)
+ WINPTY_GVKS_KEY(NUMPAD4) WINPTY_GVKS_KEY(OEM_2)
+ WINPTY_GVKS_KEY(NUMPAD5) WINPTY_GVKS_KEY(OEM_3)
+ WINPTY_GVKS_KEY(NUMPAD6) WINPTY_GVKS_KEY(OEM_4)
+ WINPTY_GVKS_KEY(NUMPAD7) WINPTY_GVKS_KEY(OEM_5)
+ WINPTY_GVKS_KEY(NUMPAD8) WINPTY_GVKS_KEY(OEM_6)
+ WINPTY_GVKS_KEY(NUMPAD9) WINPTY_GVKS_KEY(OEM_7)
+ WINPTY_GVKS_KEY(MULTIPLY) WINPTY_GVKS_KEY(OEM_8)
+ WINPTY_GVKS_KEY(ADD) WINPTY_GVKS_KEY(OEM_102)
+ WINPTY_GVKS_KEY(SEPARATOR) WINPTY_GVKS_KEY(PROCESSKEY)
+ WINPTY_GVKS_KEY(SUBTRACT) WINPTY_GVKS_KEY(PACKET)
+ WINPTY_GVKS_KEY(DECIMAL) WINPTY_GVKS_KEY(ATTN)
+ WINPTY_GVKS_KEY(DIVIDE) WINPTY_GVKS_KEY(CRSEL)
+ WINPTY_GVKS_KEY(F1) WINPTY_GVKS_KEY(EXSEL)
+ WINPTY_GVKS_KEY(F2) WINPTY_GVKS_KEY(EREOF)
+ WINPTY_GVKS_KEY(F3) WINPTY_GVKS_KEY(PLAY)
+ WINPTY_GVKS_KEY(F4) WINPTY_GVKS_KEY(ZOOM)
+ WINPTY_GVKS_KEY(F5) WINPTY_GVKS_KEY(NONAME)
+ WINPTY_GVKS_KEY(F6) WINPTY_GVKS_KEY(PA1)
+ WINPTY_GVKS_KEY(F7) WINPTY_GVKS_KEY(OEM_CLEAR)
+ WINPTY_GVKS_KEY(F8)
+#undef WINPTY_GVKS_KEY
+ default: return NULL;
+ }
+}
+
+} // anonymous namespace
+
+std::string InputMap::Key::toString() const {
+ std::string ret;
+ ret += controlKeyStatePrefix(keyState);
+ char buf[256];
+ const char *vkString = getVirtualKeyString(virtualKey);
+ if (vkString != NULL) {
+ ret += vkString;
+ } else if ((virtualKey >= 'A' && virtualKey <= 'Z') ||
+ (virtualKey >= '0' && virtualKey <= '9')) {
+ ret += static_cast<char>(virtualKey);
+ } else {
+ winpty_snprintf(buf, "%#x", virtualKey);
+ ret += buf;
+ }
+ if (unicodeChar >= 32 && unicodeChar <= 126) {
+ winpty_snprintf(buf, " ch='%c'",
+ static_cast<char>(unicodeChar));
+ } else {
+ winpty_snprintf(buf, " ch=%#x",
+ static_cast<unsigned int>(unicodeChar));
+ }
+ ret += buf;
+ return ret;
+}
+
+void InputMap::set(const char *encoding, int encodingLen, const Key &key) {
+ ASSERT(encodingLen > 0);
+ setHelper(m_root, encoding, encodingLen, key);
+}
+
+void InputMap::setHelper(Node &node, const char *encoding, int encodingLen, const Key &key) {
+ if (encodingLen == 0) {
+ node.key = key;
+ } else {
+ setHelper(getOrCreateChild(node, encoding[0]), encoding + 1, encodingLen - 1, key);
+ }
+}
+
+InputMap::Node &InputMap::getOrCreateChild(Node &node, unsigned char ch) {
+ Node *ret = getChild(node, ch);
+ if (ret != NULL) {
+ return *ret;
+ }
+ if (node.childCount < Node::kTinyCount) {
+ // Maintain sorted order for the sake of the InputMap dumping.
+ int insertIndex = node.childCount;
+ for (int i = 0; i < node.childCount; ++i) {
+ if (ch < node.u.tiny.values[i]) {
+ insertIndex = i;
+ break;
+ }
+ }
+ for (int j = node.childCount; j > insertIndex; --j) {
+ node.u.tiny.values[j] = node.u.tiny.values[j - 1];
+ node.u.tiny.children[j] = node.u.tiny.children[j - 1];
+ }
+ node.u.tiny.values[insertIndex] = ch;
+ node.u.tiny.children[insertIndex] = ret = m_nodePool.alloc();
+ ++node.childCount;
+ return *ret;
+ }
+ if (node.childCount == Node::kTinyCount) {
+ Branch *branch = m_branchPool.alloc();
+ for (int i = 0; i < node.childCount; ++i) {
+ branch->children[node.u.tiny.values[i]] = node.u.tiny.children[i];
+ }
+ node.u.branch = branch;
+ }
+ node.u.branch->children[ch] = ret = m_nodePool.alloc();
+ ++node.childCount;
+ return *ret;
+}
+
+// Find the longest matching key and node.
+int InputMap::lookupKey(const char *input, int inputSize,
+ Key &keyOut, bool &incompleteOut) const {
+ keyOut = kKeyZero;
+ incompleteOut = false;
+
+ const Node *node = &m_root;
+ InputMap::Key longestMatch = kKeyZero;
+ int longestMatchLen = 0;
+
+ for (int i = 0; i < inputSize; ++i) {
+ unsigned char ch = input[i];
+ node = getChild(*node, ch);
+ if (node == NULL) {
+ keyOut = longestMatch;
+ return longestMatchLen;
+ } else if (node->hasKey()) {
+ longestMatchLen = i + 1;
+ longestMatch = node->key;
+ }
+ }
+ keyOut = longestMatch;
+ incompleteOut = node->childCount > 0;
+ return longestMatchLen;
+}
+
+void InputMap::dumpInputMap() const {
+ std::string encoding;
+ dumpInputMapHelper(m_root, encoding);
+}
+
+void InputMap::dumpInputMapHelper(
+ const Node &node, std::string &encoding) const {
+ if (node.hasKey()) {
+ trace("%s -> %s",
+ encoding.c_str(),
+ node.key.toString().c_str());
+ }
+ for (int i = 0; i < 256; ++i) {
+ const Node *child = getChild(node, i);
+ if (child != NULL) {
+ size_t oldSize = encoding.size();
+ if (!encoding.empty()) {
+ encoding.push_back(' ');
+ }
+ char ctrlChar = decodeUnixCtrlChar(i);
+ if (ctrlChar != '\0') {
+ encoding.push_back('^');
+ encoding.push_back(static_cast<char>(ctrlChar));
+ } else if (i == ' ') {
+ encoding.append("' '");
+ } else {
+ encoding.push_back(static_cast<char>(i));
+ }
+ dumpInputMapHelper(*child, encoding);
+ encoding.resize(oldSize);
+ }
+ }
+}
diff --git a/src/libs/3rdparty/winpty/src/agent/InputMap.h b/src/libs/3rdparty/winpty/src/agent/InputMap.h
new file mode 100644
index 00000000000..9a666c79762
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/InputMap.h
@@ -0,0 +1,114 @@
+// Copyright (c) 2011-2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef INPUT_MAP_H
+#define INPUT_MAP_H
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <string>
+
+#include "SimplePool.h"
+#include "../shared/WinptyAssert.h"
+
+class InputMap {
+public:
+ struct Key {
+ uint16_t virtualKey;
+ uint32_t unicodeChar;
+ uint16_t keyState;
+
+ std::string toString() const;
+ };
+
+private:
+ struct Node;
+
+ struct Branch {
+ Branch() {
+ memset(&children, 0, sizeof(children));
+ }
+
+ Node *children[256];
+ };
+
+ struct Node {
+ Node() : childCount(0) {
+ Key zeroKey = { 0, 0, 0 };
+ key = zeroKey;
+ }
+
+ Key key;
+ int childCount;
+ enum { kTinyCount = 8 };
+ union {
+ Branch *branch;
+ struct {
+ unsigned char values[kTinyCount];
+ Node *children[kTinyCount];
+ } tiny;
+ } u;
+
+ bool hasKey() const {
+ return key.virtualKey != 0 || key.unicodeChar != 0;
+ }
+ };
+
+private:
+ SimplePool<Node, 256> m_nodePool;
+ SimplePool<Branch, 8> m_branchPool;
+ Node m_root;
+
+public:
+ void set(const char *encoding, int encodingLen, const Key &key);
+ int lookupKey(const char *input, int inputSize,
+ Key &keyOut, bool &incompleteOut) const;
+ void dumpInputMap() const;
+
+private:
+ Node *getChild(Node &node, unsigned char ch) {
+ return const_cast<Node*>(getChild(static_cast<const Node&>(node), ch));
+ }
+
+ const Node *getChild(const Node &node, unsigned char ch) const {
+ if (node.childCount <= Node::kTinyCount) {
+ for (int i = 0; i < node.childCount; ++i) {
+ if (node.u.tiny.values[i] == ch) {
+ return node.u.tiny.children[i];
+ }
+ }
+ return NULL;
+ } else {
+ return node.u.branch->children[ch];
+ }
+ }
+
+ void setHelper(Node &node, const char *encoding, int encodingLen, const Key &key);
+ Node &getOrCreateChild(Node &node, unsigned char ch);
+ void dumpInputMapHelper(const Node &node, std::string &encoding) const;
+};
+
+const InputMap::Key kKeyZero = { 0, 0, 0 };
+
+void dumpInputMap(InputMap &inputMap);
+
+#endif // INPUT_MAP_H
diff --git a/src/libs/3rdparty/winpty/src/agent/LargeConsoleRead.cc b/src/libs/3rdparty/winpty/src/agent/LargeConsoleRead.cc
new file mode 100644
index 00000000000..80ac640e488
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/LargeConsoleRead.cc
@@ -0,0 +1,71 @@
+// Copyright (c) 2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include "LargeConsoleRead.h"
+
+#include <stdlib.h>
+
+#include "../shared/WindowsVersion.h"
+#include "Scraper.h"
+#include "Win32ConsoleBuffer.h"
+
+LargeConsoleReadBuffer::LargeConsoleReadBuffer() :
+ m_rect(0, 0, 0, 0), m_rectWidth(0)
+{
+}
+
+void largeConsoleRead(LargeConsoleReadBuffer &out,
+ Win32ConsoleBuffer &buffer,
+ const SmallRect &readArea,
+ WORD attributesMask) {
+ ASSERT(readArea.Left >= 0 &&
+ readArea.Top >= 0 &&
+ readArea.Right >= readArea.Left &&
+ readArea.Bottom >= readArea.Top &&
+ readArea.width() <= MAX_CONSOLE_WIDTH);
+ const size_t count = readArea.width() * readArea.height();
+ if (out.m_data.size() < count) {
+ out.m_data.resize(count);
+ }
+ out.m_rect = readArea;
+ out.m_rectWidth = readArea.width();
+
+ static const bool useLargeReads = isAtLeastWindows8();
+ if (useLargeReads) {
+ buffer.read(readArea, out.m_data.data());
+ } else {
+ const int maxReadLines = std::max(1, MAX_CONSOLE_WIDTH / readArea.width());
+ int curLine = readArea.Top;
+ while (curLine <= readArea.Bottom) {
+ const SmallRect subReadArea(
+ readArea.Left,
+ curLine,
+ readArea.width(),
+ std::min(maxReadLines, readArea.Bottom + 1 - curLine));
+ buffer.read(subReadArea, out.lineDataMut(curLine));
+ curLine = subReadArea.Bottom + 1;
+ }
+ }
+ if (attributesMask != static_cast<WORD>(~0)) {
+ for (size_t i = 0; i < count; ++i) {
+ out.m_data[i].Attributes &= attributesMask;
+ }
+ }
+}
diff --git a/src/libs/3rdparty/winpty/src/agent/LargeConsoleRead.h b/src/libs/3rdparty/winpty/src/agent/LargeConsoleRead.h
new file mode 100644
index 00000000000..1bcf2c0232b
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/LargeConsoleRead.h
@@ -0,0 +1,68 @@
+// Copyright (c) 2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef LARGE_CONSOLE_READ_H
+#define LARGE_CONSOLE_READ_H
+
+#include <windows.h>
+#include <stdlib.h>
+
+#include <vector>
+
+#include "SmallRect.h"
+#include "../shared/DebugClient.h"
+#include "../shared/WinptyAssert.h"
+
+class Win32ConsoleBuffer;
+
+class LargeConsoleReadBuffer {
+public:
+ LargeConsoleReadBuffer();
+ const SmallRect &rect() const { return m_rect; }
+ const CHAR_INFO *lineData(int line) const {
+ validateLineNumber(line);
+ return &m_data[(line - m_rect.Top) * m_rectWidth];
+ }
+
+private:
+ CHAR_INFO *lineDataMut(int line) {
+ validateLineNumber(line);
+ return &m_data[(line - m_rect.Top) * m_rectWidth];
+ }
+
+ void validateLineNumber(int line) const {
+ if (line < m_rect.Top || line > m_rect.Bottom) {
+ trace("Fatal error: LargeConsoleReadBuffer: invalid line %d for "
+ "read rect %s", line, m_rect.toString().c_str());
+ abort();
+ }
+ }
+
+ SmallRect m_rect;
+ int m_rectWidth;
+ std::vector<CHAR_INFO> m_data;
+
+ friend void largeConsoleRead(LargeConsoleReadBuffer &out,
+ Win32ConsoleBuffer &buffer,
+ const SmallRect &readArea,
+ WORD attributesMask);
+};
+
+#endif // LARGE_CONSOLE_READ_H
diff --git a/src/libs/3rdparty/winpty/src/agent/NamedPipe.cc b/src/libs/3rdparty/winpty/src/agent/NamedPipe.cc
new file mode 100644
index 00000000000..64044e6e5d2
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/NamedPipe.cc
@@ -0,0 +1,378 @@
+// Copyright (c) 2011-2012 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include <string.h>
+
+#include <algorithm>
+
+#include "EventLoop.h"
+#include "NamedPipe.h"
+#include "../shared/DebugClient.h"
+#include "../shared/StringUtil.h"
+#include "../shared/WindowsSecurity.h"
+#include "../shared/WinptyAssert.h"
+
+// Returns true if anything happens (data received, data sent, pipe error).
+bool NamedPipe::serviceIo(std::vector<HANDLE> *waitHandles)
+{
+ bool justConnected = false;
+ const auto kError = ServiceResult::Error;
+ const auto kProgress = ServiceResult::Progress;
+ const auto kNoProgress = ServiceResult::NoProgress;
+ if (m_handle == NULL) {
+ return false;
+ }
+ if (m_connectEvent.get() != nullptr) {
+ // We're still connecting this server pipe. Check whether the pipe is
+ // now connected. If it isn't, add the pipe to the list of handles to
+ // wait on.
+ DWORD actual = 0;
+ BOOL success =
+ GetOverlappedResult(m_handle, &m_connectOver, &actual, FALSE);
+ if (!success && GetLastError() == ERROR_PIPE_CONNECTED) {
+ // I'm not sure this can happen, but it's easy to handle if it
+ // does.
+ success = TRUE;
+ }
+ if (!success) {
+ ASSERT(GetLastError() == ERROR_IO_INCOMPLETE &&
+ "Pended ConnectNamedPipe call failed");
+ waitHandles->push_back(m_connectEvent.get());
+ } else {
+ TRACE("Server pipe [%s] connected",
+ utf8FromWide(m_name).c_str());
+ m_connectEvent.dispose();
+ startPipeWorkers();
+ justConnected = true;
+ }
+ }
+ const auto readProgress = m_inputWorker ? m_inputWorker->service() : kNoProgress;
+ const auto writeProgress = m_outputWorker ? m_outputWorker->service() : kNoProgress;
+ if (readProgress == kError || writeProgress == kError) {
+ closePipe();
+ return true;
+ }
+ if (m_inputWorker && m_inputWorker->getWaitEvent() != nullptr) {
+ waitHandles->push_back(m_inputWorker->getWaitEvent());
+ }
+ if (m_outputWorker && m_outputWorker->getWaitEvent() != nullptr) {
+ waitHandles->push_back(m_outputWorker->getWaitEvent());
+ }
+ return justConnected
+ || readProgress == kProgress
+ || writeProgress == kProgress;
+}
+
+// manual reset, initially unset
+static OwnedHandle createEvent() {
+ HANDLE ret = CreateEventW(nullptr, TRUE, FALSE, nullptr);
+ ASSERT(ret != nullptr && "CreateEventW failed");
+ return OwnedHandle(ret);
+}
+
+NamedPipe::IoWorker::IoWorker(NamedPipe &namedPipe) :
+ m_namedPipe(namedPipe),
+ m_event(createEvent())
+{
+}
+
+NamedPipe::ServiceResult NamedPipe::IoWorker::service()
+{
+ ServiceResult progress = ServiceResult::NoProgress;
+ if (m_pending) {
+ DWORD actual = 0;
+ BOOL ret = GetOverlappedResult(m_namedPipe.m_handle, &m_over, &actual, FALSE);
+ if (!ret) {
+ if (GetLastError() == ERROR_IO_INCOMPLETE) {
+ // There is a pending I/O.
+ return progress;
+ } else {
+ // Pipe error.
+ return ServiceResult::Error;
+ }
+ }
+ ResetEvent(m_event.get());
+ m_pending = false;
+ completeIo(actual);
+ m_currentIoSize = 0;
+ progress = ServiceResult::Progress;
+ }
+ DWORD nextSize = 0;
+ bool isRead = false;
+ while (shouldIssueIo(&nextSize, &isRead)) {
+ m_currentIoSize = nextSize;
+ DWORD actual = 0;
+ memset(&m_over, 0, sizeof(m_over));
+ m_over.hEvent = m_event.get();
+ BOOL ret = isRead
+ ? ReadFile(m_namedPipe.m_handle, m_buffer, nextSize, &actual, &m_over)
+ : WriteFile(m_namedPipe.m_handle, m_buffer, nextSize, &actual, &m_over);
+ if (!ret) {
+ if (GetLastError() == ERROR_IO_PENDING) {
+ // There is a pending I/O.
+ m_pending = true;
+ return progress;
+ } else {
+ // Pipe error.
+ return ServiceResult::Error;
+ }
+ }
+ ResetEvent(m_event.get());
+ completeIo(actual);
+ m_currentIoSize = 0;
+ progress = ServiceResult::Progress;
+ }
+ return progress;
+}
+
+// This function is called after CancelIo has returned. We need to block until
+// the I/O operations have completed, which should happen very quickly.
+// https://blogs.msdn.microsoft.com/oldnewthing/20110202-00/?p=11613
+void NamedPipe::IoWorker::waitForCanceledIo()
+{
+ if (m_pending) {
+ DWORD actual = 0;
+ GetOverlappedResult(m_namedPipe.m_handle, &m_over, &actual, TRUE);
+ m_pending = false;
+ }
+}
+
+HANDLE NamedPipe::IoWorker::getWaitEvent()
+{
+ return m_pending ? m_event.get() : NULL;
+}
+
+void NamedPipe::InputWorker::completeIo(DWORD size)
+{
+ m_namedPipe.m_inQueue.append(m_buffer, size);
+}
+
+bool NamedPipe::InputWorker::shouldIssueIo(DWORD *size, bool *isRead)
+{
+ *isRead = true;
+ ASSERT(!m_namedPipe.isConnecting());
+ if (m_namedPipe.isClosed()) {
+ return false;
+ } else if (m_namedPipe.m_inQueue.size() < m_namedPipe.readBufferSize()) {
+ *size = kIoSize;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void NamedPipe::OutputWorker::completeIo(DWORD size)
+{
+ ASSERT(size == m_currentIoSize);
+}
+
+bool NamedPipe::OutputWorker::shouldIssueIo(DWORD *size, bool *isRead)
+{
+ *isRead = false;
+ if (!m_namedPipe.m_outQueue.empty()) {
+ auto &out = m_namedPipe.m_outQueue;
+ const DWORD writeSize = std::min<size_t>(out.size(), kIoSize);
+ std::copy(&out[0], &out[writeSize], m_buffer);
+ out.erase(0, writeSize);
+ *size = writeSize;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+DWORD NamedPipe::OutputWorker::getPendingIoSize()
+{
+ return m_pending ? m_currentIoSize : 0;
+}
+
+void NamedPipe::openServerPipe(LPCWSTR pipeName, OpenMode::t openMode,
+ int outBufferSize, int inBufferSize) {
+ ASSERT(isClosed());
+ ASSERT((openMode & OpenMode::Duplex) != 0);
+ const DWORD winOpenMode =
+ ((openMode & OpenMode::Reading) ? PIPE_ACCESS_INBOUND : 0)
+ | ((openMode & OpenMode::Writing) ? PIPE_ACCESS_OUTBOUND : 0)
+ | FILE_FLAG_FIRST_PIPE_INSTANCE
+ | FILE_FLAG_OVERLAPPED;
+ const auto sd = createPipeSecurityDescriptorOwnerFullControl();
+ ASSERT(sd && "error creating data pipe SECURITY_DESCRIPTOR");
+ SECURITY_ATTRIBUTES sa = {};
+ sa.nLength = sizeof(sa);
+ sa.lpSecurityDescriptor = sd.get();
+ HANDLE handle = CreateNamedPipeW(
+ pipeName,
+ /*dwOpenMode=*/winOpenMode,
+ /*dwPipeMode=*/rejectRemoteClientsPipeFlag(),
+ /*nMaxInstances=*/1,
+ /*nOutBufferSize=*/outBufferSize,
+ /*nInBufferSize=*/inBufferSize,
+ /*nDefaultTimeOut=*/30000,
+ &sa);
+ TRACE("opened server pipe [%s], handle == %p",
+ utf8FromWide(pipeName).c_str(), handle);
+ ASSERT(handle != INVALID_HANDLE_VALUE && "Could not open server pipe");
+ m_name = pipeName;
+ m_handle = handle;
+ m_openMode = openMode;
+
+ // Start an asynchronous connection attempt.
+ m_connectEvent = createEvent();
+ memset(&m_connectOver, 0, sizeof(m_connectOver));
+ m_connectOver.hEvent = m_connectEvent.get();
+ BOOL success = ConnectNamedPipe(m_handle, &m_connectOver);
+ const auto err = GetLastError();
+ if (!success && err == ERROR_PIPE_CONNECTED) {
+ success = TRUE;
+ }
+ if (success) {
+ TRACE("Server pipe [%s] connected", utf8FromWide(pipeName).c_str());
+ m_connectEvent.dispose();
+ startPipeWorkers();
+ } else if (err != ERROR_IO_PENDING) {
+ ASSERT(false && "ConnectNamedPipe call failed");
+ }
+}
+
+void NamedPipe::connectToServer(LPCWSTR pipeName, OpenMode::t openMode)
+{
+ ASSERT(isClosed());
+ ASSERT((openMode & OpenMode::Duplex) != 0);
+ HANDLE handle = CreateFileW(
+ pipeName,
+ GENERIC_READ | GENERIC_WRITE,
+ 0,
+ NULL,
+ OPEN_EXISTING,
+ SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION | FILE_FLAG_OVERLAPPED,
+ NULL);
+ TRACE("connected to [%s], handle == %p",
+ utf8FromWide(pipeName).c_str(), handle);
+ ASSERT(handle != INVALID_HANDLE_VALUE && "Could not connect to pipe");
+ m_name = pipeName;
+ m_handle = handle;
+ m_openMode = openMode;
+ startPipeWorkers();
+}
+
+void NamedPipe::startPipeWorkers()
+{
+ if (m_openMode & OpenMode::Reading) {
+ m_inputWorker.reset(new InputWorker(*this));
+ }
+ if (m_openMode & OpenMode::Writing) {
+ m_outputWorker.reset(new OutputWorker(*this));
+ }
+}
+
+size_t NamedPipe::bytesToSend()
+{
+ ASSERT(m_openMode & OpenMode::Writing);
+ auto ret = m_outQueue.size();
+ if (m_outputWorker != NULL) {
+ ret += m_outputWorker->getPendingIoSize();
+ }
+ return ret;
+}
+
+void NamedPipe::write(const void *data, size_t size)
+{
+ ASSERT(m_openMode & OpenMode::Writing);
+ m_outQueue.append(reinterpret_cast<const char*>(data), size);
+}
+
+void NamedPipe::write(const char *text)
+{
+ write(text, strlen(text));
+}
+
+size_t NamedPipe::readBufferSize()
+{
+ ASSERT(m_openMode & OpenMode::Reading);
+ return m_readBufferSize;
+}
+
+void NamedPipe::setReadBufferSize(size_t size)
+{
+ ASSERT(m_openMode & OpenMode::Reading);
+ m_readBufferSize = size;
+}
+
+size_t NamedPipe::bytesAvailable()
+{
+ ASSERT(m_openMode & OpenMode::Reading);
+ return m_inQueue.size();
+}
+
+size_t NamedPipe::peek(void *data, size_t size)
+{
+ ASSERT(m_openMode & OpenMode::Reading);
+ const auto out = reinterpret_cast<char*>(data);
+ const size_t ret = std::min(size, m_inQueue.size());
+ std::copy(&m_inQueue[0], &m_inQueue[ret], out);
+ return ret;
+}
+
+size_t NamedPipe::read(void *data, size_t size)
+{
+ size_t ret = peek(data, size);
+ m_inQueue.erase(0, ret);
+ return ret;
+}
+
+std::string NamedPipe::readToString(size_t size)
+{
+ ASSERT(m_openMode & OpenMode::Reading);
+ size_t retSize = std::min(size, m_inQueue.size());
+ std::string ret = m_inQueue.substr(0, retSize);
+ m_inQueue.erase(0, retSize);
+ return ret;
+}
+
+std::string NamedPipe::readAllToString()
+{
+ ASSERT(m_openMode & OpenMode::Reading);
+ std::string ret = m_inQueue;
+ m_inQueue.clear();
+ return ret;
+}
+
+void NamedPipe::closePipe()
+{
+ if (m_handle == NULL) {
+ return;
+ }
+ CancelIo(m_handle);
+ if (m_connectEvent.get() != nullptr) {
+ DWORD actual = 0;
+ GetOverlappedResult(m_handle, &m_connectOver, &actual, TRUE);
+ m_connectEvent.dispose();
+ }
+ if (m_inputWorker) {
+ m_inputWorker->waitForCanceledIo();
+ m_inputWorker.reset();
+ }
+ if (m_outputWorker) {
+ m_outputWorker->waitForCanceledIo();
+ m_outputWorker.reset();
+ }
+ CloseHandle(m_handle);
+ m_handle = NULL;
+}
diff --git a/src/libs/3rdparty/winpty/src/agent/NamedPipe.h b/src/libs/3rdparty/winpty/src/agent/NamedPipe.h
new file mode 100644
index 00000000000..0a4d8b0c75a
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/NamedPipe.h
@@ -0,0 +1,125 @@
+// Copyright (c) 2011-2012 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef NAMEDPIPE_H
+#define NAMEDPIPE_H
+
+#include <windows.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "../shared/OwnedHandle.h"
+
+class EventLoop;
+
+class NamedPipe
+{
+private:
+ // The EventLoop uses these private members.
+ friend class EventLoop;
+ NamedPipe() {}
+ ~NamedPipe() { closePipe(); }
+ bool serviceIo(std::vector<HANDLE> *waitHandles);
+ void startPipeWorkers();
+
+ enum class ServiceResult { NoProgress, Error, Progress };
+
+private:
+ class IoWorker
+ {
+ public:
+ IoWorker(NamedPipe &namedPipe);
+ virtual ~IoWorker() {}
+ ServiceResult service();
+ void waitForCanceledIo();
+ HANDLE getWaitEvent();
+ protected:
+ NamedPipe &m_namedPipe;
+ bool m_pending = false;
+ DWORD m_currentIoSize = 0;
+ OwnedHandle m_event;
+ OVERLAPPED m_over = {};
+ enum { kIoSize = 64 * 1024 };
+ char m_buffer[kIoSize];
+ virtual void completeIo(DWORD size) = 0;
+ virtual bool shouldIssueIo(DWORD *size, bool *isRead) = 0;
+ };
+
+ class InputWorker : public IoWorker
+ {
+ public:
+ InputWorker(NamedPipe &namedPipe) : IoWorker(namedPipe) {}
+ protected:
+ virtual void completeIo(DWORD size) override;
+ virtual bool shouldIssueIo(DWORD *size, bool *isRead) override;
+ };
+
+ class OutputWorker : public IoWorker
+ {
+ public:
+ OutputWorker(NamedPipe &namedPipe) : IoWorker(namedPipe) {}
+ DWORD getPendingIoSize();
+ protected:
+ virtual void completeIo(DWORD size) override;
+ virtual bool shouldIssueIo(DWORD *size, bool *isRead) override;
+ };
+
+public:
+ struct OpenMode {
+ typedef int t;
+ enum { None = 0, Reading = 1, Writing = 2, Duplex = 3 };
+ };
+
+ std::wstring name() const { return m_name; }
+ void openServerPipe(LPCWSTR pipeName, OpenMode::t openMode,
+ int outBufferSize, int inBufferSize);
+ void connectToServer(LPCWSTR pipeName, OpenMode::t openMode);
+ size_t bytesToSend();
+ void write(const void *data, size_t size);
+ void write(const char *text);
+ size_t readBufferSize();
+ void setReadBufferSize(size_t size);
+ size_t bytesAvailable();
+ size_t peek(void *data, size_t size);
+ size_t read(void *data, size_t size);
+ std::string readToString(size_t size);
+ std::string readAllToString();
+ void closePipe();
+ bool isClosed() { return m_handle == nullptr; }
+ bool isConnected() { return !isClosed() && !isConnecting(); }
+ bool isConnecting() { return m_connectEvent.get() != nullptr; }
+
+private:
+ // Input/output buffers
+ std::wstring m_name;
+ OVERLAPPED m_connectOver = {};
+ OwnedHandle m_connectEvent;
+ OpenMode::t m_openMode = OpenMode::None;
+ size_t m_readBufferSize = 64 * 1024;
+ std::string m_inQueue;
+ std::string m_outQueue;
+ HANDLE m_handle = nullptr;
+ std::unique_ptr<InputWorker> m_inputWorker;
+ std::unique_ptr<OutputWorker> m_outputWorker;
+};
+
+#endif // NAMEDPIPE_H
diff --git a/src/libs/3rdparty/winpty/src/agent/Scraper.cc b/src/libs/3rdparty/winpty/src/agent/Scraper.cc
new file mode 100644
index 00000000000..21f9c67104e
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/Scraper.cc
@@ -0,0 +1,699 @@
+// Copyright (c) 2011-2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include "Scraper.h"
+
+#include <windows.h>
+
+#include <stdint.h>
+
+#include <algorithm>
+#include <utility>
+
+#include "../shared/WinptyAssert.h"
+#include "../shared/winpty_snprintf.h"
+
+#include "ConsoleFont.h"
+#include "Win32Console.h"
+#include "Win32ConsoleBuffer.h"
+
+namespace {
+
+template <typename T>
+T constrained(T min, T val, T max) {
+ ASSERT(min <= max);
+ return std::min(std::max(min, val), max);
+}
+
+} // anonymous namespace
+
+Scraper::Scraper(
+ Win32Console &console,
+ Win32ConsoleBuffer &buffer,
+ std::unique_ptr<Terminal> terminal,
+ Coord initialSize) :
+ m_console(console),
+ m_terminal(std::move(terminal)),
+ m_ptySize(initialSize)
+{
+ m_consoleBuffer = &buffer;
+
+ resetConsoleTracking(Terminal::OmitClear, buffer.windowRect().top());
+
+ m_bufferData.resize(BUFFER_LINE_COUNT);
+
+ // Setup the initial screen buffer and window size.
+ //
+ // Use SetConsoleWindowInfo to shrink the console window as much as
+ // possible -- to a 1x1 cell at the top-left. This call always succeeds.
+ // Prior to the new Windows 10 console, it also actually resizes the GUI
+ // window to 1x1 cell. Nevertheless, even though the GUI window can
+ // therefore be narrower than its minimum, calling
+ // SetConsoleScreenBufferSize with a 1x1 size still fails.
+ //
+ // While the small font intends to support large buffers, a user could
+ // still hit a limit imposed by their monitor width, so cap the new window
+ // size to GetLargestConsoleWindowSize().
+ setSmallFont(buffer.conout(), initialSize.X, m_console.isNewW10());
+ buffer.moveWindow(SmallRect(0, 0, 1, 1));
+ buffer.resizeBufferRange(Coord(initialSize.X, BUFFER_LINE_COUNT));
+ const auto largest = GetLargestConsoleWindowSize(buffer.conout());
+ buffer.moveWindow(SmallRect(
+ 0, 0,
+ std::min(initialSize.X, largest.X),
+ std::min(initialSize.Y, largest.Y)));
+ buffer.setCursorPosition(Coord(0, 0));
+
+ // For the sake of the color translation heuristic, set the console color
+ // to LtGray-on-Black.
+ buffer.setTextAttribute(Win32ConsoleBuffer::kDefaultAttributes);
+ buffer.clearAllLines(m_consoleBuffer->bufferInfo());
+
+ m_consoleBuffer = nullptr;
+}
+
+Scraper::~Scraper()
+{
+}
+
+// Whether or not the agent is frozen on entry, it will be frozen on exit.
+void Scraper::resizeWindow(Win32ConsoleBuffer &buffer,
+ Coord newSize,
+ ConsoleScreenBufferInfo &finalInfoOut)
+{
+ m_consoleBuffer = &buffer;
+ m_ptySize = newSize;
+ syncConsoleContentAndSize(true, finalInfoOut);
+ m_consoleBuffer = nullptr;
+}
+
+// This function may freeze the agent, but it will not unfreeze it.
+void Scraper::scrapeBuffer(Win32ConsoleBuffer &buffer,
+ ConsoleScreenBufferInfo &finalInfoOut)
+{
+ m_consoleBuffer = &buffer;
+ syncConsoleContentAndSize(false, finalInfoOut);
+ m_consoleBuffer = nullptr;
+}
+
+void Scraper::resetConsoleTracking(
+ Terminal::SendClearFlag sendClear, int64_t scrapedLineCount)
+{
+ for (ConsoleLine &line : m_bufferData) {
+ line.reset();
+ }
+ m_syncRow = -1;
+ m_scrapedLineCount = scrapedLineCount;
+ m_scrolledCount = 0;
+ m_maxBufferedLine = -1;
+ m_dirtyWindowTop = -1;
+ m_dirtyLineCount = 0;
+ m_terminal->reset(sendClear, m_scrapedLineCount);
+}
+
+// Detect window movement. If the window moves down (presumably as a
+// result of scrolling), then assume that all screen buffer lines down to
+// the bottom of the window are dirty.
+void Scraper::markEntireWindowDirty(const SmallRect &windowRect)
+{
+ m_dirtyLineCount = std::max(m_dirtyLineCount,
+ windowRect.top() + windowRect.height());
+}
+
+// Scan the screen buffer and advance the dirty line count when we find
+// non-empty lines.
+void Scraper::scanForDirtyLines(const SmallRect &windowRect)
+{
+ const int w = m_readBuffer.rect().width();
+ ASSERT(m_dirtyLineCount >= 1);
+ const CHAR_INFO *const prevLine =
+ m_readBuffer.lineData(m_dirtyLineCount - 1);
+ WORD prevLineAttr = prevLine[w - 1].Attributes;
+ const int stopLine = windowRect.top() + windowRect.height();
+
+ for (int line = m_dirtyLineCount; line < stopLine; ++line) {
+ const CHAR_INFO *lineData = m_readBuffer.lineData(line);
+ for (int col = 0; col < w; ++col) {
+ const WORD colAttr = lineData[col].Attributes;
+ if (lineData[col].Char.UnicodeChar != L' ' ||
+ colAttr != prevLineAttr) {
+ m_dirtyLineCount = line + 1;
+ break;
+ }
+ }
+ prevLineAttr = lineData[w - 1].Attributes;
+ }
+}
+
+// Clear lines in the line buffer. The `firstRow` parameter is in
+// screen-buffer coordinates.
+void Scraper::clearBufferLines(
+ const int firstRow,
+ const int count)
+{
+ ASSERT(!m_directMode);
+ for (int row = firstRow; row < firstRow + count; ++row) {
+ const int64_t bufLine = row + m_scrolledCount;
+ m_maxBufferedLine = std::max(m_maxBufferedLine, bufLine);
+ m_bufferData[bufLine % BUFFER_LINE_COUNT].blank(
+ Win32ConsoleBuffer::kDefaultAttributes);
+ }
+}
+
+static bool cursorInWindow(const ConsoleScreenBufferInfo &info)
+{
+ return info.dwCursorPosition.Y >= info.srWindow.Top &&
+ info.dwCursorPosition.Y <= info.srWindow.Bottom;
+}
+
+void Scraper::resizeImpl(const ConsoleScreenBufferInfo &origInfo)
+{
+ ASSERT(m_console.frozen());
+ const int cols = m_ptySize.X;
+ const int rows = m_ptySize.Y;
+ Coord finalBufferSize;
+
+ {
+ //
+ // To accommodate Windows 10, erase all lines up to the top of the
+ // visible window. It's hard to tell whether this is strictly
+ // necessary. It ensures that the sync marker won't move downward,
+ // and it ensures that we won't repeat lines that have already scrolled
+ // up into the scrollback.
+ //
+ // It *is* possible for these blank lines to reappear in the visible
+ // window (e.g. if the window is made taller), but because we blanked
+ // the lines in the line buffer, we still don't output them again.
+ //
+ const Coord origBufferSize = origInfo.bufferSize();
+ const SmallRect origWindowRect = origInfo.windowRect();
+
+ if (m_directMode) {
+ for (ConsoleLine &line : m_bufferData) {
+ line.reset();
+ }
+ } else {
+ m_consoleBuffer->clearLines(0, origWindowRect.Top, origInfo);
+ clearBufferLines(0, origWindowRect.Top);
+ if (m_syncRow != -1) {
+ createSyncMarker(std::min(
+ m_syncRow,
+ BUFFER_LINE_COUNT - rows
+ - SYNC_MARKER_LEN
+ - SYNC_MARKER_MARGIN));
+ }
+ }
+
+ finalBufferSize = Coord(
+ cols,
+ // If there was previously no scrollback (e.g. a full-screen app
+ // in direct mode) and we're reducing the window height, then
+ // reduce the console buffer's height too.
+ (origWindowRect.height() == origBufferSize.Y)
+ ? rows
+ : std::max<int>(rows, origBufferSize.Y));
+
+ // Reset the console font size. We need to do this before shrinking
+ // the window, because we might need to make the font bigger to permit
+ // a smaller window width. Making the font smaller could expand the
+ // screen buffer, which would hang the conhost process in the
+ // Windows 10 (10240 build) if the console selection is in progress, so
+ // unfreeze it first.
+ m_console.setFrozen(false);
+ setSmallFont(m_consoleBuffer->conout(), cols, m_console.isNewW10());
+ }
+
+ // We try to make the font small enough so that the entire screen buffer
+ // fits on the monitor, but it can't be guaranteed.
+ const auto largest =
+ GetLargestConsoleWindowSize(m_consoleBuffer->conout());
+ const short visibleCols = std::min<short>(cols, largest.X);
+ const short visibleRows = std::min<short>(rows, largest.Y);
+
+ {
+ // Make the window small enough. We want the console frozen during
+ // this step so we don't accidentally move the window above the cursor.
+ m_console.setFrozen(true);
+ const auto info = m_consoleBuffer->bufferInfo();
+ const auto &bufferSize = info.dwSize;
+ const int tmpWindowWidth = std::min(bufferSize.X, visibleCols);
+ const int tmpWindowHeight = std::min(bufferSize.Y, visibleRows);
+ SmallRect tmpWindowRect(
+ 0,
+ std::min<int>(bufferSize.Y - tmpWindowHeight,
+ info.windowRect().Top),
+ tmpWindowWidth,
+ tmpWindowHeight);
+ if (cursorInWindow(info)) {
+ tmpWindowRect = tmpWindowRect.ensureLineIncluded(
+ info.cursorPosition().Y);
+ }
+ m_consoleBuffer->moveWindow(tmpWindowRect);
+ }
+
+ {
+ // Resize the buffer to the final desired size.
+ m_console.setFrozen(false);
+ m_consoleBuffer->resizeBufferRange(finalBufferSize);
+ }
+
+ {
+ // Expand the window to its full size.
+ m_console.setFrozen(true);
+ const ConsoleScreenBufferInfo info = m_consoleBuffer->bufferInfo();
+
+ SmallRect finalWindowRect(
+ 0,
+ std::min<int>(info.bufferSize().Y - visibleRows,
+ info.windowRect().Top),
+ visibleCols,
+ visibleRows);
+
+ //
+ // Once a line in the screen buffer is "dirty", it should stay visible
+ // in the console window, so that we continue to update its content in
+ // the terminal. This code is particularly (only?) necessary on
+ // Windows 10, where making the buffer wider can rewrap lines and move
+ // the console window upward.
+ //
+ if (!m_directMode && m_dirtyLineCount > finalWindowRect.Bottom + 1) {
+ // In theory, we avoid ensureLineIncluded, because, a massive
+ // amount of output could have occurred while the console was
+ // unfrozen, so that the *top* of the window is now below the
+ // dirtiest tracked line.
+ finalWindowRect = SmallRect(
+ 0, m_dirtyLineCount - visibleRows,
+ visibleCols, visibleRows);
+ }
+
+ // Highest priority constraint: ensure that the cursor remains visible.
+ if (cursorInWindow(info)) {
+ finalWindowRect = finalWindowRect.ensureLineIncluded(
+ info.cursorPosition().Y);
+ }
+
+ m_consoleBuffer->moveWindow(finalWindowRect);
+ m_dirtyWindowTop = finalWindowRect.Top;
+ }
+
+ ASSERT(m_console.frozen());
+}
+
+void Scraper::syncConsoleContentAndSize(
+ bool forceResize,
+ ConsoleScreenBufferInfo &finalInfoOut)
+{
+ // We'll try to avoid freezing the console by reading large chunks (or
+ // all!) of the screen buffer without otherwise attempting to synchronize
+ // with the console application. We can only do this on Windows 10 and up
+ // because:
+ // - Prior to Windows 8, the size of a ReadConsoleOutputW call was limited
+ // by the ~32KB RPC buffer.
+ // - Prior to Windows 10, an out-of-range read region crashes the caller.
+ // (See misc/WindowsBugCrashReader.cc.)
+ //
+ if (!m_console.isNewW10() || forceResize) {
+ m_console.setFrozen(true);
+ }
+
+ const ConsoleScreenBufferInfo info = m_consoleBuffer->bufferInfo();
+ bool cursorVisible = true;
+ CONSOLE_CURSOR_INFO cursorInfo = {};
+ if (!GetConsoleCursorInfo(GetStdHandle(STD_OUTPUT_HANDLE), &cursorInfo)) {
+ trace("GetConsoleCursorInfo failed");
+ } else {
+ cursorVisible = cursorInfo.bVisible != 0;
+ }
+
+ // If an app resizes the buffer height, then we enter "direct mode", where
+ // we stop trying to track incremental console changes.
+ const bool newDirectMode = (info.bufferSize().Y != BUFFER_LINE_COUNT);
+ if (newDirectMode != m_directMode) {
+ trace("Entering %s mode", newDirectMode ? "direct" : "scrolling");
+ resetConsoleTracking(Terminal::SendClear,
+ newDirectMode ? 0 : info.windowRect().top());
+ m_directMode = newDirectMode;
+
+ // When we switch from direct->scrolling mode, make sure the console is
+ // the right size.
+ if (!m_directMode) {
+ m_console.setFrozen(true);
+ forceResize = true;
+ }
+ }
+
+ if (m_directMode) {
+ // In direct-mode, resizing the console redraws the terminal, so do it
+ // before scraping.
+ if (forceResize) {
+ resizeImpl(info);
+ }
+ directScrapeOutput(info, cursorVisible);
+ } else {
+ if (!m_console.frozen()) {
+ if (!scrollingScrapeOutput(info, cursorVisible, true)) {
+ m_console.setFrozen(true);
+ }
+ }
+ if (m_console.frozen()) {
+ scrollingScrapeOutput(info, cursorVisible, false);
+ }
+ // In scrolling mode, we want to scrape before resizing, because we'll
+ // erase everything in the console buffer up to the top of the console
+ // window.
+ if (forceResize) {
+ resizeImpl(info);
+ }
+ }
+
+ finalInfoOut = forceResize ? m_consoleBuffer->bufferInfo() : info;
+}
+
+// Try to match Windows' behavior w.r.t. to the LVB attribute flags. In some
+// situations, Windows ignores the LVB flags on a character cell because of
+// backwards compatibility -- apparently some programs set the flags without
+// intending to enable reverse-video or underscores.
+//
+// [rprichard 2017-01-15] I haven't actually noticed any old programs that need
+// this treatment -- the motivation for this function comes from the MSDN
+// documentation for SetConsoleMode and ENABLE_LVB_GRID_WORLDWIDE.
+WORD Scraper::attributesMask()
+{
+ const auto WINPTY_ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4u;
+ const auto WINPTY_ENABLE_LVB_GRID_WORLDWIDE = 0x10u;
+ const auto WINPTY_COMMON_LVB_REVERSE_VIDEO = 0x4000u;
+ const auto WINPTY_COMMON_LVB_UNDERSCORE = 0x8000u;
+
+ const auto cp = GetConsoleOutputCP();
+ const auto isCjk = (cp == 932 || cp == 936 || cp == 949 || cp == 950);
+
+ const DWORD outputMode = [this]{
+ ASSERT(this->m_consoleBuffer != nullptr);
+ DWORD mode = 0;
+ if (!GetConsoleMode(this->m_consoleBuffer->conout(), &mode)) {
+ mode = 0;
+ }
+ return mode;
+ }();
+ const bool hasEnableLvbGridWorldwide =
+ (outputMode & WINPTY_ENABLE_LVB_GRID_WORLDWIDE) != 0;
+ const bool hasEnableVtProcessing =
+ (outputMode & WINPTY_ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0;
+
+ // The new Windows 10 console (as of 14393) seems to respect
+ // COMMON_LVB_REVERSE_VIDEO even in CP437 w/o the other enabling modes, so
+ // try to match that behavior.
+ const auto isReverseSupported =
+ isCjk || hasEnableLvbGridWorldwide || hasEnableVtProcessing || m_console.isNewW10();
+ const auto isUnderscoreSupported =
+ isCjk || hasEnableLvbGridWorldwide || hasEnableVtProcessing;
+
+ WORD mask = ~0;
+ if (!isReverseSupported) { mask &= ~WINPTY_COMMON_LVB_REVERSE_VIDEO; }
+ if (!isUnderscoreSupported) { mask &= ~WINPTY_COMMON_LVB_UNDERSCORE; }
+ return mask;
+}
+
+void Scraper::directScrapeOutput(const ConsoleScreenBufferInfo &info,
+ bool consoleCursorVisible)
+{
+ const SmallRect windowRect = info.windowRect();
+
+ const SmallRect scrapeRect(
+ windowRect.left(), windowRect.top(),
+ std::min<SHORT>(std::min(windowRect.width(), m_ptySize.X),
+ MAX_CONSOLE_WIDTH),
+ std::min<SHORT>(std::min(windowRect.height(), m_ptySize.Y),
+ BUFFER_LINE_COUNT));
+ const int w = scrapeRect.width();
+ const int h = scrapeRect.height();
+
+ const Coord cursor = info.cursorPosition();
+ const bool showTerminalCursor =
+ consoleCursorVisible && scrapeRect.contains(cursor);
+ const int cursorColumn = !showTerminalCursor ? -1 : cursor.X - scrapeRect.Left;
+ const int cursorLine = !showTerminalCursor ? -1 : cursor.Y - scrapeRect.Top;
+
+ if (!showTerminalCursor) {
+ m_terminal->hideTerminalCursor();
+ }
+
+ largeConsoleRead(m_readBuffer, *m_consoleBuffer, scrapeRect, attributesMask());
+
+ for (int line = 0; line < h; ++line) {
+ const CHAR_INFO *const curLine =
+ m_readBuffer.lineData(scrapeRect.top() + line);
+ ConsoleLine &bufLine = m_bufferData[line];
+ if (bufLine.detectChangeAndSetLine(curLine, w)) {
+ const int lineCursorColumn =
+ line == cursorLine ? cursorColumn : -1;
+ m_terminal->sendLine(line, curLine, w, lineCursorColumn);
+ }
+ }
+
+ if (showTerminalCursor) {
+ m_terminal->showTerminalCursor(cursorColumn, cursorLine);
+ }
+}
+
+bool Scraper::scrollingScrapeOutput(const ConsoleScreenBufferInfo &info,
+ bool consoleCursorVisible,
+ bool tentative)
+{
+ const Coord cursor = info.cursorPosition();
+ const SmallRect windowRect = info.windowRect();
+
+ if (m_syncRow != -1) {
+ // If a synchronizing marker was placed into the history, look for it
+ // and adjust the scroll count.
+ const int markerRow = findSyncMarker();
+ if (markerRow == -1) {
+ if (tentative) {
+ // I *think* it's possible to keep going, but it's simple to
+ // bail out.
+ return false;
+ }
+ // Something has happened. Reset the terminal.
+ trace("Sync marker has disappeared -- resetting the terminal"
+ " (m_syncCounter=%u)",
+ m_syncCounter);
+ resetConsoleTracking(Terminal::SendClear, windowRect.top());
+ } else if (markerRow != m_syncRow) {
+ ASSERT(markerRow < m_syncRow);
+ m_scrolledCount += (m_syncRow - markerRow);
+ m_syncRow = markerRow;
+ // If the buffer has scrolled, then the entire window is dirty.
+ markEntireWindowDirty(windowRect);
+ }
+ }
+
+ // Creating a new sync row requires clearing part of the console buffer, so
+ // avoid doing it if there's already a sync row that's good enough.
+ const int newSyncRow =
+ static_cast<int>(windowRect.top()) - SYNC_MARKER_LEN - SYNC_MARKER_MARGIN;
+ const bool shouldCreateSyncRow =
+ newSyncRow >= m_syncRow + SYNC_MARKER_LEN + SYNC_MARKER_MARGIN;
+ if (tentative && shouldCreateSyncRow) {
+ // It's difficult even in principle to put down a new marker if the
+ // console can scroll an arbitrarily amount while we're writing.
+ return false;
+ }
+
+ // Update the dirty line count:
+ // - If the window has moved, the entire window is dirty.
+ // - Everything up to the cursor is dirty.
+ // - All lines above the window are dirty.
+ // - Any non-blank lines are dirty.
+ if (m_dirtyWindowTop != -1) {
+ if (windowRect.top() > m_dirtyWindowTop) {
+ // The window has moved down, presumably as a result of scrolling.
+ markEntireWindowDirty(windowRect);
+ } else if (windowRect.top() < m_dirtyWindowTop) {
+ if (tentative) {
+ // I *think* it's possible to keep going, but it's simple to
+ // bail out.
+ return false;
+ }
+ // The window has moved upward. This is generally not expected to
+ // happen, but the CMD/PowerShell CLS command will move the window
+ // to the top as part of clearing everything else in the console.
+ trace("Window moved upward -- resetting the terminal"
+ " (m_syncCounter=%u)",
+ m_syncCounter);
+ resetConsoleTracking(Terminal::SendClear, windowRect.top());
+ }
+ }
+ m_dirtyWindowTop = windowRect.top();
+ m_dirtyLineCount = std::max(m_dirtyLineCount, cursor.Y + 1);
+ m_dirtyLineCount = std::max(m_dirtyLineCount, (int)windowRect.top());
+
+ // There will be at least one dirty line, because there is a cursor.
+ ASSERT(m_dirtyLineCount >= 1);
+
+ // The first line to scrape, in virtual line coordinates.
+ const int64_t firstVirtLine = std::min(m_scrapedLineCount,
+ windowRect.top() + m_scrolledCount);
+
+ // Read all the data we will need from the console. Start reading with the
+ // first line to scrape, but adjust the the read area upward to account for
+ // scanForDirtyLines' need to read the previous attribute. Read to the
+ // bottom of the window. (It's not clear to me whether the
+ // m_dirtyLineCount adjustment here is strictly necessary. It isn't
+ // necessary so long as the cursor is inside the current window.)
+ const int firstReadLine = std::min<int>(firstVirtLine - m_scrolledCount,
+ m_dirtyLineCount - 1);
+ const int stopReadLine = std::max(windowRect.top() + windowRect.height(),
+ m_dirtyLineCount);
+ ASSERT(firstReadLine >= 0 && stopReadLine > firstReadLine);
+ largeConsoleRead(m_readBuffer,
+ *m_consoleBuffer,
+ SmallRect(0, firstReadLine,
+ std::min<SHORT>(info.bufferSize().X,
+ MAX_CONSOLE_WIDTH),
+ stopReadLine - firstReadLine),
+ attributesMask());
+
+ // If we're scraping the buffer without freezing it, we have to query the
+ // buffer position data separately from the buffer content, so the two
+ // could easily be out-of-sync. If they *are* out-of-sync, abort the
+ // scrape operation and restart it frozen. (We may have updated the
+ // dirty-line high-water-mark, but that should be OK.)
+ if (tentative) {
+ const auto infoCheck = m_consoleBuffer->bufferInfo();
+ if (info.bufferSize() != infoCheck.bufferSize() ||
+ info.windowRect() != infoCheck.windowRect() ||
+ info.cursorPosition() != infoCheck.cursorPosition()) {
+ return false;
+ }
+ if (m_syncRow != -1 && m_syncRow != findSyncMarker()) {
+ return false;
+ }
+ }
+
+ if (shouldCreateSyncRow) {
+ ASSERT(!tentative);
+ createSyncMarker(newSyncRow);
+ }
+
+ // At this point, we're finished interacting (reading or writing) the
+ // console, and we just need to convert our collected data into terminal
+ // output.
+
+ scanForDirtyLines(windowRect);
+
+ // Note that it's possible for all the lines on the current window to
+ // be non-dirty.
+
+ // The line to stop scraping at, in virtual line coordinates.
+ const int64_t stopVirtLine =
+ std::min(m_dirtyLineCount, windowRect.top() + windowRect.height()) +
+ m_scrolledCount;
+
+ const bool showTerminalCursor =
+ consoleCursorVisible && windowRect.contains(cursor);
+ const int64_t cursorLine = !showTerminalCursor ? -1 : cursor.Y + m_scrolledCount;
+ const int cursorColumn = !showTerminalCursor ? -1 : cursor.X;
+
+ if (!showTerminalCursor) {
+ m_terminal->hideTerminalCursor();
+ }
+
+ bool sawModifiedLine = false;
+
+ const int w = m_readBuffer.rect().width();
+ for (int64_t line = firstVirtLine; line < stopVirtLine; ++line) {
+ const CHAR_INFO *curLine =
+ m_readBuffer.lineData(line - m_scrolledCount);
+ ConsoleLine &bufLine = m_bufferData[line % BUFFER_LINE_COUNT];
+ if (line > m_maxBufferedLine) {
+ m_maxBufferedLine = line;
+ sawModifiedLine = true;
+ }
+ if (sawModifiedLine) {
+ bufLine.setLine(curLine, w);
+ } else {
+ sawModifiedLine = bufLine.detectChangeAndSetLine(curLine, w);
+ }
+ if (sawModifiedLine) {
+ const int lineCursorColumn =
+ line == cursorLine ? cursorColumn : -1;
+ m_terminal->sendLine(line, curLine, w, lineCursorColumn);
+ }
+ }
+
+ m_scrapedLineCount = windowRect.top() + m_scrolledCount;
+
+ if (showTerminalCursor) {
+ m_terminal->showTerminalCursor(cursorColumn, cursorLine);
+ }
+
+ return true;
+}
+
+void Scraper::syncMarkerText(CHAR_INFO (&output)[SYNC_MARKER_LEN])
+{
+ // XXX: The marker text generated here could easily collide with ordinary
+ // console output. Does it make sense to try to avoid the collision?
+ char str[SYNC_MARKER_LEN + 1];
+ winpty_snprintf(str, "S*Y*N*C*%08x", m_syncCounter);
+ for (int i = 0; i < SYNC_MARKER_LEN; ++i) {
+ output[i].Char.UnicodeChar = str[i];
+ output[i].Attributes = 7;
+ }
+}
+
+int Scraper::findSyncMarker()
+{
+ ASSERT(m_syncRow >= 0);
+ CHAR_INFO marker[SYNC_MARKER_LEN];
+ CHAR_INFO column[BUFFER_LINE_COUNT];
+ syncMarkerText(marker);
+ SmallRect rect(0, 0, 1, m_syncRow + SYNC_MARKER_LEN);
+ m_consoleBuffer->read(rect, column);
+ int i;
+ for (i = m_syncRow; i >= 0; --i) {
+ int j;
+ for (j = 0; j < SYNC_MARKER_LEN; ++j) {
+ if (column[i + j].Char.UnicodeChar != marker[j].Char.UnicodeChar)
+ break;
+ }
+ if (j == SYNC_MARKER_LEN)
+ return i;
+ }
+ return -1;
+}
+
+void Scraper::createSyncMarker(int row)
+{
+ ASSERT(row >= 1);
+
+ // Clear the lines around the marker to ensure that Windows 10's rewrapping
+ // does not affect the marker.
+ m_consoleBuffer->clearLines(row - 1, SYNC_MARKER_LEN + 1,
+ m_consoleBuffer->bufferInfo());
+
+ // Write a new marker.
+ m_syncCounter++;
+ CHAR_INFO marker[SYNC_MARKER_LEN];
+ syncMarkerText(marker);
+ m_syncRow = row;
+ SmallRect markerRect(0, m_syncRow, 1, SYNC_MARKER_LEN);
+ m_consoleBuffer->write(markerRect, marker);
+}
diff --git a/src/libs/3rdparty/winpty/src/agent/Scraper.h b/src/libs/3rdparty/winpty/src/agent/Scraper.h
new file mode 100644
index 00000000000..9c10d80aedc
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/Scraper.h
@@ -0,0 +1,103 @@
+// Copyright (c) 2011-2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef AGENT_SCRAPER_H
+#define AGENT_SCRAPER_H
+
+#include <windows.h>
+
+#include <stdint.h>
+
+#include <memory>
+#include <vector>
+
+#include "ConsoleLine.h"
+#include "Coord.h"
+#include "LargeConsoleRead.h"
+#include "SmallRect.h"
+#include "Terminal.h"
+
+class ConsoleScreenBufferInfo;
+class Win32Console;
+class Win32ConsoleBuffer;
+
+// We must be able to issue a single ReadConsoleOutputW call of
+// MAX_CONSOLE_WIDTH characters, and a single read of approximately several
+// hundred fewer characters than BUFFER_LINE_COUNT.
+const int BUFFER_LINE_COUNT = 3000;
+const int MAX_CONSOLE_WIDTH = 2500;
+const int MAX_CONSOLE_HEIGHT = 2000;
+const int SYNC_MARKER_LEN = 16;
+const int SYNC_MARKER_MARGIN = 200;
+
+class Scraper {
+public:
+ Scraper(
+ Win32Console &console,
+ Win32ConsoleBuffer &buffer,
+ std::unique_ptr<Terminal> terminal,
+ Coord initialSize);
+ ~Scraper();
+ void resizeWindow(Win32ConsoleBuffer &buffer,
+ Coord newSize,
+ ConsoleScreenBufferInfo &finalInfoOut);
+ void scrapeBuffer(Win32ConsoleBuffer &buffer,
+ ConsoleScreenBufferInfo &finalInfoOut);
+ Terminal &terminal() { return *m_terminal; }
+
+private:
+ void resetConsoleTracking(
+ Terminal::SendClearFlag sendClear, int64_t scrapedLineCount);
+ void markEntireWindowDirty(const SmallRect &windowRect);
+ void scanForDirtyLines(const SmallRect &windowRect);
+ void clearBufferLines(int firstRow, int count);
+ void resizeImpl(const ConsoleScreenBufferInfo &origInfo);
+ void syncConsoleContentAndSize(bool forceResize,
+ ConsoleScreenBufferInfo &finalInfoOut);
+ WORD attributesMask();
+ void directScrapeOutput(const ConsoleScreenBufferInfo &info,
+ bool consoleCursorVisible);
+ bool scrollingScrapeOutput(const ConsoleScreenBufferInfo &info,
+ bool consoleCursorVisible,
+ bool tentative);
+ void syncMarkerText(CHAR_INFO (&output)[SYNC_MARKER_LEN]);
+ int findSyncMarker();
+ void createSyncMarker(int row);
+
+private:
+ Win32Console &m_console;
+ Win32ConsoleBuffer *m_consoleBuffer = nullptr;
+ std::unique_ptr<Terminal> m_terminal;
+
+ int m_syncRow = -1;
+ unsigned int m_syncCounter = 0;
+
+ bool m_directMode = false;
+ Coord m_ptySize;
+ int64_t m_scrapedLineCount = 0;
+ int64_t m_scrolledCount = 0;
+ int64_t m_maxBufferedLine = -1;
+ LargeConsoleReadBuffer m_readBuffer;
+ std::vector<ConsoleLine> m_bufferData;
+ int m_dirtyWindowTop = -1;
+ int m_dirtyLineCount = 0;
+};
+
+#endif // AGENT_SCRAPER_H
diff --git a/src/libs/3rdparty/winpty/src/agent/SimplePool.h b/src/libs/3rdparty/winpty/src/agent/SimplePool.h
new file mode 100644
index 00000000000..41ff94a90de
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/SimplePool.h
@@ -0,0 +1,75 @@
+// Copyright (c) 2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef SIMPLE_POOL_H
+#define SIMPLE_POOL_H
+
+#include <stdlib.h>
+
+#include <vector>
+
+#include "../shared/WinptyAssert.h"
+
+template <typename T, size_t chunkSize>
+class SimplePool {
+public:
+ ~SimplePool();
+ T *alloc();
+ void clear();
+private:
+ struct Chunk {
+ size_t count;
+ T *data;
+ };
+ std::vector<Chunk> m_chunks;
+};
+
+template <typename T, size_t chunkSize>
+SimplePool<T, chunkSize>::~SimplePool() {
+ clear();
+}
+
+template <typename T, size_t chunkSize>
+void SimplePool<T, chunkSize>::clear() {
+ for (size_t ci = 0; ci < m_chunks.size(); ++ci) {
+ Chunk &chunk = m_chunks[ci];
+ for (size_t ti = 0; ti < chunk.count; ++ti) {
+ chunk.data[ti].~T();
+ }
+ free(chunk.data);
+ }
+ m_chunks.clear();
+}
+
+template <typename T, size_t chunkSize>
+T *SimplePool<T, chunkSize>::alloc() {
+ if (m_chunks.empty() || m_chunks.back().count == chunkSize) {
+ T *newData = reinterpret_cast<T*>(malloc(sizeof(T) * chunkSize));
+ ASSERT(newData != NULL);
+ Chunk newChunk = { 0, newData };
+ m_chunks.push_back(newChunk);
+ }
+ Chunk &chunk = m_chunks.back();
+ T *ret = &chunk.data[chunk.count++];
+ new (ret) T();
+ return ret;
+}
+
+#endif // SIMPLE_POOL_H
diff --git a/src/libs/3rdparty/winpty/src/agent/SmallRect.h b/src/libs/3rdparty/winpty/src/agent/SmallRect.h
new file mode 100644
index 00000000000..bad0b88683a
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/SmallRect.h
@@ -0,0 +1,143 @@
+// Copyright (c) 2011-2012 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef SMALLRECT_H
+#define SMALLRECT_H
+
+#include <windows.h>
+
+#include <algorithm>
+#include <string>
+
+#include "../shared/winpty_snprintf.h"
+#include "Coord.h"
+
+struct SmallRect : SMALL_RECT
+{
+ SmallRect()
+ {
+ Left = Right = Top = Bottom = 0;
+ }
+
+ SmallRect(SHORT x, SHORT y, SHORT width, SHORT height)
+ {
+ Left = x;
+ Top = y;
+ Right = x + width - 1;
+ Bottom = y + height - 1;
+ }
+
+ SmallRect(const COORD &topLeft, const COORD &size)
+ {
+ Left = topLeft.X;
+ Top = topLeft.Y;
+ Right = Left + size.X - 1;
+ Bottom = Top + size.Y - 1;
+ }
+
+ SmallRect(const SMALL_RECT &other)
+ {
+ *(SMALL_RECT*)this = other;
+ }
+
+ SmallRect(const SmallRect &other)
+ {
+ *(SMALL_RECT*)this = *(const SMALL_RECT*)&other;
+ }
+
+ SmallRect &operator=(const SmallRect &other)
+ {
+ *(SMALL_RECT*)this = *(const SMALL_RECT*)&other;
+ return *this;
+ }
+
+ bool contains(const SmallRect &other) const
+ {
+ return other.Left >= Left &&
+ other.Right <= Right &&
+ other.Top >= Top &&
+ other.Bottom <= Bottom;
+ }
+
+ bool contains(const Coord &other) const
+ {
+ return other.X >= Left &&
+ other.X <= Right &&
+ other.Y >= Top &&
+ other.Y <= Bottom;
+ }
+
+ SmallRect intersected(const SmallRect &other) const
+ {
+ int x1 = std::max(Left, other.Left);
+ int x2 = std::min(Right, other.Right);
+ int y1 = std::max(Top, other.Top);
+ int y2 = std::min(Bottom, other.Bottom);
+ return SmallRect(x1,
+ y1,
+ std::max(0, x2 - x1 + 1),
+ std::max(0, y2 - y1 + 1));
+ }
+
+ SmallRect ensureLineIncluded(SHORT line) const
+ {
+ const SHORT h = height();
+ if (line < Top) {
+ return SmallRect(Left, line, width(), h);
+ } else if (line > Bottom) {
+ return SmallRect(Left, line - h + 1, width(), h);
+ } else {
+ return *this;
+ }
+ }
+
+ SHORT top() const { return Top; }
+ SHORT left() const { return Left; }
+ SHORT width() const { return Right - Left + 1; }
+ SHORT height() const { return Bottom - Top + 1; }
+ void setTop(SHORT top) { Top = top; }
+ void setLeft(SHORT left) { Left = left; }
+ void setWidth(SHORT width) { Right = Left + width - 1; }
+ void setHeight(SHORT height) { Bottom = Top + height - 1; }
+ Coord size() const { return Coord(width(), height()); }
+
+ bool operator==(const SmallRect &other) const
+ {
+ return Left == other.Left &&
+ Right == other.Right &&
+ Top == other.Top &&
+ Bottom == other.Bottom;
+ }
+
+ bool operator!=(const SmallRect &other) const
+ {
+ return !(*this == other);
+ }
+
+ std::string toString() const
+ {
+ char ret[64];
+ winpty_snprintf(ret, "(x=%d,y=%d,w=%d,h=%d)",
+ Left, Top, width(), height());
+ return std::string(ret);
+ }
+};
+
+#endif // SMALLRECT_H
diff --git a/src/libs/3rdparty/winpty/src/agent/Terminal.cc b/src/libs/3rdparty/winpty/src/agent/Terminal.cc
new file mode 100644
index 00000000000..afa0a362600
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/Terminal.cc
@@ -0,0 +1,535 @@
+// Copyright (c) 2011-2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include "Terminal.h"
+
+#include <windows.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <string>
+
+#include "NamedPipe.h"
+#include "UnicodeEncoding.h"
+#include "../shared/DebugClient.h"
+#include "../shared/WinptyAssert.h"
+#include "../shared/winpty_snprintf.h"
+
+#define CSI "\x1b["
+
+// Work around the old MinGW, which lacks COMMON_LVB_LEADING_BYTE and
+// COMMON_LVB_TRAILING_BYTE.
+const int WINPTY_COMMON_LVB_LEADING_BYTE = 0x100;
+const int WINPTY_COMMON_LVB_TRAILING_BYTE = 0x200;
+const int WINPTY_COMMON_LVB_REVERSE_VIDEO = 0x4000;
+const int WINPTY_COMMON_LVB_UNDERSCORE = 0x8000;
+
+const int COLOR_ATTRIBUTE_MASK =
+ FOREGROUND_BLUE |
+ FOREGROUND_GREEN |
+ FOREGROUND_RED |
+ FOREGROUND_INTENSITY |
+ BACKGROUND_BLUE |
+ BACKGROUND_GREEN |
+ BACKGROUND_RED |
+ BACKGROUND_INTENSITY |
+ WINPTY_COMMON_LVB_REVERSE_VIDEO |
+ WINPTY_COMMON_LVB_UNDERSCORE;
+
+const int FLAG_RED = 1;
+const int FLAG_GREEN = 2;
+const int FLAG_BLUE = 4;
+const int FLAG_BRIGHT = 8;
+
+const int BLACK = 0;
+const int DKGRAY = BLACK | FLAG_BRIGHT;
+const int LTGRAY = FLAG_RED | FLAG_GREEN | FLAG_BLUE;
+const int WHITE = LTGRAY | FLAG_BRIGHT;
+
+// SGR parameters (Select Graphic Rendition)
+const int SGR_FORE = 30;
+const int SGR_FORE_HI = 90;
+const int SGR_BACK = 40;
+const int SGR_BACK_HI = 100;
+
+namespace {
+
+static void outUInt(std::string &out, unsigned int n)
+{
+ char buf[32];
+ char *pbuf = &buf[32];
+ *(--pbuf) = '\0';
+ do {
+ *(--pbuf) = '0' + n % 10;
+ n /= 10;
+ } while (n != 0);
+ out.append(pbuf);
+}
+
+static void outputSetColorSgrParams(std::string &out, bool isFore, int color)
+{
+ out.push_back(';');
+ const int sgrBase = isFore ? SGR_FORE : SGR_BACK;
+ if (color & FLAG_BRIGHT) {
+ // Some terminals don't support the 9X/10X "intensive" color parameters
+ // (e.g. the Eclipse TM terminal as of this writing). Those terminals
+ // will quietly ignore a 9X/10X code, and the other terminals will
+ // ignore a 3X/4X code if it's followed by a 9X/10X code. Therefore,
+ // output a 3X/4X code as a fallback, then override it.
+ const int colorBase = color & ~FLAG_BRIGHT;
+ outUInt(out, sgrBase + colorBase);
+ out.push_back(';');
+ outUInt(out, sgrBase + (SGR_FORE_HI - SGR_FORE) + colorBase);
+ } else {
+ outUInt(out, sgrBase + color);
+ }
+}
+
+static void outputSetColor(std::string &out, int color)
+{
+ int fore = 0;
+ int back = 0;
+ if (color & FOREGROUND_RED) fore |= FLAG_RED;
+ if (color & FOREGROUND_GREEN) fore |= FLAG_GREEN;
+ if (color & FOREGROUND_BLUE) fore |= FLAG_BLUE;
+ if (color & FOREGROUND_INTENSITY) fore |= FLAG_BRIGHT;
+ if (color & BACKGROUND_RED) back |= FLAG_RED;
+ if (color & BACKGROUND_GREEN) back |= FLAG_GREEN;
+ if (color & BACKGROUND_BLUE) back |= FLAG_BLUE;
+ if (color & BACKGROUND_INTENSITY) back |= FLAG_BRIGHT;
+
+ if (color & WINPTY_COMMON_LVB_REVERSE_VIDEO) {
+ // n.b.: The COMMON_LVB_REVERSE_VIDEO flag also swaps
+ // FOREGROUND_INTENSITY and BACKGROUND_INTENSITY. Tested on
+ // Windows 10 v14393.
+ std::swap(fore, back);
+ }
+
+ // Translate the fore/back colors into terminal escape codes using
+ // a heuristic that works OK with common white-on-black or
+ // black-on-white color schemes. We don't know which color scheme
+ // the terminal is using. It is ugly to force white-on-black text
+ // on a black-on-white terminal, and it's even ugly to force the
+ // matching scheme. It's probably relevant that the default
+ // fore/back terminal colors frequently do not match any of the 16
+ // palette colors.
+
+ // Typical default terminal color schemes (according to palette,
+ // when possible):
+ // - mintty: LtGray-on-Black(A)
+ // - putty: LtGray-on-Black(A)
+ // - xterm: LtGray-on-Black(A)
+ // - Konsole: LtGray-on-Black(A)
+ // - JediTerm/JetBrains: Black-on-White(B)
+ // - rxvt: Black-on-White(B)
+
+ // If the background is the default color (black), then it will
+ // map to Black(A) or White(B). If we translate White to White,
+ // then a Black background and a White background in the console
+ // are both White with (B). Therefore, we should translate White
+ // using SGR 7 (Invert). The typical finished mapping table for
+ // background grayscale colors is:
+ //
+ // (A) White => LtGray(fore)
+ // (A) Black => Black(back)
+ // (A) LtGray => LtGray
+ // (A) DkGray => DkGray
+ //
+ // (B) White => Black(fore)
+ // (B) Black => White(back)
+ // (B) LtGray => LtGray
+ // (B) DkGray => DkGray
+ //
+
+ out.append(CSI "0");
+ if (back == BLACK) {
+ if (fore == LTGRAY) {
+ // The "default" foreground color. Use the terminal's
+ // default colors.
+ } else if (fore == WHITE) {
+ // Sending the literal color white would behave poorly if
+ // the terminal were black-on-white. Sending Bold is not
+ // guaranteed to alter the color, but it will make the text
+ // visually distinct, so do that instead.
+ out.append(";1");
+ } else if (fore == DKGRAY) {
+ // Set the foreground color to DkGray(90) with a fallback
+ // of LtGray(37) for terminals that don't handle the 9X SGR
+ // parameters (e.g. Eclipse's TM Terminal as of this
+ // writing).
+ out.append(";37;90");
+ } else {
+ outputSetColorSgrParams(out, true, fore);
+ }
+ } else if (back == WHITE) {
+ // Set the background color using Invert on the default
+ // foreground color, and set the foreground color by setting a
+ // background color.
+
+ // Use the terminal's inverted colors.
+ out.append(";7");
+ if (fore == LTGRAY || fore == BLACK) {
+ // We're likely mapping Console White to terminal LtGray or
+ // Black. If they are the Console foreground color, then
+ // don't set a terminal foreground color to avoid creating
+ // invisible text.
+ } else {
+ outputSetColorSgrParams(out, false, fore);
+ }
+ } else {
+ // Set the foreground and background to match exactly that in
+ // the Windows console.
+ outputSetColorSgrParams(out, true, fore);
+ outputSetColorSgrParams(out, false, back);
+ }
+ if (fore == back) {
+ // The foreground and background colors are exactly equal, so
+ // attempt to hide the text using the Conceal SGR parameter,
+ // which some terminals support.
+ out.append(";8");
+ }
+ if (color & WINPTY_COMMON_LVB_UNDERSCORE) {
+ out.append(";4");
+ }
+ out.push_back('m');
+}
+
+static inline unsigned int fixSpecialCharacters(unsigned int ch)
+{
+ if (ch <= 0x1b) {
+ switch (ch) {
+ // The Windows Console has a popup window (e.g. that appears with
+ // F7) that is sometimes bordered with box-drawing characters.
+ // With the Japanese and Korean system locales (CP932 and CP949),
+ // the UnicodeChar values for the box-drawing characters are 1
+ // through 6. Detect this and map the values to the correct
+ // Unicode values.
+ //
+ // N.B. In the English locale, the UnicodeChar values are correct,
+ // and they identify single-line characters rather than
+ // double-line. In the Chinese Simplified and Traditional locales,
+ // the popups use ASCII characters instead.
+ case 1: return 0x2554; // BOX DRAWINGS DOUBLE DOWN AND RIGHT
+ case 2: return 0x2557; // BOX DRAWINGS DOUBLE DOWN AND LEFT
+ case 3: return 0x255A; // BOX DRAWINGS DOUBLE UP AND RIGHT
+ case 4: return 0x255D; // BOX DRAWINGS DOUBLE UP AND LEFT
+ case 5: return 0x2551; // BOX DRAWINGS DOUBLE VERTICAL
+ case 6: return 0x2550; // BOX DRAWINGS DOUBLE HORIZONTAL
+
+ // Convert an escape character to some other character. This
+ // conversion only applies to console cells containing an escape
+ // character. In newer versions of Windows 10 (e.g. 10.0.10586),
+ // the non-legacy console recognizes escape sequences in
+ // WriteConsole and interprets them without writing them to the
+ // cells of the screen buffer. In that case, the conversion here
+ // does not apply.
+ case 0x1b: return '?';
+ }
+ }
+ return ch;
+}
+
+static inline bool isFullWidthCharacter(const CHAR_INFO *data, int width)
+{
+ if (width < 2) {
+ return false;
+ }
+ return
+ (data[0].Attributes & WINPTY_COMMON_LVB_LEADING_BYTE) &&
+ (data[1].Attributes & WINPTY_COMMON_LVB_TRAILING_BYTE) &&
+ data[0].Char.UnicodeChar == data[1].Char.UnicodeChar;
+}
+
+// Scan to find a single Unicode Scalar Value. Full-width characters occupy
+// two console cells, and this code also tries to handle UTF-16 surrogate
+// pairs.
+//
+// Windows expands at least some wide characters outside the Basic
+// Multilingual Plane into four cells, such as U+20000:
+// 1. 0xD840, attr=0x107
+// 2. 0xD840, attr=0x207
+// 3. 0xDC00, attr=0x107
+// 4. 0xDC00, attr=0x207
+// Even in the Traditional Chinese locale on Windows 10, this text is rendered
+// as two boxes, but if those boxes are copied-and-pasted, the character is
+// copied correctly.
+static inline void scanUnicodeScalarValue(
+ const CHAR_INFO *data, int width,
+ int &outCellCount, unsigned int &outCharValue)
+{
+ ASSERT(width >= 1);
+
+ const int w1 = isFullWidthCharacter(data, width) ? 2 : 1;
+ const wchar_t c1 = data[0].Char.UnicodeChar;
+
+ if ((c1 & 0xF800) == 0xD800) {
+ // The first cell is either a leading or trailing surrogate pair.
+ if ((c1 & 0xFC00) != 0xD800 ||
+ width <= w1 ||
+ ((data[w1].Char.UnicodeChar & 0xFC00) != 0xDC00)) {
+ // Invalid surrogate pair
+ outCellCount = w1;
+ outCharValue = '?';
+ } else {
+ // Valid surrogate pair
+ outCellCount = w1 + (isFullWidthCharacter(&data[w1], width - w1) ? 2 : 1);
+ outCharValue = decodeSurrogatePair(c1, data[w1].Char.UnicodeChar);
+ }
+ } else {
+ outCellCount = w1;
+ outCharValue = c1;
+ }
+}
+
+} // anonymous namespace
+
+void Terminal::reset(SendClearFlag sendClearFirst, int64_t newLine)
+{
+ if (sendClearFirst == SendClear && !m_plainMode) {
+ // 0m ==> reset SGR parameters
+ // 1;1H ==> move cursor to top-left position
+ // 2J ==> clear the entire screen
+ m_output.write(CSI "0m" CSI "1;1H" CSI "2J");
+ }
+ m_remoteLine = newLine;
+ m_remoteColumn = 0;
+ m_lineData.clear();
+ m_cursorHidden = false;
+ m_remoteColor = -1;
+}
+
+void Terminal::sendLine(int64_t line, const CHAR_INFO *lineData, int width,
+ int cursorColumn)
+{
+ ASSERT(width >= 1);
+
+ moveTerminalToLine(line);
+
+ // If possible, see if we can append to what we've already output for this
+ // line.
+ if (m_lineDataValid) {
+ ASSERT(m_lineData.size() == static_cast<size_t>(m_remoteColumn));
+ if (m_remoteColumn > 0) {
+ // In normal mode, if m_lineData.size() equals `width`, then we
+ // will have trouble outputing the "erase rest of line" command,
+ // which must be output before reaching the end of the line. In
+ // plain mode, we don't output that command, so we're OK with a
+ // full line.
+ bool okWidth = false;
+ if (m_plainMode) {
+ okWidth = static_cast<size_t>(width) >= m_lineData.size();
+ } else {
+ okWidth = static_cast<size_t>(width) > m_lineData.size();
+ }
+ if (!okWidth ||
+ memcmp(m_lineData.data(), lineData,
+ sizeof(CHAR_INFO) * m_lineData.size()) != 0) {
+ m_lineDataValid = false;
+ }
+ }
+ }
+ if (!m_lineDataValid) {
+ // We can't reuse, so we must reset this line.
+ hideTerminalCursor();
+ if (m_plainMode) {
+ // We can't backtrack, so repeat this line.
+ m_output.write("\r\n");
+ } else {
+ m_output.write("\r");
+ }
+ m_lineDataValid = true;
+ m_lineData.clear();
+ m_remoteColumn = 0;
+ }
+
+ std::string &termLine = m_termLineWorkingBuffer;
+ termLine.clear();
+ size_t trimmedLineLength = 0;
+ int trimmedCellCount = m_lineData.size();
+ bool alreadyErasedLine = false;
+
+ int cellCount = 1;
+ for (int i = m_lineData.size(); i < width; i += cellCount) {
+ if (m_outputColor) {
+ int color = lineData[i].Attributes & COLOR_ATTRIBUTE_MASK;
+ if (color != m_remoteColor) {
+ outputSetColor(termLine, color);
+ trimmedLineLength = termLine.size();
+ m_remoteColor = color;
+
+ // All the cells just up to this color change will be output.
+ trimmedCellCount = i;
+ }
+ }
+ unsigned int ch;
+ scanUnicodeScalarValue(&lineData[i], width - i, cellCount, ch);
+ if (ch == ' ') {
+ // Tentatively add this space character. We'll only output it if
+ // we see something interesting after it.
+ termLine.push_back(' ');
+ } else {
+ if (i + cellCount == width) {
+ // We'd like to erase the line after outputting all non-blank
+ // characters, but this doesn't work if the last cell in the
+ // line is non-blank. At the point, the cursor is positioned
+ // just past the end of the line, but in many terminals,
+ // issuing a CSI 0K at that point also erases the last cell in
+ // the line. Work around this behavior by issuing the erase
+ // one character early in that case.
+ if (!m_plainMode) {
+ termLine.append(CSI "0K"); // Erase from cursor to EOL
+ }
+ alreadyErasedLine = true;
+ }
+ ch = fixSpecialCharacters(ch);
+ char enc[4];
+ int enclen = encodeUtf8(enc, ch);
+ if (enclen == 0) {
+ enc[0] = '?';
+ enclen = 1;
+ }
+ termLine.append(enc, enclen);
+ trimmedLineLength = termLine.size();
+
+ // All the cells up to and including this cell will be output.
+ trimmedCellCount = i + cellCount;
+ }
+ }
+
+ if (cursorColumn != -1 && trimmedCellCount > cursorColumn) {
+ // The line content would run past the cursor, so hide it before we
+ // output.
+ hideTerminalCursor();
+ }
+
+ m_output.write(termLine.data(), trimmedLineLength);
+ if (!alreadyErasedLine && !m_plainMode) {
+ m_output.write(CSI "0K"); // Erase from cursor to EOL
+ }
+
+ ASSERT(trimmedCellCount <= width);
+ m_lineData.insert(m_lineData.end(),
+ &lineData[m_lineData.size()],
+ &lineData[trimmedCellCount]);
+ m_remoteColumn = trimmedCellCount;
+}
+
+void Terminal::showTerminalCursor(int column, int64_t line)
+{
+ moveTerminalToLine(line);
+ if (!m_plainMode) {
+ if (m_remoteColumn != column) {
+ char buffer[32];
+ winpty_snprintf(buffer, CSI "%dG", column + 1);
+ m_output.write(buffer);
+ m_lineDataValid = (column == 0);
+ m_lineData.clear();
+ m_remoteColumn = column;
+ }
+ if (m_cursorHidden) {
+ m_output.write(CSI "?25h");
+ m_cursorHidden = false;
+ }
+ }
+}
+
+void Terminal::hideTerminalCursor()
+{
+ if (!m_plainMode) {
+ if (m_cursorHidden) {
+ return;
+ }
+ m_output.write(CSI "?25l");
+ m_cursorHidden = true;
+ }
+}
+
+void Terminal::moveTerminalToLine(int64_t line)
+{
+ if (line == m_remoteLine) {
+ return;
+ }
+
+ // Do not use CPL or CNL. Konsole 2.5.4 does not support Cursor Previous
+ // Line (CPL) -- there are "Undecodable sequence" errors. gnome-terminal
+ // 2.32.0 does handle it. Cursor Next Line (CNL) does nothing if the
+ // cursor is on the last line already.
+
+ hideTerminalCursor();
+
+ if (line < m_remoteLine) {
+ if (m_plainMode) {
+ // We can't backtrack, so instead repeat the lines again.
+ m_output.write("\r\n");
+ m_remoteLine = line;
+ } else {
+ // Backtrack and overwrite previous lines.
+ // CUrsor Up (CUU)
+ char buffer[32];
+ winpty_snprintf(buffer, "\r" CSI "%uA",
+ static_cast<unsigned int>(m_remoteLine - line));
+ m_output.write(buffer);
+ m_remoteLine = line;
+ }
+ } else if (line > m_remoteLine) {
+ while (line > m_remoteLine) {
+ m_output.write("\r\n");
+ m_remoteLine++;
+ }
+ }
+
+ m_lineDataValid = true;
+ m_lineData.clear();
+ m_remoteColumn = 0;
+}
+
+void Terminal::enableMouseMode(bool enabled)
+{
+ if (m_mouseModeEnabled == enabled || m_plainMode) {
+ return;
+ }
+ m_mouseModeEnabled = enabled;
+ if (enabled) {
+ // Start by disabling UTF-8 coordinate mode (1005), just in case we
+ // have a terminal that does not support 1006/1015 modes, and 1005
+ // happens to be enabled. The UTF-8 coordinates can't be unambiguously
+ // decoded.
+ //
+ // Enable basic mouse support first (1000), then try to switch to
+ // button-move mode (1002), then try full mouse-move mode (1003).
+ // Terminals that don't support a mode will be stuck at the highest
+ // mode they do support.
+ //
+ // Enable encoding mode 1015 first, then try to switch to 1006. On
+ // some terminals, both modes will be enabled, but 1006 will have
+ // priority. On other terminals, 1006 wins because it's listed last.
+ //
+ // See misc/MouseInputNotes.txt for details.
+ m_output.write(
+ CSI "?1005l"
+ CSI "?1000h" CSI "?1002h" CSI "?1003h" CSI "?1015h" CSI "?1006h");
+ } else {
+ // Resetting both encoding modes (1006 and 1015) is necessary, but
+ // apparently we only need to use reset on one of the 100[023] modes.
+ // Doing both doesn't hurt.
+ m_output.write(
+ CSI "?1006l" CSI "?1015l" CSI "?1003l" CSI "?1002l" CSI "?1000l");
+ }
+}
diff --git a/src/libs/3rdparty/winpty/src/agent/Terminal.h b/src/libs/3rdparty/winpty/src/agent/Terminal.h
new file mode 100644
index 00000000000..058eb2650e7
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/Terminal.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2011-2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef TERMINAL_H
+#define TERMINAL_H
+
+#include <windows.h>
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "Coord.h"
+
+class NamedPipe;
+
+class Terminal
+{
+public:
+ explicit Terminal(NamedPipe &output, bool plainMode, bool outputColor)
+ : m_output(output), m_plainMode(plainMode), m_outputColor(outputColor)
+ {
+ }
+
+ enum SendClearFlag { OmitClear, SendClear };
+ void reset(SendClearFlag sendClearFirst, int64_t newLine);
+ void sendLine(int64_t line, const CHAR_INFO *lineData, int width,
+ int cursorColumn);
+ void showTerminalCursor(int column, int64_t line);
+ void hideTerminalCursor();
+
+private:
+ void moveTerminalToLine(int64_t line);
+
+public:
+ void enableMouseMode(bool enabled);
+
+private:
+ NamedPipe &m_output;
+ int64_t m_remoteLine = 0;
+ int m_remoteColumn = 0;
+ bool m_lineDataValid = true;
+ std::vector<CHAR_INFO> m_lineData;
+ bool m_cursorHidden = false;
+ int m_remoteColor = -1;
+ std::string m_termLineWorkingBuffer;
+ bool m_plainMode = false;
+ bool m_outputColor = true;
+ bool m_mouseModeEnabled = false;
+};
+
+#endif // TERMINAL_H
diff --git a/src/libs/3rdparty/winpty/src/agent/UnicodeEncoding.h b/src/libs/3rdparty/winpty/src/agent/UnicodeEncoding.h
new file mode 100644
index 00000000000..6b0de3eff9f
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/UnicodeEncoding.h
@@ -0,0 +1,157 @@
+// Copyright (c) 2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef UNICODE_ENCODING_H
+#define UNICODE_ENCODING_H
+
+#include <stdint.h>
+
+// Encode the Unicode codepoint with UTF-8. The buffer must be at least 4
+// bytes in size.
+static inline int encodeUtf8(char *out, uint32_t code) {
+ if (code < 0x80) {
+ out[0] = code;
+ return 1;
+ } else if (code < 0x800) {
+ out[0] = ((code >> 6) & 0x1F) | 0xC0;
+ out[1] = ((code >> 0) & 0x3F) | 0x80;
+ return 2;
+ } else if (code < 0x10000) {
+ if (code >= 0xD800 && code <= 0xDFFF) {
+ // The code points 0xD800 to 0xDFFF are reserved for UTF-16
+ // surrogate pairs and do not have an encoding in UTF-8.
+ return 0;
+ }
+ out[0] = ((code >> 12) & 0x0F) | 0xE0;
+ out[1] = ((code >> 6) & 0x3F) | 0x80;
+ out[2] = ((code >> 0) & 0x3F) | 0x80;
+ return 3;
+ } else if (code < 0x110000) {
+ out[0] = ((code >> 18) & 0x07) | 0xF0;
+ out[1] = ((code >> 12) & 0x3F) | 0x80;
+ out[2] = ((code >> 6) & 0x3F) | 0x80;
+ out[3] = ((code >> 0) & 0x3F) | 0x80;
+ return 4;
+ } else {
+ // Encoding error
+ return 0;
+ }
+}
+
+// Encode the Unicode codepoint with UTF-16. The buffer must be large enough
+// to hold the output -- either 1 or 2 elements.
+static inline int encodeUtf16(wchar_t *out, uint32_t code) {
+ if (code < 0x10000) {
+ if (code >= 0xD800 && code <= 0xDFFF) {
+ // The code points 0xD800 to 0xDFFF are reserved for UTF-16
+ // surrogate pairs and do not have an encoding in UTF-16.
+ return 0;
+ }
+ out[0] = code;
+ return 1;
+ } else if (code < 0x110000) {
+ code -= 0x10000;
+ out[0] = 0xD800 | (code >> 10);
+ out[1] = 0xDC00 | (code & 0x3FF);
+ return 2;
+ } else {
+ // Encoding error
+ return 0;
+ }
+}
+
+// Return the byte size of a UTF-8 character using the value of the first
+// byte.
+static inline int utf8CharLength(char firstByte) {
+ // This code would probably be faster if it used __builtin_clz.
+ if ((firstByte & 0x80) == 0) {
+ return 1;
+ } else if ((firstByte & 0xE0) == 0xC0) {
+ return 2;
+ } else if ((firstByte & 0xF0) == 0xE0) {
+ return 3;
+ } else if ((firstByte & 0xF8) == 0xF0) {
+ return 4;
+ } else {
+ // Malformed UTF-8.
+ return 0;
+ }
+}
+
+// The pointer must point to 1-4 bytes, as indicated by the first byte.
+// Returns -1 on decoding error.
+static inline uint32_t decodeUtf8(const char *in) {
+ const uint32_t kInvalid = static_cast<uint32_t>(-1);
+ switch (utf8CharLength(in[0])) {
+ case 1: {
+ return in[0];
+ }
+ case 2: {
+ if ((in[1] & 0xC0) != 0x80) {
+ return kInvalid;
+ }
+ uint32_t tmp = 0;
+ tmp = (in[0] & 0x1F) << 6;
+ tmp |= (in[1] & 0x3F);
+ return tmp <= 0x7F ? kInvalid : tmp;
+ }
+ case 3: {
+ if ((in[1] & 0xC0) != 0x80 ||
+ (in[2] & 0xC0) != 0x80) {
+ return kInvalid;
+ }
+ uint32_t tmp = 0;
+ tmp = (in[0] & 0x0F) << 12;
+ tmp |= (in[1] & 0x3F) << 6;
+ tmp |= (in[2] & 0x3F);
+ if (tmp <= 0x07FF || (tmp >= 0xD800 && tmp <= 0xDFFF)) {
+ return kInvalid;
+ } else {
+ return tmp;
+ }
+ }
+ case 4: {
+ if ((in[1] & 0xC0) != 0x80 ||
+ (in[2] & 0xC0) != 0x80 ||
+ (in[3] & 0xC0) != 0x80) {
+ return kInvalid;
+ }
+ uint32_t tmp = 0;
+ tmp = (in[0] & 0x07) << 18;
+ tmp |= (in[1] & 0x3F) << 12;
+ tmp |= (in[2] & 0x3F) << 6;
+ tmp |= (in[3] & 0x3F);
+ if (tmp <= 0xFFFF || tmp > 0x10FFFF) {
+ return kInvalid;
+ } else {
+ return tmp;
+ }
+ }
+ default: {
+ return kInvalid;
+ }
+ }
+}
+
+static inline uint32_t decodeSurrogatePair(wchar_t ch1, wchar_t ch2) {
+ return ((ch1 - 0xD800) << 10) + (ch2 - 0xDC00) + 0x10000;
+}
+
+#endif // UNICODE_ENCODING_H
diff --git a/src/libs/3rdparty/winpty/src/agent/UnicodeEncodingTest.cc b/src/libs/3rdparty/winpty/src/agent/UnicodeEncodingTest.cc
new file mode 100644
index 00000000000..cd4abeb1916
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/UnicodeEncodingTest.cc
@@ -0,0 +1,189 @@
+// Copyright (c) 2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+// Encode every code-point using this module and verify that it matches the
+// encoding generated using Windows WideCharToMultiByte.
+
+#include "UnicodeEncoding.h"
+
+#include <windows.h>
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+static void correctnessByCode()
+{
+ char mbstr1[4];
+ char mbstr2[4];
+ wchar_t wch[2];
+ for (unsigned int code = 0; code < 0x110000; ++code) {
+
+ // Surrogate pair reserved region.
+ const bool isReserved = (code >= 0xD800 && code <= 0xDFFF);
+
+ int mblen1 = encodeUtf8(mbstr1, code);
+ if (isReserved ? mblen1 != 0 : mblen1 <= 0) {
+ printf("Error: 0x%04X: mblen1=%d\n", code, mblen1);
+ continue;
+ }
+
+ int wlen = encodeUtf16(wch, code);
+ if (isReserved ? wlen != 0 : wlen <= 0) {
+ printf("Error: 0x%04X: wlen=%d\n", code, wlen);
+ continue;
+ }
+
+ if (isReserved) {
+ continue;
+ }
+
+ if (mblen1 != utf8CharLength(mbstr1[0])) {
+ printf("Error: 0x%04X: mblen1=%d, utf8CharLength(mbstr1[0])=%d\n",
+ code, mblen1, utf8CharLength(mbstr1[0]));
+ continue;
+ }
+
+ if (code != decodeUtf8(mbstr1)) {
+ printf("Error: 0x%04X: decodeUtf8(mbstr1)=%u\n",
+ code, decodeUtf8(mbstr1));
+ continue;
+ }
+
+ int mblen2 = WideCharToMultiByte(CP_UTF8, 0, wch, wlen, mbstr2, 4, NULL, NULL);
+ if (mblen1 != mblen2) {
+ printf("Error: 0x%04X: mblen1=%d, mblen2=%d\n", code, mblen1, mblen2);
+ continue;
+ }
+
+ if (memcmp(mbstr1, mbstr2, mblen1) != 0) {
+ printf("Error: 0x%04x: encodings are different\n", code);
+ continue;
+ }
+ }
+}
+
+static const char *encodingStr(char (&output)[128], char (&buf)[4])
+{
+ sprintf(output, "Encoding %02X %02X %02X %02X",
+ static_cast<uint8_t>(buf[0]),
+ static_cast<uint8_t>(buf[1]),
+ static_cast<uint8_t>(buf[2]),
+ static_cast<uint8_t>(buf[3]));
+ return output;
+}
+
+// This test can take a couple of minutes to run.
+static void correctnessByUtf8Encoding()
+{
+ for (uint64_t encoding = 0; encoding <= 0xFFFFFFFF; ++encoding) {
+
+ char mb[4];
+ mb[0] = encoding;
+ mb[1] = encoding >> 8;
+ mb[2] = encoding >> 16;
+ mb[3] = encoding >> 24;
+
+ const int mblen = utf8CharLength(mb[0]);
+ if (mblen == 0) {
+ continue;
+ }
+
+ // Test this module.
+ const uint32_t code1 = decodeUtf8(mb);
+ wchar_t ws1[2] = {};
+ const int wslen1 = encodeUtf16(ws1, code1);
+
+ // Test using Windows. We can't decode a codepoint directly; we have
+ // to do UTF8->UTF16, then decode the surrogate pair.
+ wchar_t ws2[2] = {};
+ const int wslen2 = MultiByteToWideChar(
+ CP_UTF8, MB_ERR_INVALID_CHARS, mb, mblen, ws2, 2);
+ const uint32_t code2 =
+ (wslen2 == 1 ? ws2[0] :
+ wslen2 == 2 ? decodeSurrogatePair(ws2[0], ws2[1]) :
+ static_cast<uint32_t>(-1));
+
+ // Verify that the two implementations match.
+ char prefix[128];
+ if (code1 != code2) {
+ printf("%s: code1=0x%04x code2=0x%04x\n",
+ encodingStr(prefix, mb),
+ code1, code2);
+ continue;
+ }
+ if (wslen1 != wslen2) {
+ printf("%s: wslen1=%d wslen2=%d\n",
+ encodingStr(prefix, mb),
+ wslen1, wslen2);
+ continue;
+ }
+ if (memcmp(ws1, ws2, wslen1 * sizeof(wchar_t)) != 0) {
+ printf("%s: ws1 != ws2\n", encodingStr(prefix, mb));
+ continue;
+ }
+ }
+}
+
+wchar_t g_wch_TEST[] = { 0xD840, 0xDC00 };
+char g_ch_TEST[4];
+wchar_t *volatile g_pwch = g_wch_TEST;
+char *volatile g_pch = g_ch_TEST;
+unsigned int volatile g_code = 0xA2000;
+
+static void performance()
+{
+ {
+ clock_t start = clock();
+ for (long long i = 0; i < 250000000LL; ++i) {
+ int mblen = WideCharToMultiByte(CP_UTF8, 0, g_pwch, 2, g_pch, 4, NULL, NULL);
+ assert(mblen == 4);
+ }
+ clock_t stop = clock();
+ printf("%.3fns per char\n", (double)(stop - start) / CLOCKS_PER_SEC * 4.0);
+ }
+
+ {
+ clock_t start = clock();
+ for (long long i = 0; i < 3000000000LL; ++i) {
+ int mblen = encodeUtf8(g_pch, g_code);
+ assert(mblen == 4);
+ }
+ clock_t stop = clock();
+ printf("%.3fns per char\n", (double)(stop - start) / CLOCKS_PER_SEC / 3.0);
+ }
+}
+
+int main()
+{
+ printf("Testing correctnessByCode...\n");
+ fflush(stdout);
+ correctnessByCode();
+
+ printf("Testing correctnessByUtf8Encoding... (may take a couple minutes)\n");
+ fflush(stdout);
+ correctnessByUtf8Encoding();
+
+ printf("Testing performance...\n");
+ fflush(stdout);
+ performance();
+
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/src/agent/Win32Console.cc b/src/libs/3rdparty/winpty/src/agent/Win32Console.cc
new file mode 100644
index 00000000000..d53de021f5a
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/Win32Console.cc
@@ -0,0 +1,107 @@
+// Copyright (c) 2011-2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include "Win32Console.h"
+
+#include <windows.h>
+#include <wchar.h>
+
+#include <string>
+
+#include "../shared/DebugClient.h"
+#include "../shared/WinptyAssert.h"
+
+Win32Console::Win32Console() : m_titleWorkBuf(16)
+{
+ // The console window must be non-NULL. It is used for two purposes:
+ // (1) "Freezing" the console to detect the exact number of lines that
+ // have scrolled.
+ // (2) Killing processes attached to the console, by posting a WM_CLOSE
+ // message to the console window.
+ m_hwnd = GetConsoleWindow();
+ ASSERT(m_hwnd != nullptr);
+}
+
+std::wstring Win32Console::title()
+{
+ while (true) {
+ // Calling GetConsoleTitleW is tricky, because its behavior changed
+ // from XP->Vista, then again from Win7->Win8. The Vista+Win7 behavior
+ // is especially broken.
+ //
+ // The MSDN documentation documents nSize as the "size of the buffer
+ // pointed to by the lpConsoleTitle parameter, in characters" and the
+ // successful return value as "the length of the console window's
+ // title, in characters."
+ //
+ // On XP, the function returns the title length, AFTER truncation
+ // (excluding the NUL terminator). If the title is blank, the API
+ // returns 0 and does not NUL-terminate the buffer. To accommodate
+ // XP, the function must:
+ // * Terminate the buffer itself.
+ // * Double the size of the title buffer in a loop.
+ //
+ // On Vista and up, the function returns the non-truncated title
+ // length (excluding the NUL terminator).
+ //
+ // On Vista and Windows 7, there is a bug where the buffer size is
+ // interpreted as a byte count rather than a wchar_t count. To
+ // work around this, we must pass GetConsoleTitleW a buffer that is
+ // twice as large as what is actually needed.
+ //
+ // See misc/*/Test_GetConsoleTitleW.cc for tests demonstrating Windows'
+ // behavior.
+
+ DWORD count = GetConsoleTitleW(m_titleWorkBuf.data(),
+ m_titleWorkBuf.size());
+ const size_t needed = (count + 1) * sizeof(wchar_t);
+ if (m_titleWorkBuf.size() < needed) {
+ m_titleWorkBuf.resize(needed);
+ continue;
+ }
+ m_titleWorkBuf[count] = L'\0';
+ return m_titleWorkBuf.data();
+ }
+}
+
+void Win32Console::setTitle(const std::wstring &title)
+{
+ if (!SetConsoleTitleW(title.c_str())) {
+ trace("SetConsoleTitleW failed");
+ }
+}
+
+void Win32Console::setFrozen(bool frozen) {
+ const int SC_CONSOLE_MARK = 0xFFF2;
+ const int SC_CONSOLE_SELECT_ALL = 0xFFF5;
+ if (frozen == m_frozen) {
+ // Do nothing.
+ } else if (frozen) {
+ // Enter selection mode by activating either Mark or SelectAll.
+ const int command = m_freezeUsesMark ? SC_CONSOLE_MARK
+ : SC_CONSOLE_SELECT_ALL;
+ SendMessage(m_hwnd, WM_SYSCOMMAND, command, 0);
+ m_frozen = true;
+ } else {
+ // Send Escape to cancel the selection.
+ SendMessage(m_hwnd, WM_CHAR, 27, 0x00010001);
+ m_frozen = false;
+ }
+}
diff --git a/src/libs/3rdparty/winpty/src/agent/Win32Console.h b/src/libs/3rdparty/winpty/src/agent/Win32Console.h
new file mode 100644
index 00000000000..ed83877e993
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/Win32Console.h
@@ -0,0 +1,67 @@
+// Copyright (c) 2011-2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef AGENT_WIN32_CONSOLE_H
+#define AGENT_WIN32_CONSOLE_H
+
+#include <windows.h>
+
+#include <string>
+#include <vector>
+
+class Win32Console
+{
+public:
+ class FreezeGuard {
+ public:
+ FreezeGuard(Win32Console &console, bool frozen) :
+ m_console(console), m_previous(console.frozen()) {
+ m_console.setFrozen(frozen);
+ }
+ ~FreezeGuard() {
+ m_console.setFrozen(m_previous);
+ }
+ FreezeGuard(const FreezeGuard &other) = delete;
+ FreezeGuard &operator=(const FreezeGuard &other) = delete;
+ private:
+ Win32Console &m_console;
+ bool m_previous;
+ };
+
+ Win32Console();
+
+ HWND hwnd() { return m_hwnd; }
+ std::wstring title();
+ void setTitle(const std::wstring &title);
+ void setFreezeUsesMark(bool useMark) { m_freezeUsesMark = useMark; }
+ void setNewW10(bool isNewW10) { m_isNewW10 = isNewW10; }
+ bool isNewW10() { return m_isNewW10; }
+ void setFrozen(bool frozen=true);
+ bool frozen() { return m_frozen; }
+
+private:
+ HWND m_hwnd = nullptr;
+ bool m_frozen = false;
+ bool m_freezeUsesMark = false;
+ bool m_isNewW10 = false;
+ std::vector<wchar_t> m_titleWorkBuf;
+};
+
+#endif // AGENT_WIN32_CONSOLE_H
diff --git a/src/libs/3rdparty/winpty/src/agent/Win32ConsoleBuffer.cc b/src/libs/3rdparty/winpty/src/agent/Win32ConsoleBuffer.cc
new file mode 100644
index 00000000000..ed93f4081f8
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/Win32ConsoleBuffer.cc
@@ -0,0 +1,193 @@
+// Copyright (c) 2011-2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include "Win32ConsoleBuffer.h"
+
+#include <windows.h>
+
+#include "../shared/DebugClient.h"
+#include "../shared/StringBuilder.h"
+#include "../shared/WinptyAssert.h"
+
+std::unique_ptr<Win32ConsoleBuffer> Win32ConsoleBuffer::openStdout() {
+ return std::unique_ptr<Win32ConsoleBuffer>(
+ new Win32ConsoleBuffer(GetStdHandle(STD_OUTPUT_HANDLE), false));
+}
+
+std::unique_ptr<Win32ConsoleBuffer> Win32ConsoleBuffer::openConout() {
+ const HANDLE conout = CreateFileW(L"CONOUT$",
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL, OPEN_EXISTING, 0, NULL);
+ ASSERT(conout != INVALID_HANDLE_VALUE);
+ return std::unique_ptr<Win32ConsoleBuffer>(
+ new Win32ConsoleBuffer(conout, true));
+}
+
+std::unique_ptr<Win32ConsoleBuffer> Win32ConsoleBuffer::createErrorBuffer() {
+ SECURITY_ATTRIBUTES sa = {};
+ sa.nLength = sizeof(sa);
+ sa.bInheritHandle = TRUE;
+ const HANDLE conout =
+ CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ &sa,
+ CONSOLE_TEXTMODE_BUFFER,
+ nullptr);
+ ASSERT(conout != INVALID_HANDLE_VALUE);
+ return std::unique_ptr<Win32ConsoleBuffer>(
+ new Win32ConsoleBuffer(conout, true));
+}
+
+HANDLE Win32ConsoleBuffer::conout() {
+ return m_conout;
+}
+
+void Win32ConsoleBuffer::clearLines(
+ int row,
+ int count,
+ const ConsoleScreenBufferInfo &info) {
+ // TODO: error handling
+ const int width = info.bufferSize().X;
+ DWORD actual = 0;
+ if (!FillConsoleOutputCharacterW(
+ m_conout, L' ', width * count, Coord(0, row),
+ &actual) || static_cast<int>(actual) != width * count) {
+ trace("FillConsoleOutputCharacterW failed");
+ }
+ if (!FillConsoleOutputAttribute(
+ m_conout, kDefaultAttributes, width * count, Coord(0, row),
+ &actual) || static_cast<int>(actual) != width * count) {
+ trace("FillConsoleOutputAttribute failed");
+ }
+}
+
+void Win32ConsoleBuffer::clearAllLines(const ConsoleScreenBufferInfo &info) {
+ clearLines(0, info.bufferSize().Y, info);
+}
+
+ConsoleScreenBufferInfo Win32ConsoleBuffer::bufferInfo() {
+ // TODO: error handling
+ ConsoleScreenBufferInfo info;
+ if (!GetConsoleScreenBufferInfo(m_conout, &info)) {
+ trace("GetConsoleScreenBufferInfo failed");
+ }
+ return info;
+}
+
+Coord Win32ConsoleBuffer::bufferSize() {
+ return bufferInfo().bufferSize();
+}
+
+SmallRect Win32ConsoleBuffer::windowRect() {
+ return bufferInfo().windowRect();
+}
+
+bool Win32ConsoleBuffer::resizeBufferRange(const Coord &initialSize,
+ Coord &finalSize) {
+ if (SetConsoleScreenBufferSize(m_conout, initialSize)) {
+ finalSize = initialSize;
+ return true;
+ }
+ // The font might be too small to accommodate a very narrow console window.
+ // In that case, rather than simply give up, it's better to try wider
+ // buffer sizes until the call succeeds.
+ Coord size = initialSize;
+ while (size.X < 20) {
+ size.X++;
+ if (SetConsoleScreenBufferSize(m_conout, size)) {
+ finalSize = size;
+ trace("SetConsoleScreenBufferSize: initial size (%d,%d) failed, "
+ "but wider size (%d,%d) succeeded",
+ initialSize.X, initialSize.Y,
+ finalSize.X, finalSize.Y);
+ return true;
+ }
+ }
+ trace("SetConsoleScreenBufferSize failed: "
+ "tried (%d,%d) through (%d,%d)",
+ initialSize.X, initialSize.Y,
+ size.X, size.Y);
+ return false;
+}
+
+void Win32ConsoleBuffer::resizeBuffer(const Coord &size) {
+ // TODO: error handling
+ if (!SetConsoleScreenBufferSize(m_conout, size)) {
+ trace("SetConsoleScreenBufferSize failed: size=(%d,%d)",
+ size.X, size.Y);
+ }
+}
+
+void Win32ConsoleBuffer::moveWindow(const SmallRect &rect) {
+ // TODO: error handling
+ if (!SetConsoleWindowInfo(m_conout, TRUE, &rect)) {
+ trace("SetConsoleWindowInfo failed");
+ }
+}
+
+Coord Win32ConsoleBuffer::cursorPosition() {
+ return bufferInfo().dwCursorPosition;
+}
+
+void Win32ConsoleBuffer::setCursorPosition(const Coord &coord) {
+ // TODO: error handling
+ if (!SetConsoleCursorPosition(m_conout, coord)) {
+ trace("SetConsoleCursorPosition failed");
+ }
+}
+
+void Win32ConsoleBuffer::read(const SmallRect &rect, CHAR_INFO *data) {
+ // TODO: error handling
+ SmallRect tmp(rect);
+ if (!ReadConsoleOutputW(m_conout, data, rect.size(), Coord(), &tmp) &&
+ isTracingEnabled()) {
+ StringBuilder sb(256);
+ auto outStruct = [&](const SMALL_RECT &sr) {
+ sb << "{L=" << sr.Left << ",T=" << sr.Top
+ << ",R=" << sr.Right << ",B=" << sr.Bottom << '}';
+ };
+ sb << "Win32ConsoleBuffer::read: ReadConsoleOutput failed: readRegion=";
+ outStruct(rect);
+ CONSOLE_SCREEN_BUFFER_INFO info = {};
+ if (GetConsoleScreenBufferInfo(m_conout, &info)) {
+ sb << ", dwSize=(" << info.dwSize.X << ',' << info.dwSize.Y
+ << "), srWindow=";
+ outStruct(info.srWindow);
+ } else {
+ sb << ", GetConsoleScreenBufferInfo also failed";
+ }
+ trace("%s", sb.c_str());
+ }
+}
+
+void Win32ConsoleBuffer::write(const SmallRect &rect, const CHAR_INFO *data) {
+ // TODO: error handling
+ SmallRect tmp(rect);
+ if (!WriteConsoleOutputW(m_conout, data, rect.size(), Coord(), &tmp)) {
+ trace("WriteConsoleOutput failed");
+ }
+}
+
+void Win32ConsoleBuffer::setTextAttribute(WORD attributes) {
+ if (!SetConsoleTextAttribute(m_conout, attributes)) {
+ trace("SetConsoleTextAttribute failed");
+ }
+}
diff --git a/src/libs/3rdparty/winpty/src/agent/Win32ConsoleBuffer.h b/src/libs/3rdparty/winpty/src/agent/Win32ConsoleBuffer.h
new file mode 100644
index 00000000000..a68d8d304fd
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/Win32ConsoleBuffer.h
@@ -0,0 +1,99 @@
+// Copyright (c) 2011-2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef AGENT_WIN32_CONSOLE_BUFFER_H
+#define AGENT_WIN32_CONSOLE_BUFFER_H
+
+#include <windows.h>
+
+#include <string.h>
+
+#include <memory>
+
+#include "Coord.h"
+#include "SmallRect.h"
+
+class ConsoleScreenBufferInfo : public CONSOLE_SCREEN_BUFFER_INFO {
+public:
+ ConsoleScreenBufferInfo()
+ {
+ memset(this, 0, sizeof(*this));
+ }
+
+ Coord bufferSize() const { return dwSize; }
+ SmallRect windowRect() const { return srWindow; }
+ Coord cursorPosition() const { return dwCursorPosition; }
+};
+
+class Win32ConsoleBuffer {
+private:
+ Win32ConsoleBuffer(HANDLE conout, bool owned) :
+ m_conout(conout), m_owned(owned)
+ {
+ }
+
+public:
+ static const int kDefaultAttributes = 7;
+
+ ~Win32ConsoleBuffer() {
+ if (m_owned) {
+ CloseHandle(m_conout);
+ }
+ }
+
+ static std::unique_ptr<Win32ConsoleBuffer> openStdout();
+ static std::unique_ptr<Win32ConsoleBuffer> openConout();
+ static std::unique_ptr<Win32ConsoleBuffer> createErrorBuffer();
+
+ Win32ConsoleBuffer(const Win32ConsoleBuffer &other) = delete;
+ Win32ConsoleBuffer &operator=(const Win32ConsoleBuffer &other) = delete;
+
+ HANDLE conout();
+ void clearLines(int row, int count, const ConsoleScreenBufferInfo &info);
+ void clearAllLines(const ConsoleScreenBufferInfo &info);
+
+ // Buffer and window sizes.
+ ConsoleScreenBufferInfo bufferInfo();
+ Coord bufferSize();
+ SmallRect windowRect();
+ void resizeBuffer(const Coord &size);
+ bool resizeBufferRange(const Coord &initialSize, Coord &finalSize);
+ bool resizeBufferRange(const Coord &initialSize) {
+ Coord dummy;
+ return resizeBufferRange(initialSize, dummy);
+ }
+ void moveWindow(const SmallRect &rect);
+
+ // Cursor.
+ Coord cursorPosition();
+ void setCursorPosition(const Coord &point);
+
+ // Screen content.
+ void read(const SmallRect &rect, CHAR_INFO *data);
+ void write(const SmallRect &rect, const CHAR_INFO *data);
+
+ void setTextAttribute(WORD attributes);
+
+private:
+ HANDLE m_conout = nullptr;
+ bool m_owned = false;
+};
+
+#endif // AGENT_WIN32_CONSOLE_BUFFER_H
diff --git a/src/libs/3rdparty/winpty/src/agent/main.cc b/src/libs/3rdparty/winpty/src/agent/main.cc
new file mode 100644
index 00000000000..427cb3a3aa1
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/main.cc
@@ -0,0 +1,120 @@
+// Copyright (c) 2011-2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include <windows.h>
+#include <wchar.h>
+#include <shellapi.h>
+
+#include "../shared/StringUtil.h"
+#include "../shared/WindowsVersion.h"
+#include "../shared/WinptyAssert.h"
+#include "../shared/WinptyVersion.h"
+
+#include "Agent.h"
+#include "AgentCreateDesktop.h"
+#include "DebugShowInput.h"
+
+const char USAGE[] =
+"Usage: %ls controlPipeName flags mouseMode cols rows\n"
+"Usage: %ls controlPipeName --create-desktop\n"
+"\n"
+"Ordinarily, this program is launched by winpty.dll and is not directly\n"
+"useful to winpty users. However, it also has options intended for\n"
+"debugging winpty.\n"
+"\n"
+"Usage: %ls [options]\n"
+"\n"
+"Options:\n"
+" --show-input [--with-mouse] [--escape-input]\n"
+" Dump INPUT_RECORDs from the console input buffer\n"
+" --with-mouse: Include MOUSE_INPUT_RECORDs in the dump\n"
+" output\n"
+" --escape-input: Direct the new Windows 10 console to use\n"
+" escape sequences for input\n"
+" --version Print the winpty version\n";
+
+static uint64_t winpty_atoi64(const char *str) {
+ return strtoll(str, NULL, 10);
+}
+
+int main() {
+ dumpWindowsVersion();
+ dumpVersionToTrace();
+
+ // Technically, we should free the CommandLineToArgvW return value using
+ // a single call to LocalFree, but the call will never actually happen in
+ // the normal case.
+ int argc = 0;
+ wchar_t *cmdline = GetCommandLineW();
+ ASSERT(cmdline != nullptr && "GetCommandLineW returned NULL");
+ wchar_t **argv = CommandLineToArgvW(cmdline, &argc);
+ ASSERT(argv != nullptr && "CommandLineToArgvW returned NULL");
+
+ if (argc == 2 && !wcscmp(argv[1], L"--version")) {
+ dumpVersionToStdout();
+ return 0;
+ }
+
+ if (argc >= 2 && !wcscmp(argv[1], L"--show-input")) {
+ bool withMouse = false;
+ bool escapeInput = false;
+ for (int i = 2; i < argc; ++i) {
+ if (!wcscmp(argv[i], L"--with-mouse")) {
+ withMouse = true;
+ } else if (!wcscmp(argv[i], L"--escape-input")) {
+ escapeInput = true;
+ } else {
+ fprintf(stderr, "Unrecognized --show-input option: %ls\n",
+ argv[i]);
+ return 1;
+ }
+ }
+ debugShowInput(withMouse, escapeInput);
+ return 0;
+ }
+
+ if (argc == 3 && !wcscmp(argv[2], L"--create-desktop")) {
+ handleCreateDesktop(argv[1]);
+ return 0;
+ }
+
+ if (argc != 6) {
+ fprintf(stderr, USAGE, argv[0], argv[0], argv[0]);
+ return 1;
+ }
+
+ Agent agent(argv[1],
+ winpty_atoi64(utf8FromWide(argv[2]).c_str()),
+ atoi(utf8FromWide(argv[3]).c_str()),
+ atoi(utf8FromWide(argv[4]).c_str()),
+ atoi(utf8FromWide(argv[5]).c_str()));
+ agent.run();
+
+ // The Agent destructor shouldn't return, but if it does, exit
+ // unsuccessfully.
+ return 1;
+}
diff --git a/src/libs/3rdparty/winpty/src/agent/subdir.mk b/src/libs/3rdparty/winpty/src/agent/subdir.mk
new file mode 100644
index 00000000000..1c7d37e3e53
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/agent/subdir.mk
@@ -0,0 +1,61 @@
+# Copyright (c) 2011-2015 Ryan Prichard
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+ALL_TARGETS += build/winpty-agent.exe
+
+$(eval $(call def_mingw_target,agent,-DWINPTY_AGENT_ASSERT))
+
+AGENT_OBJECTS = \
+ build/agent/agent/Agent.o \
+ build/agent/agent/AgentCreateDesktop.o \
+ build/agent/agent/ConsoleFont.o \
+ build/agent/agent/ConsoleInput.o \
+ build/agent/agent/ConsoleInputReencoding.o \
+ build/agent/agent/ConsoleLine.o \
+ build/agent/agent/DebugShowInput.o \
+ build/agent/agent/DefaultInputMap.o \
+ build/agent/agent/EventLoop.o \
+ build/agent/agent/InputMap.o \
+ build/agent/agent/LargeConsoleRead.o \
+ build/agent/agent/NamedPipe.o \
+ build/agent/agent/Scraper.o \
+ build/agent/agent/Terminal.o \
+ build/agent/agent/Win32Console.o \
+ build/agent/agent/Win32ConsoleBuffer.o \
+ build/agent/agent/main.o \
+ build/agent/shared/BackgroundDesktop.o \
+ build/agent/shared/Buffer.o \
+ build/agent/shared/DebugClient.o \
+ build/agent/shared/GenRandom.o \
+ build/agent/shared/OwnedHandle.o \
+ build/agent/shared/StringUtil.o \
+ build/agent/shared/WindowsSecurity.o \
+ build/agent/shared/WindowsVersion.o \
+ build/agent/shared/WinptyAssert.o \
+ build/agent/shared/WinptyException.o \
+ build/agent/shared/WinptyVersion.o
+
+build/agent/shared/WinptyVersion.o : build/gen/GenVersion.h
+
+build/winpty-agent.exe : $(AGENT_OBJECTS)
+ $(info Linking $@)
+ @$(MINGW_CXX) $(MINGW_LDFLAGS) -o $@ $^
+
+-include $(AGENT_OBJECTS:.o=.d)
diff --git a/src/libs/3rdparty/winpty/src/configurations.gypi b/src/libs/3rdparty/winpty/src/configurations.gypi
new file mode 100644
index 00000000000..e990a60338e
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/configurations.gypi
@@ -0,0 +1,60 @@
+# By default gyp/msbuild build for 32-bit Windows. This gyp include file
+# defines configurations for both 32-bit and 64-bit Windows. To use it, run:
+#
+# C:\...\winpty\src>gyp -I configurations.gypi
+#
+# This command generates Visual Studio project files with a Release
+# configuration and two Platforms--Win32 and x64. Both can be built:
+#
+# C:\...\winpty\src>msbuild winpty.sln /p:Platform=Win32
+# C:\...\winpty\src>msbuild winpty.sln /p:Platform=x64
+#
+# The output is placed in:
+#
+# C:\...\winpty\src\Release\Win32
+# C:\...\winpty\src\Release\x64
+#
+# Windows XP note: By default, the project files will use the default "toolset"
+# for the given MSVC version. For MSVC 2013 and MSVC 2015, the default toolset
+# generates binaries that do not run on Windows XP. To target Windows XP,
+# select the XP-specific toolset by passing
+# -D WINPTY_MSBUILD_TOOLSET={v120_xp,v140_xp} to gyp (v120_xp == MSVC 2013,
+# v140_xp == MSVC 2015). Unfortunately, it isn't possible to have a single
+# project file with configurations for both XP and post-XP. This seems to be a
+# limitation of the MSVC project file format.
+#
+# This file is not included by default, because I suspect it would interfere
+# with node-gyp, which has a different system for building 32-vs-64-bit
+# binaries. It uses a common.gypi, and the project files it generates can only
+# build a single architecture, the output paths are not differentiated by
+# architecture.
+
+{
+ 'variables': {
+ 'WINPTY_MSBUILD_TOOLSET%': '',
+ },
+ 'target_defaults': {
+ 'default_configuration': 'Release_Win32',
+ 'configurations': {
+ 'Release_Win32': {
+ 'msvs_configuration_platform': 'Win32',
+ },
+ 'Release_x64': {
+ 'msvs_configuration_platform': 'x64',
+ },
+ },
+ 'msvs_configuration_attributes': {
+ 'OutputDirectory': '$(SolutionDir)$(ConfigurationName)\\$(Platform)',
+ 'IntermediateDirectory': '$(ConfigurationName)\\$(Platform)\\obj\\$(ProjectName)',
+ },
+ 'msvs_settings': {
+ 'VCLinkerTool': {
+ 'SubSystem': '1', # /SUBSYSTEM:CONSOLE
+ },
+ 'VCCLCompilerTool': {
+ 'RuntimeLibrary': '0', # MultiThreaded (/MT)
+ },
+ },
+ 'msbuild_toolset' : '<(WINPTY_MSBUILD_TOOLSET)',
+ }
+}
diff --git a/src/libs/3rdparty/winpty/src/debugserver/DebugServer.cc b/src/libs/3rdparty/winpty/src/debugserver/DebugServer.cc
new file mode 100644
index 00000000000..353d31c1c6e
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/debugserver/DebugServer.cc
@@ -0,0 +1,117 @@
+// Copyright (c) 2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include <cstdio>
+#include <cstdlib>
+
+#include <windows.h>
+
+#include "../shared/WindowsSecurity.h"
+#include "../shared/WinptyException.h"
+
+const wchar_t *kPipeName = L"\\\\.\\pipe\\DebugServer";
+
+// A message may not be larger than this size.
+const int MSG_SIZE = 4096;
+
+static void usage(const char *program, int code) {
+ printf("Usage: %s [--everyone]\n"
+ "\n"
+ "Creates the named pipe %ls and reads messages. Prints each\n"
+ "message to stdout. By default, only the current user can send messages.\n"
+ "Pass --everyone to let anyone send a message.\n"
+ "\n"
+ "Use the WINPTY_DEBUG environment variable to enable winpty trace output.\n"
+ "(e.g. WINPTY_DEBUG=trace for the default trace output.) Set WINPTYDBG=1\n"
+ "to enable trace with older winpty versions.\n",
+ program, kPipeName);
+ exit(code);
+}
+
+int main(int argc, char *argv[]) {
+ bool everyone = false;
+ for (int i = 1; i < argc; ++i) {
+ std::string arg = argv[i];
+ if (arg == "--everyone") {
+ everyone = true;
+ } else if (arg == "-h" || arg == "--help") {
+ usage(argv[0], 0);
+ } else {
+ usage(argv[0], 1);
+ }
+ }
+
+ SecurityDescriptor sd;
+ PSECURITY_ATTRIBUTES psa = nullptr;
+ SECURITY_ATTRIBUTES sa = {};
+ if (everyone) {
+ try {
+ sd = createPipeSecurityDescriptorOwnerFullControlEveryoneWrite();
+ } catch (const WinptyException &e) {
+ fprintf(stderr,
+ "error creating security descriptor: %ls\n", e.what());
+ exit(1);
+ }
+ sa.nLength = sizeof(sa);
+ sa.lpSecurityDescriptor = sd.get();
+ psa = &sa;
+ }
+
+ HANDLE serverPipe = CreateNamedPipeW(
+ kPipeName,
+ /*dwOpenMode=*/PIPE_ACCESS_DUPLEX | FILE_FLAG_FIRST_PIPE_INSTANCE,
+ /*dwPipeMode=*/PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE |
+ rejectRemoteClientsPipeFlag(),
+ /*nMaxInstances=*/1,
+ /*nOutBufferSize=*/MSG_SIZE,
+ /*nInBufferSize=*/MSG_SIZE,
+ /*nDefaultTimeOut=*/10 * 1000,
+ psa);
+
+ if (serverPipe == INVALID_HANDLE_VALUE) {
+ fprintf(stderr, "error: could not create %ls pipe: error %u\n",
+ kPipeName, static_cast<unsigned>(GetLastError()));
+ exit(1);
+ }
+
+ char msgBuffer[MSG_SIZE + 1];
+
+ while (true) {
+ if (!ConnectNamedPipe(serverPipe, nullptr)) {
+ fprintf(stderr, "error: ConnectNamedPipe failed\n");
+ fflush(stderr);
+ exit(1);
+ }
+ DWORD bytesRead = 0;
+ if (!ReadFile(serverPipe, msgBuffer, MSG_SIZE, &bytesRead, nullptr)) {
+ fprintf(stderr, "error: ReadFile on pipe failed\n");
+ fflush(stderr);
+ DisconnectNamedPipe(serverPipe);
+ continue;
+ }
+ msgBuffer[bytesRead] = '\n';
+ fwrite(msgBuffer, 1, bytesRead + 1, stdout);
+ fflush(stdout);
+
+ DWORD bytesWritten = 0;
+ WriteFile(serverPipe, "OK", 2, &bytesWritten, nullptr);
+ DisconnectNamedPipe(serverPipe);
+ }
+}
diff --git a/src/libs/3rdparty/winpty/src/debugserver/subdir.mk b/src/libs/3rdparty/winpty/src/debugserver/subdir.mk
new file mode 100644
index 00000000000..beed1bd597d
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/debugserver/subdir.mk
@@ -0,0 +1,41 @@
+# Copyright (c) 2015 Ryan Prichard
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+ALL_TARGETS += build/winpty-debugserver.exe
+
+$(eval $(call def_mingw_target,debugserver,))
+
+DEBUGSERVER_OBJECTS = \
+ build/debugserver/debugserver/DebugServer.o \
+ build/debugserver/shared/DebugClient.o \
+ build/debugserver/shared/OwnedHandle.o \
+ build/debugserver/shared/StringUtil.o \
+ build/debugserver/shared/WindowsSecurity.o \
+ build/debugserver/shared/WindowsVersion.o \
+ build/debugserver/shared/WinptyAssert.o \
+ build/debugserver/shared/WinptyException.o
+
+build/debugserver/shared/WindowsVersion.o : build/gen/GenVersion.h
+
+build/winpty-debugserver.exe : $(DEBUGSERVER_OBJECTS)
+ $(info Linking $@)
+ @$(MINGW_CXX) $(MINGW_LDFLAGS) -o $@ $^
+
+-include $(DEBUGSERVER_OBJECTS:.o=.d)
diff --git a/src/libs/3rdparty/winpty/src/include/winpty.h b/src/libs/3rdparty/winpty/src/include/winpty.h
new file mode 100644
index 00000000000..fdfe4bca21d
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/include/winpty.h
@@ -0,0 +1,242 @@
+/*
+ * Copyright (c) 2011-2016 Ryan Prichard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef WINPTY_H
+#define WINPTY_H
+
+#include <windows.h>
+
+#include "winpty_constants.h"
+
+/* On 32-bit Windows, winpty functions have the default __cdecl (not __stdcall)
+ * calling convention. (64-bit Windows has only a single calling convention.)
+ * When compiled with __declspec(dllexport), with either MinGW or MSVC, the
+ * winpty functions are unadorned--no underscore prefix or '@nn' suffix--so
+ * GetProcAddress can be used easily. */
+#ifdef COMPILING_WINPTY_DLL
+#define WINPTY_API __declspec(dllexport)
+#else
+#define WINPTY_API __declspec(dllimport)
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* The winpty API uses wide characters, instead of UTF-8, to avoid conversion
+ * complications related to surrogates. Windows generally tolerates unpaired
+ * surrogates in text, which makes conversion to and from UTF-8 ambiguous and
+ * complicated. (There are different UTF-8 variants that deal with UTF-16
+ * surrogates differently.) */
+
+
+
+/*****************************************************************************
+ * Error handling. */
+
+/* All the APIs have an optional winpty_error_t output parameter. If a
+ * non-NULL argument is specified, then either the API writes NULL to the
+ * value (on success) or writes a newly allocated winpty_error_t object. The
+ * object must be freed using winpty_error_free. */
+
+/* An error object. */
+typedef struct winpty_error_s winpty_error_t;
+typedef winpty_error_t *winpty_error_ptr_t;
+
+/* An error code -- one of WINPTY_ERROR_xxx. */
+typedef DWORD winpty_result_t;
+
+/* Gets the error code from the error object. */
+WINPTY_API winpty_result_t winpty_error_code(winpty_error_ptr_t err);
+
+/* Returns a textual representation of the error. The string is freed when
+ * the error is freed. */
+WINPTY_API LPCWSTR winpty_error_msg(winpty_error_ptr_t err);
+
+/* Free the error object. Every error returned from the winpty API must be
+ * freed. */
+WINPTY_API void winpty_error_free(winpty_error_ptr_t err);
+
+
+
+/*****************************************************************************
+ * Configuration of a new agent. */
+
+/* The winpty_config_t object is not thread-safe. */
+typedef struct winpty_config_s winpty_config_t;
+
+/* Allocate a winpty_config_t value. Returns NULL on error. There are no
+ * required settings -- the object may immediately be used. agentFlags is a
+ * set of zero or more WINPTY_FLAG_xxx values. An unrecognized flag results
+ * in an assertion failure. */
+WINPTY_API winpty_config_t *
+winpty_config_new(UINT64 agentFlags, winpty_error_ptr_t *err /*OPTIONAL*/);
+
+/* Free the cfg object after passing it to winpty_open. */
+WINPTY_API void winpty_config_free(winpty_config_t *cfg);
+
+WINPTY_API void
+winpty_config_set_initial_size(winpty_config_t *cfg, int cols, int rows);
+
+/* Set the mouse mode to one of the WINPTY_MOUSE_MODE_xxx constants. */
+WINPTY_API void
+winpty_config_set_mouse_mode(winpty_config_t *cfg, int mouseMode);
+
+/* Amount of time to wait for the agent to startup and to wait for any given
+ * agent RPC request. Must be greater than 0. Can be INFINITE. */
+WINPTY_API void
+winpty_config_set_agent_timeout(winpty_config_t *cfg, DWORD timeoutMs);
+
+
+
+/*****************************************************************************
+ * Start the agent. */
+
+/* The winpty_t object is thread-safe. */
+typedef struct winpty_s winpty_t;
+
+/* Starts the agent. Returns NULL on error. This process will connect to the
+ * agent over a control pipe, and the agent will open data pipes (e.g. CONIN
+ * and CONOUT). */
+WINPTY_API winpty_t *
+winpty_open(const winpty_config_t *cfg,
+ winpty_error_ptr_t *err /*OPTIONAL*/);
+
+/* A handle to the agent process. This value is valid for the lifetime of the
+ * winpty_t object. Do not close it. */
+WINPTY_API HANDLE winpty_agent_process(winpty_t *wp);
+
+
+
+/*****************************************************************************
+ * I/O pipes. */
+
+/* Returns the names of named pipes used for terminal I/O. Each input or
+ * output direction uses a different half-duplex pipe. The agent creates
+ * these pipes, and the client can connect to them using ordinary I/O methods.
+ * The strings are freed when the winpty_t object is freed.
+ *
+ * winpty_conerr_name returns NULL unless WINPTY_FLAG_CONERR is specified.
+ *
+ * N.B.: CreateFile does not block when connecting to a local server pipe. If
+ * the server pipe does not exist or is already connected, then it fails
+ * instantly. */
+WINPTY_API LPCWSTR winpty_conin_name(winpty_t *wp);
+WINPTY_API LPCWSTR winpty_conout_name(winpty_t *wp);
+WINPTY_API LPCWSTR winpty_conerr_name(winpty_t *wp);
+
+
+
+/*****************************************************************************
+ * winpty agent RPC call: process creation. */
+
+/* The winpty_spawn_config_t object is not thread-safe. */
+typedef struct winpty_spawn_config_s winpty_spawn_config_t;
+
+/* winpty_spawn_config strings do not need to live as long as the config
+ * object. They are copied. Returns NULL on error. spawnFlags is a set of
+ * zero or more WINPTY_SPAWN_FLAG_xxx values. An unrecognized flag results in
+ * an assertion failure.
+ *
+ * env is a a pointer to an environment block like that passed to
+ * CreateProcess--a contiguous array of NUL-terminated "VAR=VAL" strings
+ * followed by a final NUL terminator.
+ *
+ * N.B.: If you want to gather all of the child's output, you may want the
+ * WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN flag.
+ */
+WINPTY_API winpty_spawn_config_t *
+winpty_spawn_config_new(UINT64 spawnFlags,
+ LPCWSTR appname /*OPTIONAL*/,
+ LPCWSTR cmdline /*OPTIONAL*/,
+ LPCWSTR cwd /*OPTIONAL*/,
+ LPCWSTR env /*OPTIONAL*/,
+ winpty_error_ptr_t *err /*OPTIONAL*/);
+
+/* Free the cfg object after passing it to winpty_spawn. */
+WINPTY_API void winpty_spawn_config_free(winpty_spawn_config_t *cfg);
+
+/*
+ * Spawns the new process.
+ *
+ * The function initializes all output parameters to zero or NULL.
+ *
+ * On success, the function returns TRUE. For each of process_handle and
+ * thread_handle that is non-NULL, the HANDLE returned from CreateProcess is
+ * duplicated from the agent and returned to the winpty client. The client is
+ * responsible for closing these HANDLES.
+ *
+ * On failure, the function returns FALSE, and if err is non-NULL, then *err
+ * is set to an error object.
+ *
+ * If the agent's CreateProcess call failed, then *create_process_error is set
+ * to GetLastError(), and the WINPTY_ERROR_SPAWN_CREATE_PROCESS_FAILED error
+ * is returned.
+ *
+ * winpty_spawn can only be called once per winpty_t object. If it is called
+ * before the output data pipe(s) is/are connected, then collected output is
+ * buffered until the pipes are connected, rather than being discarded.
+ *
+ * N.B.: GetProcessId works even if the process has exited. The PID is not
+ * recycled until the NT process object is freed.
+ * (https://blogs.msdn.microsoft.com/oldnewthing/20110107-00/?p=11803)
+ */
+WINPTY_API BOOL
+winpty_spawn(winpty_t *wp,
+ const winpty_spawn_config_t *cfg,
+ HANDLE *process_handle /*OPTIONAL*/,
+ HANDLE *thread_handle /*OPTIONAL*/,
+ DWORD *create_process_error /*OPTIONAL*/,
+ winpty_error_ptr_t *err /*OPTIONAL*/);
+
+
+
+/*****************************************************************************
+ * winpty agent RPC calls: everything else */
+
+/* Change the size of the Windows console window. */
+WINPTY_API BOOL
+winpty_set_size(winpty_t *wp, int cols, int rows,
+ winpty_error_ptr_t *err /*OPTIONAL*/);
+
+/* Gets a list of processes attached to the console. */
+WINPTY_API int
+winpty_get_console_process_list(winpty_t *wp, int *processList, const int processCount,
+ winpty_error_ptr_t *err /*OPTIONAL*/);
+
+/* Frees the winpty_t object and the OS resources contained in it. This
+ * call breaks the connection with the agent, which should then close its
+ * console, terminating the processes attached to it.
+ *
+ * This function must not be called if any other threads are using the
+ * winpty_t object. Undefined behavior results. */
+WINPTY_API void winpty_free(winpty_t *wp);
+
+
+
+/****************************************************************************/
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WINPTY_H */
diff --git a/src/libs/3rdparty/winpty/src/include/winpty_constants.h b/src/libs/3rdparty/winpty/src/include/winpty_constants.h
new file mode 100644
index 00000000000..11e34cf171c
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/include/winpty_constants.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2016 Ryan Prichard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef WINPTY_CONSTANTS_H
+#define WINPTY_CONSTANTS_H
+
+/*
+ * You may want to include winpty.h instead, which includes this header.
+ *
+ * This file is split out from winpty.h so that the agent can access the
+ * winpty flags without also declaring the libwinpty APIs.
+ */
+
+/*****************************************************************************
+ * Error codes. */
+
+#define WINPTY_ERROR_SUCCESS 0
+#define WINPTY_ERROR_OUT_OF_MEMORY 1
+#define WINPTY_ERROR_SPAWN_CREATE_PROCESS_FAILED 2
+#define WINPTY_ERROR_LOST_CONNECTION 3
+#define WINPTY_ERROR_AGENT_EXE_MISSING 4
+#define WINPTY_ERROR_UNSPECIFIED 5
+#define WINPTY_ERROR_AGENT_DIED 6
+#define WINPTY_ERROR_AGENT_TIMEOUT 7
+#define WINPTY_ERROR_AGENT_CREATION_FAILED 8
+
+
+
+/*****************************************************************************
+ * Configuration of a new agent. */
+
+/* Create a new screen buffer (connected to the "conerr" terminal pipe) and
+ * pass it to child processes as the STDERR handle. This flag also prevents
+ * the agent from reopening CONOUT$ when it polls -- regardless of whether the
+ * active screen buffer changes, winpty continues to monitor the original
+ * primary screen buffer. */
+#define WINPTY_FLAG_CONERR 0x1ull
+
+/* Don't output escape sequences. */
+#define WINPTY_FLAG_PLAIN_OUTPUT 0x2ull
+
+/* Do output color escape sequences. These escapes are output by default, but
+ * are suppressed with WINPTY_FLAG_PLAIN_OUTPUT. Use this flag to reenable
+ * them. */
+#define WINPTY_FLAG_COLOR_ESCAPES 0x4ull
+
+/* On XP and Vista, winpty needs to put the hidden console on a desktop in a
+ * service window station so that its polling does not interfere with other
+ * (visible) console windows. To create this desktop, it must change the
+ * process' window station (i.e. SetProcessWindowStation) for the duration of
+ * the winpty_open call. In theory, this change could interfere with the
+ * winpty client (e.g. other threads, spawning children), so winpty by default
+ * spawns a special agent process to create the hidden desktop. Spawning
+ * processes on Windows is slow, though, so if
+ * WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION is set, winpty changes this
+ * process' window station instead.
+ * See https://github.com/rprichard/winpty/issues/58. */
+#define WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION 0x8ull
+
+#define WINPTY_FLAG_MASK (0ull \
+ | WINPTY_FLAG_CONERR \
+ | WINPTY_FLAG_PLAIN_OUTPUT \
+ | WINPTY_FLAG_COLOR_ESCAPES \
+ | WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION \
+)
+
+/* QuickEdit mode is initially disabled, and the agent does not send mouse
+ * mode sequences to the terminal. If it receives mouse input, though, it
+ * still writes MOUSE_EVENT_RECORD values into CONIN. */
+#define WINPTY_MOUSE_MODE_NONE 0
+
+/* QuickEdit mode is initially enabled. As CONIN enters or leaves mouse
+ * input mode (i.e. where ENABLE_MOUSE_INPUT is on and ENABLE_QUICK_EDIT_MODE
+ * is off), the agent enables or disables mouse input on the terminal.
+ *
+ * This is the default mode. */
+#define WINPTY_MOUSE_MODE_AUTO 1
+
+/* QuickEdit mode is initially disabled, and the agent enables the terminal's
+ * mouse input mode. It does not disable terminal mouse mode (until exit). */
+#define WINPTY_MOUSE_MODE_FORCE 2
+
+
+
+/*****************************************************************************
+ * winpty agent RPC call: process creation. */
+
+/* If the spawn is marked "auto-shutdown", then the agent shuts down console
+ * output once the process exits. The agent stops polling for new console
+ * output, and once all pending data has been written to the output pipe, the
+ * agent closes the pipe. (At that point, the pipe may still have data in it,
+ * which the client may read. Once all the data has been read, further reads
+ * return EOF.) */
+#define WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN 1ull
+
+/* After the agent shuts down output, and after all output has been written
+ * into the pipe(s), exit the agent by closing the console. If there any
+ * surviving processes still attached to the console, they are killed.
+ *
+ * Note: With this flag, an RPC call (e.g. winpty_set_size) issued after the
+ * agent exits will fail with an I/O or dead-agent error. */
+#define WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN 2ull
+
+/* All the spawn flags. */
+#define WINPTY_SPAWN_FLAG_MASK (0ull \
+ | WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN \
+ | WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN \
+)
+
+
+
+#endif /* WINPTY_CONSTANTS_H */
diff --git a/src/libs/3rdparty/winpty/src/libwinpty/AgentLocation.cc b/src/libs/3rdparty/winpty/src/libwinpty/AgentLocation.cc
new file mode 100644
index 00000000000..82d00b2da2d
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/libwinpty/AgentLocation.cc
@@ -0,0 +1,75 @@
+// Copyright (c) 2011-2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include "AgentLocation.h"
+
+#include <windows.h>
+
+#include <string>
+
+#include "../shared/WinptyAssert.h"
+
+#include "LibWinptyException.h"
+
+#define AGENT_EXE L"winpty-agent.exe"
+
+static HMODULE getCurrentModule() {
+ HMODULE module;
+ if (!GetModuleHandleExW(
+ GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
+ GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
+ reinterpret_cast<LPCWSTR>(getCurrentModule),
+ &module)) {
+ ASSERT(false && "GetModuleHandleEx failed");
+ }
+ return module;
+}
+
+static std::wstring getModuleFileName(HMODULE module) {
+ const int bufsize = 4096;
+ wchar_t path[bufsize];
+ int size = GetModuleFileNameW(module, path, bufsize);
+ ASSERT(size != 0 && size != bufsize);
+ return std::wstring(path);
+}
+
+static std::wstring dirname(const std::wstring &path) {
+ std::wstring::size_type pos = path.find_last_of(L"\\/");
+ if (pos == std::wstring::npos) {
+ return L"";
+ } else {
+ return path.substr(0, pos);
+ }
+}
+
+static bool pathExists(const std::wstring &path) {
+ return GetFileAttributesW(path.c_str()) != 0xFFFFFFFF;
+}
+
+std::wstring findAgentProgram() {
+ std::wstring progDir = dirname(getModuleFileName(getCurrentModule()));
+ std::wstring ret = progDir + (L"\\" AGENT_EXE);
+ if (!pathExists(ret)) {
+ throw LibWinptyException(
+ WINPTY_ERROR_AGENT_EXE_MISSING,
+ (L"agent executable does not exist: '" + ret + L"'").c_str());
+ }
+ return ret;
+}
diff --git a/src/libs/3rdparty/winpty/src/libwinpty/AgentLocation.h b/src/libs/3rdparty/winpty/src/libwinpty/AgentLocation.h
new file mode 100644
index 00000000000..a96b854cd2a
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/libwinpty/AgentLocation.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2011-2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef LIBWINPTY_AGENT_LOCATION_H
+#define LIBWINPTY_AGENT_LOCATION_H
+
+#include <string>
+
+std::wstring findAgentProgram();
+
+#endif // LIBWINPTY_AGENT_LOCATION_H
diff --git a/src/libs/3rdparty/winpty/src/libwinpty/LibWinptyException.h b/src/libs/3rdparty/winpty/src/libwinpty/LibWinptyException.h
new file mode 100644
index 00000000000..2274798d238
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/libwinpty/LibWinptyException.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef LIB_WINPTY_EXCEPTION_H
+#define LIB_WINPTY_EXCEPTION_H
+
+#include "../include/winpty.h"
+
+#include "../shared/WinptyException.h"
+
+#include <memory>
+#include <string>
+
+class LibWinptyException : public WinptyException {
+public:
+ LibWinptyException(winpty_result_t code, const wchar_t *what) :
+ m_code(code), m_what(std::make_shared<std::wstring>(what)) {}
+
+ winpty_result_t code() const WINPTY_NOEXCEPT {
+ return m_code;
+ }
+
+ const wchar_t *what() const WINPTY_NOEXCEPT override {
+ return m_what->c_str();
+ }
+
+ std::shared_ptr<std::wstring> whatSharedStr() const WINPTY_NOEXCEPT {
+ return m_what;
+ }
+
+private:
+ winpty_result_t m_code;
+ // Using a shared_ptr ensures that copying the object raises no exception.
+ std::shared_ptr<std::wstring> m_what;
+};
+
+#endif // LIB_WINPTY_EXCEPTION_H
diff --git a/src/libs/3rdparty/winpty/src/libwinpty/WinptyInternal.h b/src/libs/3rdparty/winpty/src/libwinpty/WinptyInternal.h
new file mode 100644
index 00000000000..93e992d5c55
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/libwinpty/WinptyInternal.h
@@ -0,0 +1,72 @@
+// Copyright (c) 2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef LIBWINPTY_WINPTY_INTERNAL_H
+#define LIBWINPTY_WINPTY_INTERNAL_H
+
+#include <memory>
+#include <string>
+
+#include "../include/winpty.h"
+
+#include "../shared/Mutex.h"
+#include "../shared/OwnedHandle.h"
+
+// The structures in this header are not intended to be accessed directly by
+// client programs.
+
+struct winpty_error_s {
+ winpty_result_t code;
+ const wchar_t *msgStatic;
+ // Use a pointer to a std::shared_ptr so that the struct remains simple
+ // enough to statically initialize, for the benefit of static error
+ // objects like kOutOfMemory.
+ std::shared_ptr<std::wstring> *msgDynamic;
+};
+
+struct winpty_config_s {
+ uint64_t flags = 0;
+ int cols = 80;
+ int rows = 25;
+ int mouseMode = WINPTY_MOUSE_MODE_AUTO;
+ DWORD timeoutMs = 30000;
+};
+
+struct winpty_s {
+ Mutex mutex;
+ OwnedHandle agentProcess;
+ OwnedHandle controlPipe;
+ DWORD agentTimeoutMs = 0;
+ OwnedHandle ioEvent;
+ std::wstring spawnDesktopName;
+ std::wstring coninPipeName;
+ std::wstring conoutPipeName;
+ std::wstring conerrPipeName;
+};
+
+struct winpty_spawn_config_s {
+ uint64_t winptyFlags = 0;
+ std::wstring appname;
+ std::wstring cmdline;
+ std::wstring cwd;
+ std::wstring env;
+};
+
+#endif // LIBWINPTY_WINPTY_INTERNAL_H
diff --git a/src/libs/3rdparty/winpty/src/libwinpty/subdir.mk b/src/libs/3rdparty/winpty/src/libwinpty/subdir.mk
new file mode 100644
index 00000000000..ba32bad6e64
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/libwinpty/subdir.mk
@@ -0,0 +1,46 @@
+# Copyright (c) 2011-2015 Ryan Prichard
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+ALL_TARGETS += build/winpty.dll
+
+$(eval $(call def_mingw_target,libwinpty,-DCOMPILING_WINPTY_DLL))
+
+LIBWINPTY_OBJECTS = \
+ build/libwinpty/libwinpty/AgentLocation.o \
+ build/libwinpty/libwinpty/winpty.o \
+ build/libwinpty/shared/BackgroundDesktop.o \
+ build/libwinpty/shared/Buffer.o \
+ build/libwinpty/shared/DebugClient.o \
+ build/libwinpty/shared/GenRandom.o \
+ build/libwinpty/shared/OwnedHandle.o \
+ build/libwinpty/shared/StringUtil.o \
+ build/libwinpty/shared/WindowsSecurity.o \
+ build/libwinpty/shared/WindowsVersion.o \
+ build/libwinpty/shared/WinptyAssert.o \
+ build/libwinpty/shared/WinptyException.o \
+ build/libwinpty/shared/WinptyVersion.o
+
+build/libwinpty/shared/WinptyVersion.o : build/gen/GenVersion.h
+
+build/winpty.dll : $(LIBWINPTY_OBJECTS)
+ $(info Linking $@)
+ @$(MINGW_CXX) $(MINGW_LDFLAGS) -shared -o $@ $^ -Wl,--out-implib,build/winpty.lib
+
+-include $(LIBWINPTY_OBJECTS:.o=.d)
diff --git a/src/libs/3rdparty/winpty/src/libwinpty/winpty.cc b/src/libs/3rdparty/winpty/src/libwinpty/winpty.cc
new file mode 100644
index 00000000000..3d977498ef9
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/libwinpty/winpty.cc
@@ -0,0 +1,970 @@
+// Copyright (c) 2011-2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include <windows.h>
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <limits>
+#include <string>
+#include <vector>
+
+#include "../include/winpty.h"
+
+#include "../shared/AgentMsg.h"
+#include "../shared/BackgroundDesktop.h"
+#include "../shared/Buffer.h"
+#include "../shared/DebugClient.h"
+#include "../shared/GenRandom.h"
+#include "../shared/OwnedHandle.h"
+#include "../shared/StringBuilder.h"
+#include "../shared/StringUtil.h"
+#include "../shared/WindowsSecurity.h"
+#include "../shared/WindowsVersion.h"
+#include "../shared/WinptyAssert.h"
+#include "../shared/WinptyException.h"
+#include "../shared/WinptyVersion.h"
+
+#include "AgentLocation.h"
+#include "LibWinptyException.h"
+#include "WinptyInternal.h"
+
+
+
+/*****************************************************************************
+ * Error handling -- translate C++ exceptions to an optional error object
+ * output and log the result. */
+
+static const winpty_error_s kOutOfMemory = {
+ WINPTY_ERROR_OUT_OF_MEMORY,
+ L"Out of memory",
+ nullptr
+};
+
+static const winpty_error_s kBadRpcPacket = {
+ WINPTY_ERROR_UNSPECIFIED,
+ L"Bad RPC packet",
+ nullptr
+};
+
+static const winpty_error_s kUncaughtException = {
+ WINPTY_ERROR_UNSPECIFIED,
+ L"Uncaught C++ exception",
+ nullptr
+};
+
+/* Gets the error code from the error object. */
+WINPTY_API winpty_result_t winpty_error_code(winpty_error_ptr_t err) {
+ return err != nullptr ? err->code : WINPTY_ERROR_SUCCESS;
+}
+
+/* Returns a textual representation of the error. The string is freed when
+ * the error is freed. */
+WINPTY_API LPCWSTR winpty_error_msg(winpty_error_ptr_t err) {
+ if (err != nullptr) {
+ if (err->msgStatic != nullptr) {
+ return err->msgStatic;
+ } else {
+ ASSERT(err->msgDynamic != nullptr);
+ std::wstring *msgPtr = err->msgDynamic->get();
+ ASSERT(msgPtr != nullptr);
+ return msgPtr->c_str();
+ }
+ } else {
+ return L"Success";
+ }
+}
+
+/* Free the error object. Every error returned from the winpty API must be
+ * freed. */
+WINPTY_API void winpty_error_free(winpty_error_ptr_t err) {
+ if (err != nullptr && err->msgDynamic != nullptr) {
+ delete err->msgDynamic;
+ delete err;
+ }
+}
+
+static void translateException(winpty_error_ptr_t *&err) {
+ winpty_error_ptr_t ret = nullptr;
+ try {
+ try {
+ throw;
+ } catch (const ReadBuffer::DecodeError&) {
+ ret = const_cast<winpty_error_ptr_t>(&kBadRpcPacket);
+ } catch (const LibWinptyException &e) {
+ std::unique_ptr<winpty_error_t> obj(new winpty_error_t);
+ obj->code = e.code();
+ obj->msgStatic = nullptr;
+ obj->msgDynamic =
+ new std::shared_ptr<std::wstring>(e.whatSharedStr());
+ ret = obj.release();
+ } catch (const WinptyException &e) {
+ std::unique_ptr<winpty_error_t> obj(new winpty_error_t);
+ std::shared_ptr<std::wstring> msg(new std::wstring(e.what()));
+ obj->code = WINPTY_ERROR_UNSPECIFIED;
+ obj->msgStatic = nullptr;
+ obj->msgDynamic = new std::shared_ptr<std::wstring>(msg);
+ ret = obj.release();
+ }
+ } catch (const std::bad_alloc&) {
+ ret = const_cast<winpty_error_ptr_t>(&kOutOfMemory);
+ } catch (...) {
+ ret = const_cast<winpty_error_ptr_t>(&kUncaughtException);
+ }
+ trace("libwinpty error: code=%u msg='%s'",
+ static_cast<unsigned>(ret->code),
+ utf8FromWide(winpty_error_msg(ret)).c_str());
+ if (err != nullptr) {
+ *err = ret;
+ } else {
+ winpty_error_free(ret);
+ }
+}
+
+#define API_TRY \
+ if (err != nullptr) { *err = nullptr; } \
+ try
+
+#define API_CATCH(ret) \
+ catch (...) { translateException(err); return (ret); }
+
+
+
+/*****************************************************************************
+ * Configuration of a new agent. */
+
+WINPTY_API winpty_config_t *
+winpty_config_new(UINT64 flags, winpty_error_ptr_t *err /*OPTIONAL*/) {
+ API_TRY {
+ ASSERT((flags & WINPTY_FLAG_MASK) == flags);
+ std::unique_ptr<winpty_config_t> ret(new winpty_config_t);
+ ret->flags = flags;
+ return ret.release();
+ } API_CATCH(nullptr)
+}
+
+WINPTY_API void winpty_config_free(winpty_config_t *cfg) {
+ delete cfg;
+}
+
+WINPTY_API void
+winpty_config_set_initial_size(winpty_config_t *cfg, int cols, int rows) {
+ ASSERT(cfg != nullptr && cols > 0 && rows > 0);
+ cfg->cols = cols;
+ cfg->rows = rows;
+}
+
+WINPTY_API void
+winpty_config_set_mouse_mode(winpty_config_t *cfg, int mouseMode) {
+ ASSERT(cfg != nullptr &&
+ mouseMode >= WINPTY_MOUSE_MODE_NONE &&
+ mouseMode <= WINPTY_MOUSE_MODE_FORCE);
+ cfg->mouseMode = mouseMode;
+}
+
+WINPTY_API void
+winpty_config_set_agent_timeout(winpty_config_t *cfg, DWORD timeoutMs) {
+ ASSERT(cfg != nullptr && timeoutMs > 0);
+ cfg->timeoutMs = timeoutMs;
+}
+
+
+
+/*****************************************************************************
+ * Agent I/O. */
+
+namespace {
+
+// Once an I/O operation fails with ERROR_IO_PENDING, the caller *must* wait
+// for it to complete, even after calling CancelIo on it! See
+// https://blogs.msdn.microsoft.com/oldnewthing/20110202-00/?p=11613. This
+// class enforces that requirement.
+class PendingIo {
+ HANDLE m_file;
+ OVERLAPPED &m_over;
+ bool m_finished;
+public:
+ // The file handle and OVERLAPPED object must live as long as the PendingIo
+ // object.
+ PendingIo(HANDLE file, OVERLAPPED &over) :
+ m_file(file), m_over(over), m_finished(false) {}
+ ~PendingIo() {
+ if (!m_finished) {
+ // We're not usually that interested in CancelIo's return value.
+ // In any case, we must not throw an exception in this dtor.
+ CancelIo(m_file);
+ waitForCompletion();
+ }
+ }
+ std::tuple<BOOL, DWORD> waitForCompletion(DWORD &actual) WINPTY_NOEXCEPT {
+ m_finished = true;
+ const BOOL success =
+ GetOverlappedResult(m_file, &m_over, &actual, TRUE);
+ return std::make_tuple(success, GetLastError());
+ }
+ std::tuple<BOOL, DWORD> waitForCompletion() WINPTY_NOEXCEPT {
+ DWORD actual = 0;
+ return waitForCompletion(actual);
+ }
+};
+
+} // anonymous namespace
+
+static void handlePendingIo(winpty_t &wp, OVERLAPPED &over, BOOL &success,
+ DWORD &lastError, DWORD &actual) {
+ if (!success && lastError == ERROR_IO_PENDING) {
+ PendingIo io(wp.controlPipe.get(), over);
+ const HANDLE waitHandles[2] = { wp.ioEvent.get(),
+ wp.agentProcess.get() };
+ DWORD waitRet = WaitForMultipleObjects(
+ 2, waitHandles, FALSE, wp.agentTimeoutMs);
+ if (waitRet != WAIT_OBJECT_0) {
+ // The I/O is still pending. Cancel it, close the I/O event, and
+ // throw an exception.
+ if (waitRet == WAIT_OBJECT_0 + 1) {
+ throw LibWinptyException(WINPTY_ERROR_AGENT_DIED, L"agent died");
+ } else if (waitRet == WAIT_TIMEOUT) {
+ throw LibWinptyException(WINPTY_ERROR_AGENT_TIMEOUT,
+ L"agent timed out");
+ } else if (waitRet == WAIT_FAILED) {
+ throwWindowsError(L"WaitForMultipleObjects failed");
+ } else {
+ ASSERT(false &&
+ "unexpected WaitForMultipleObjects return value");
+ }
+ }
+ std::tie(success, lastError) = io.waitForCompletion(actual);
+ }
+}
+
+static void handlePendingIo(winpty_t &wp, OVERLAPPED &over, BOOL &success,
+ DWORD &lastError) {
+ DWORD actual = 0;
+ handlePendingIo(wp, over, success, lastError, actual);
+}
+
+static void handleReadWriteErrors(winpty_t &wp, BOOL success, DWORD lastError,
+ const wchar_t *genericErrMsg) {
+ if (!success) {
+ // If the pipe connection is broken after it's been connected, then
+ // later I/O operations fail with ERROR_BROKEN_PIPE (reads) or
+ // ERROR_NO_DATA (writes). With Wine, they may also fail with
+ // ERROR_PIPE_NOT_CONNECTED. See this gist[1].
+ //
+ // [1] https://gist.github.com/rprichard/8dd8ca134b39534b7da2733994aa07ba
+ if (lastError == ERROR_BROKEN_PIPE || lastError == ERROR_NO_DATA ||
+ lastError == ERROR_PIPE_NOT_CONNECTED) {
+ throw LibWinptyException(WINPTY_ERROR_LOST_CONNECTION,
+ L"lost connection to agent");
+ } else {
+ throwWindowsError(genericErrMsg, lastError);
+ }
+ }
+}
+
+// Calls ConnectNamedPipe to wait until the agent connects to the control pipe.
+static void
+connectControlPipe(winpty_t &wp) {
+ OVERLAPPED over = {};
+ over.hEvent = wp.ioEvent.get();
+ BOOL success = ConnectNamedPipe(wp.controlPipe.get(), &over);
+ DWORD lastError = GetLastError();
+ handlePendingIo(wp, over, success, lastError);
+ if (!success && lastError == ERROR_PIPE_CONNECTED) {
+ success = TRUE;
+ }
+ if (!success) {
+ throwWindowsError(L"ConnectNamedPipe failed", lastError);
+ }
+}
+
+static void writeData(winpty_t &wp, const void *data, size_t amount) {
+ // Perform a single pipe write.
+ DWORD actual = 0;
+ OVERLAPPED over = {};
+ over.hEvent = wp.ioEvent.get();
+ BOOL success = WriteFile(wp.controlPipe.get(), data, amount,
+ &actual, &over);
+ DWORD lastError = GetLastError();
+ if (!success) {
+ handlePendingIo(wp, over, success, lastError, actual);
+ handleReadWriteErrors(wp, success, lastError, L"WriteFile failed");
+ ASSERT(success);
+ }
+ // TODO: Can a partial write actually happen somehow?
+ ASSERT(actual == amount && "WriteFile wrote fewer bytes than requested");
+}
+
+static inline WriteBuffer newPacket() {
+ WriteBuffer packet;
+ packet.putRawValue<uint64_t>(0); // Reserve space for size.
+ return packet;
+}
+
+static void writePacket(winpty_t &wp, WriteBuffer &packet) {
+ const auto &buf = packet.buf();
+ packet.replaceRawValue<uint64_t>(0, buf.size());
+ writeData(wp, buf.data(), buf.size());
+}
+
+static size_t readData(winpty_t &wp, void *data, size_t amount) {
+ DWORD actual = 0;
+ OVERLAPPED over = {};
+ over.hEvent = wp.ioEvent.get();
+ BOOL success = ReadFile(wp.controlPipe.get(), data, amount,
+ &actual, &over);
+ DWORD lastError = GetLastError();
+ if (!success) {
+ handlePendingIo(wp, over, success, lastError, actual);
+ handleReadWriteErrors(wp, success, lastError, L"ReadFile failed");
+ }
+ return actual;
+}
+
+static void readAll(winpty_t &wp, void *data, size_t amount) {
+ while (amount > 0) {
+ const size_t chunk = readData(wp, data, amount);
+ ASSERT(chunk <= amount && "readData result is larger than amount");
+ data = reinterpret_cast<char*>(data) + chunk;
+ amount -= chunk;
+ }
+}
+
+static uint64_t readUInt64(winpty_t &wp) {
+ uint64_t ret = 0;
+ readAll(wp, &ret, sizeof(ret));
+ return ret;
+}
+
+// Returns a reply packet's payload.
+static ReadBuffer readPacket(winpty_t &wp) {
+ const uint64_t packetSize = readUInt64(wp);
+ if (packetSize < sizeof(packetSize) || packetSize > SIZE_MAX) {
+ throwWinptyException(L"Agent RPC error: invalid packet size");
+ }
+ const size_t payloadSize = packetSize - sizeof(packetSize);
+ std::vector<char> bytes(payloadSize);
+ readAll(wp, bytes.data(), bytes.size());
+ return ReadBuffer(std::move(bytes));
+}
+
+static OwnedHandle createControlPipe(const std::wstring &name) {
+ const auto sd = createPipeSecurityDescriptorOwnerFullControl();
+ if (!sd) {
+ throwWinptyException(
+ L"could not create the control pipe's SECURITY_DESCRIPTOR");
+ }
+ SECURITY_ATTRIBUTES sa = {};
+ sa.nLength = sizeof(sa);
+ sa.lpSecurityDescriptor = sd.get();
+ HANDLE ret = CreateNamedPipeW(name.c_str(),
+ /*dwOpenMode=*/
+ PIPE_ACCESS_DUPLEX |
+ FILE_FLAG_FIRST_PIPE_INSTANCE |
+ FILE_FLAG_OVERLAPPED,
+ /*dwPipeMode=*/rejectRemoteClientsPipeFlag(),
+ /*nMaxInstances=*/1,
+ /*nOutBufferSize=*/8192,
+ /*nInBufferSize=*/256,
+ /*nDefaultTimeOut=*/30000,
+ &sa);
+ if (ret == INVALID_HANDLE_VALUE) {
+ throwWindowsError(L"CreateNamedPipeW failed");
+ }
+ return OwnedHandle(ret);
+}
+
+
+
+/*****************************************************************************
+ * Start the agent. */
+
+static OwnedHandle createEvent() {
+ // manual reset, initially unset
+ HANDLE h = CreateEventW(nullptr, TRUE, FALSE, nullptr);
+ if (h == nullptr) {
+ throwWindowsError(L"CreateEventW failed");
+ }
+ return OwnedHandle(h);
+}
+
+// For debugging purposes, provide a way to keep the console on the main window
+// station, visible.
+static bool shouldShowConsoleWindow() {
+ char buf[32];
+ return GetEnvironmentVariableA("WINPTY_SHOW_CONSOLE", buf, sizeof(buf)) > 0;
+}
+
+static bool shouldCreateBackgroundDesktop(bool &createUsingAgent) {
+ // Prior to Windows 7, winpty's repeated selection-deselection loop
+ // prevented the user from interacting with their *visible* console
+ // windows, unless we placed the console onto a background desktop.
+ // The SetProcessWindowStation call interferes with the clipboard and
+ // isn't thread-safe, though[1]. The call should perhaps occur in a
+ // special agent subprocess. Spawning a process in a background desktop
+ // also breaks ConEmu, but marking the process SW_HIDE seems to correct
+ // that[2].
+ //
+ // Windows 7 moved a lot of console handling out of csrss.exe and into
+ // a per-console conhost.exe process, which may explain why it isn't
+ // affected.
+ //
+ // This is a somewhat risky change, so there are low-level flags to
+ // assist in debugging if there are issues.
+ //
+ // [1] https://github.com/rprichard/winpty/issues/58
+ // [2] https://github.com/rprichard/winpty/issues/70
+ bool ret = !shouldShowConsoleWindow() && !isAtLeastWindows7();
+ const bool force = hasDebugFlag("force_desktop");
+ const bool force_spawn = hasDebugFlag("force_desktop_spawn");
+ const bool force_curproc = hasDebugFlag("force_desktop_curproc");
+ const bool suppress = hasDebugFlag("no_desktop");
+ if (force + force_spawn + force_curproc + suppress > 1) {
+ trace("error: Only one of force_desktop, force_desktop_spawn, "
+ "force_desktop_curproc, and no_desktop may be set");
+ } else if (force) {
+ ret = true;
+ } else if (force_spawn) {
+ ret = true;
+ createUsingAgent = true;
+ } else if (force_curproc) {
+ ret = true;
+ createUsingAgent = false;
+ } else if (suppress) {
+ ret = false;
+ }
+ return ret;
+}
+
+static bool shouldSpecifyHideFlag() {
+ const bool force = hasDebugFlag("force_sw_hide");
+ const bool suppress = hasDebugFlag("no_sw_hide");
+ bool ret = !shouldShowConsoleWindow();
+ if (force && suppress) {
+ trace("error: Both the force_sw_hide and no_sw_hide flags are set");
+ } else if (force) {
+ ret = true;
+ } else if (suppress) {
+ ret = false;
+ }
+ return ret;
+}
+
+static OwnedHandle startAgentProcess(
+ const std::wstring &desktop,
+ const std::wstring &controlPipeName,
+ const std::wstring &params,
+ DWORD creationFlags,
+ DWORD &agentPid) {
+ const std::wstring exePath = findAgentProgram();
+ const std::wstring cmdline =
+ (WStringBuilder(256)
+ << L"\"" << exePath << L"\" "
+ << controlPipeName << L' '
+ << params).str_moved();
+
+ auto cmdlineV = vectorWithNulFromString(cmdline);
+ auto desktopV = vectorWithNulFromString(desktop);
+
+ // Start the agent.
+ STARTUPINFOW sui = {};
+ sui.cb = sizeof(sui);
+ sui.lpDesktop = desktop.empty() ? nullptr : desktopV.data();
+
+ if (shouldSpecifyHideFlag()) {
+ sui.dwFlags |= STARTF_USESHOWWINDOW;
+ sui.wShowWindow = SW_HIDE;
+ }
+ PROCESS_INFORMATION pi = {};
+ const BOOL success =
+ CreateProcessW(exePath.c_str(),
+ cmdlineV.data(),
+ nullptr, nullptr,
+ /*bInheritHandles=*/FALSE,
+ /*dwCreationFlags=*/creationFlags,
+ nullptr, nullptr,
+ &sui, &pi);
+ if (!success) {
+ const DWORD lastError = GetLastError();
+ const auto errStr =
+ (WStringBuilder(256)
+ << L"winpty-agent CreateProcess failed: cmdline='" << cmdline
+ << L"' err=0x" << whexOfInt(lastError)).str_moved();
+ throw LibWinptyException(
+ WINPTY_ERROR_AGENT_CREATION_FAILED, errStr.c_str());
+ }
+ CloseHandle(pi.hThread);
+ TRACE("Created agent successfully, pid=%u, cmdline=%s",
+ static_cast<unsigned int>(pi.dwProcessId),
+ utf8FromWide(cmdline).c_str());
+ agentPid = pi.dwProcessId;
+ return OwnedHandle(pi.hProcess);
+}
+
+static void verifyPipeClientPid(HANDLE serverPipe, DWORD agentPid) {
+ const auto client = getNamedPipeClientProcessId(serverPipe);
+ const auto success = std::get<0>(client);
+ const auto lastError = std::get<2>(client);
+ if (success == GetNamedPipeClientProcessId_Result::Success) {
+ const auto clientPid = std::get<1>(client);
+ if (clientPid != agentPid) {
+ WStringBuilder errMsg;
+ errMsg << L"Security check failed: pipe client pid (" << clientPid
+ << L") does not match agent pid (" << agentPid << L")";
+ throwWinptyException(errMsg.c_str());
+ }
+ } else if (success == GetNamedPipeClientProcessId_Result::UnsupportedOs) {
+ trace("Pipe client PID security check skipped: "
+ "GetNamedPipeClientProcessId unsupported on this OS version");
+ } else {
+ throwWindowsError(L"GetNamedPipeClientProcessId failed", lastError);
+ }
+}
+
+static std::unique_ptr<winpty_t>
+createAgentSession(const winpty_config_t *cfg,
+ const std::wstring &desktop,
+ const std::wstring &params,
+ DWORD creationFlags) {
+ std::unique_ptr<winpty_t> wp(new winpty_t);
+ wp->agentTimeoutMs = cfg->timeoutMs;
+ wp->ioEvent = createEvent();
+
+ // Create control server pipe.
+ const auto pipeName =
+ L"\\\\.\\pipe\\winpty-control-" + GenRandom().uniqueName();
+ wp->controlPipe = createControlPipe(pipeName);
+
+ DWORD agentPid = 0;
+ wp->agentProcess = startAgentProcess(
+ desktop, pipeName, params, creationFlags, agentPid);
+ connectControlPipe(*wp.get());
+ verifyPipeClientPid(wp->controlPipe.get(), agentPid);
+
+ return std::move(wp);
+}
+
+namespace {
+
+class AgentDesktop {
+public:
+ virtual std::wstring name() = 0;
+ virtual ~AgentDesktop() {}
+};
+
+class AgentDesktopDirect : public AgentDesktop {
+public:
+ AgentDesktopDirect(BackgroundDesktop &&desktop) :
+ m_desktop(std::move(desktop))
+ {
+ }
+ std::wstring name() override { return m_desktop.desktopName(); }
+private:
+ BackgroundDesktop m_desktop;
+};
+
+class AgentDesktopIndirect : public AgentDesktop {
+public:
+ AgentDesktopIndirect(std::unique_ptr<winpty_t> &&wp,
+ std::wstring &&desktopName) :
+ m_wp(std::move(wp)),
+ m_desktopName(std::move(desktopName))
+ {
+ }
+ std::wstring name() override { return m_desktopName; }
+private:
+ std::unique_ptr<winpty_t> m_wp;
+ std::wstring m_desktopName;
+};
+
+} // anonymous namespace
+
+std::unique_ptr<AgentDesktop>
+setupBackgroundDesktop(const winpty_config_t *cfg) {
+ bool useDesktopAgent =
+ !(cfg->flags & WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION);
+ const bool useDesktop = shouldCreateBackgroundDesktop(useDesktopAgent);
+
+ if (!useDesktop) {
+ return std::unique_ptr<AgentDesktop>();
+ }
+
+ if (useDesktopAgent) {
+ auto wp = createAgentSession(
+ cfg, std::wstring(), L"--create-desktop", DETACHED_PROCESS);
+
+ // Read the desktop name.
+ auto packet = readPacket(*wp.get());
+ auto desktopName = packet.getWString();
+ packet.assertEof();
+
+ if (desktopName.empty()) {
+ return std::unique_ptr<AgentDesktop>();
+ } else {
+ return std::unique_ptr<AgentDesktop>(
+ new AgentDesktopIndirect(std::move(wp),
+ std::move(desktopName)));
+ }
+ } else {
+ try {
+ BackgroundDesktop desktop;
+ return std::unique_ptr<AgentDesktop>(new AgentDesktopDirect(
+ std::move(desktop)));
+ } catch (const WinptyException &e) {
+ trace("Error: failed to create background desktop, "
+ "using original desktop instead: %s",
+ utf8FromWide(e.what()).c_str());
+ return std::unique_ptr<AgentDesktop>();
+ }
+ }
+}
+
+WINPTY_API winpty_t *
+winpty_open(const winpty_config_t *cfg,
+ winpty_error_ptr_t *err /*OPTIONAL*/) {
+ API_TRY {
+ ASSERT(cfg != nullptr);
+ dumpWindowsVersion();
+ dumpVersionToTrace();
+
+ // Setup a background desktop for the agent.
+ auto desktop = setupBackgroundDesktop(cfg);
+ const auto desktopName = desktop ? desktop->name() : std::wstring();
+
+ // Start the primary agent session.
+ const auto params =
+ (WStringBuilder(128)
+ << cfg->flags << L' '
+ << cfg->mouseMode << L' '
+ << cfg->cols << L' '
+ << cfg->rows).str_moved();
+ auto wp = createAgentSession(cfg, desktopName, params,
+ CREATE_NEW_CONSOLE);
+
+ // Close handles to the background desktop and restore the original
+ // window station. This must wait until we know the agent is running
+ // -- if we close these handles too soon, then the desktop and
+ // windowstation will be destroyed before the agent can connect with
+ // them.
+ //
+ // If we used a separate agent process to create the desktop, we
+ // disconnect from that process here, allowing it to exit.
+ desktop.reset();
+
+ // If we ran the agent process on a background desktop, then when we
+ // spawn a child process from the agent, it will need to be explicitly
+ // placed back onto the original desktop.
+ if (!desktopName.empty()) {
+ wp->spawnDesktopName = getCurrentDesktopName();
+ }
+
+ // Get the CONIN/CONOUT pipe names.
+ auto packet = readPacket(*wp.get());
+ wp->coninPipeName = packet.getWString();
+ wp->conoutPipeName = packet.getWString();
+ if (cfg->flags & WINPTY_FLAG_CONERR) {
+ wp->conerrPipeName = packet.getWString();
+ }
+ packet.assertEof();
+
+ return wp.release();
+ } API_CATCH(nullptr)
+}
+
+WINPTY_API HANDLE winpty_agent_process(winpty_t *wp) {
+ ASSERT(wp != nullptr);
+ return wp->agentProcess.get();
+}
+
+
+
+/*****************************************************************************
+ * I/O pipes. */
+
+static const wchar_t *cstrFromWStringOrNull(const std::wstring &str) {
+ try {
+ return str.c_str();
+ } catch (const std::bad_alloc&) {
+ return nullptr;
+ }
+}
+
+WINPTY_API LPCWSTR winpty_conin_name(winpty_t *wp) {
+ ASSERT(wp != nullptr);
+ return cstrFromWStringOrNull(wp->coninPipeName);
+}
+
+WINPTY_API LPCWSTR winpty_conout_name(winpty_t *wp) {
+ ASSERT(wp != nullptr);
+ return cstrFromWStringOrNull(wp->conoutPipeName);
+}
+
+WINPTY_API LPCWSTR winpty_conerr_name(winpty_t *wp) {
+ ASSERT(wp != nullptr);
+ if (wp->conerrPipeName.empty()) {
+ return nullptr;
+ } else {
+ return cstrFromWStringOrNull(wp->conerrPipeName);
+ }
+}
+
+
+
+/*****************************************************************************
+ * winpty agent RPC calls. */
+
+namespace {
+
+// Close the control pipe if something goes wrong with the pipe communication,
+// which could leave the control pipe in an inconsistent state.
+class RpcOperation {
+public:
+ RpcOperation(winpty_t &wp) : m_wp(wp) {
+ if (m_wp.controlPipe.get() == nullptr) {
+ throwWinptyException(L"Agent shutdown due to RPC failure");
+ }
+ }
+ ~RpcOperation() {
+ if (!m_success) {
+ trace("~RpcOperation: Closing control pipe");
+ m_wp.controlPipe.dispose(true);
+ }
+ }
+ void success() { m_success = true; }
+private:
+ winpty_t &m_wp;
+ bool m_success = false;
+};
+
+} // anonymous namespace
+
+
+
+/*****************************************************************************
+ * winpty agent RPC call: process creation. */
+
+// Return a std::wstring containing every character of the environment block.
+// Typically, the block is non-empty, so the std::wstring returned ends with
+// two NUL terminators. (These two terminators are counted in size(), so
+// calling c_str() produces a triply-terminated string.)
+static std::wstring wstringFromEnvBlock(const wchar_t *env) {
+ std::wstring envStr;
+ if (env != NULL) {
+ const wchar_t *p = env;
+ while (*p != L'\0') {
+ p += wcslen(p) + 1;
+ }
+ p++;
+ envStr.assign(env, p);
+
+ // Assuming the environment was non-empty, envStr now ends with two NUL
+ // terminators.
+ //
+ // If the environment were empty, though, then envStr would only be
+ // singly terminated, but the MSDN documentation thinks an env block is
+ // always doubly-terminated, so add an extra NUL just in case it
+ // matters.
+ const auto envStrSz = envStr.size();
+ if (envStrSz == 1) {
+ ASSERT(envStr[0] == L'\0');
+ envStr.push_back(L'\0');
+ } else {
+ ASSERT(envStrSz >= 3);
+ ASSERT(envStr[envStrSz - 3] != L'\0');
+ ASSERT(envStr[envStrSz - 2] == L'\0');
+ ASSERT(envStr[envStrSz - 1] == L'\0');
+ }
+ }
+ return envStr;
+}
+
+WINPTY_API winpty_spawn_config_t *
+winpty_spawn_config_new(UINT64 winptyFlags,
+ LPCWSTR appname /*OPTIONAL*/,
+ LPCWSTR cmdline /*OPTIONAL*/,
+ LPCWSTR cwd /*OPTIONAL*/,
+ LPCWSTR env /*OPTIONAL*/,
+ winpty_error_ptr_t *err /*OPTIONAL*/) {
+ API_TRY {
+ ASSERT((winptyFlags & WINPTY_SPAWN_FLAG_MASK) == winptyFlags);
+ std::unique_ptr<winpty_spawn_config_t> cfg(new winpty_spawn_config_t);
+ cfg->winptyFlags = winptyFlags;
+ if (appname != nullptr) { cfg->appname = appname; }
+ if (cmdline != nullptr) { cfg->cmdline = cmdline; }
+ if (cwd != nullptr) { cfg->cwd = cwd; }
+ if (env != nullptr) { cfg->env = wstringFromEnvBlock(env); }
+ return cfg.release();
+ } API_CATCH(nullptr)
+}
+
+WINPTY_API void winpty_spawn_config_free(winpty_spawn_config_t *cfg) {
+ delete cfg;
+}
+
+// It's safe to truncate a handle from 64-bits to 32-bits, or to sign-extend it
+// back to 64-bits. See the MSDN article, "Interprocess Communication Between
+// 32-bit and 64-bit Applications".
+// https://msdn.microsoft.com/en-us/library/windows/desktop/aa384203.aspx
+static inline HANDLE handleFromInt64(int64_t i) {
+ return reinterpret_cast<HANDLE>(static_cast<intptr_t>(i));
+}
+
+// Given a process and a handle in that process, duplicate the handle into the
+// current process and close it in the originating process.
+static inline OwnedHandle stealHandle(HANDLE process, HANDLE handle) {
+ HANDLE result = nullptr;
+ if (!DuplicateHandle(process, handle,
+ GetCurrentProcess(),
+ &result, 0, FALSE,
+ DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) {
+ throwWindowsError(L"DuplicateHandle of process handle");
+ }
+ return OwnedHandle(result);
+}
+
+WINPTY_API BOOL
+winpty_spawn(winpty_t *wp,
+ const winpty_spawn_config_t *cfg,
+ HANDLE *process_handle /*OPTIONAL*/,
+ HANDLE *thread_handle /*OPTIONAL*/,
+ DWORD *create_process_error /*OPTIONAL*/,
+ winpty_error_ptr_t *err /*OPTIONAL*/) {
+ API_TRY {
+ ASSERT(wp != nullptr && cfg != nullptr);
+
+ if (process_handle != nullptr) { *process_handle = nullptr; }
+ if (thread_handle != nullptr) { *thread_handle = nullptr; }
+ if (create_process_error != nullptr) { *create_process_error = 0; }
+
+ LockGuard<Mutex> lock(wp->mutex);
+ RpcOperation rpc(*wp);
+
+ // Send spawn request.
+ auto packet = newPacket();
+ packet.putInt32(AgentMsg::StartProcess);
+ packet.putInt64(cfg->winptyFlags);
+ packet.putInt32(process_handle != nullptr);
+ packet.putInt32(thread_handle != nullptr);
+ packet.putWString(cfg->appname);
+ packet.putWString(cfg->cmdline);
+ packet.putWString(cfg->cwd);
+ packet.putWString(cfg->env);
+ packet.putWString(wp->spawnDesktopName);
+ writePacket(*wp, packet);
+
+ // Receive reply.
+ auto reply = readPacket(*wp);
+ const auto result = static_cast<StartProcessResult>(reply.getInt32());
+ if (result == StartProcessResult::CreateProcessFailed) {
+ const DWORD lastError = reply.getInt32();
+ reply.assertEof();
+ if (create_process_error != nullptr) {
+ *create_process_error = lastError;
+ }
+ rpc.success();
+ throw LibWinptyException(WINPTY_ERROR_SPAWN_CREATE_PROCESS_FAILED,
+ L"CreateProcess failed");
+ } else if (result == StartProcessResult::ProcessCreated) {
+ const HANDLE remoteProcess = handleFromInt64(reply.getInt64());
+ const HANDLE remoteThread = handleFromInt64(reply.getInt64());
+ reply.assertEof();
+ OwnedHandle localProcess;
+ OwnedHandle localThread;
+ if (remoteProcess != nullptr) {
+ localProcess =
+ stealHandle(wp->agentProcess.get(), remoteProcess);
+ }
+ if (remoteThread != nullptr) {
+ localThread =
+ stealHandle(wp->agentProcess.get(), remoteThread);
+ }
+ if (process_handle != nullptr) {
+ *process_handle = localProcess.release();
+ }
+ if (thread_handle != nullptr) {
+ *thread_handle = localThread.release();
+ }
+ rpc.success();
+ } else {
+ throwWinptyException(
+ L"Agent RPC error: invalid StartProcessResult");
+ }
+ return TRUE;
+ } API_CATCH(FALSE)
+}
+
+
+
+/*****************************************************************************
+ * winpty agent RPC calls: everything else */
+
+WINPTY_API BOOL
+winpty_set_size(winpty_t *wp, int cols, int rows,
+ winpty_error_ptr_t *err /*OPTIONAL*/) {
+ API_TRY {
+ ASSERT(wp != nullptr && cols > 0 && rows > 0);
+ LockGuard<Mutex> lock(wp->mutex);
+ RpcOperation rpc(*wp);
+ auto packet = newPacket();
+ packet.putInt32(AgentMsg::SetSize);
+ packet.putInt32(cols);
+ packet.putInt32(rows);
+ writePacket(*wp, packet);
+ readPacket(*wp).assertEof();
+ rpc.success();
+ return TRUE;
+ } API_CATCH(FALSE)
+}
+
+WINPTY_API int
+winpty_get_console_process_list(winpty_t *wp, int *processList, const int processCount,
+ winpty_error_ptr_t *err /*OPTIONAL*/) {
+ API_TRY {
+ ASSERT(wp != nullptr);
+ ASSERT(processList != nullptr);
+ LockGuard<Mutex> lock(wp->mutex);
+ RpcOperation rpc(*wp);
+ auto packet = newPacket();
+ packet.putInt32(AgentMsg::GetConsoleProcessList);
+ writePacket(*wp, packet);
+ auto reply = readPacket(*wp);
+
+ auto actualProcessCount = reply.getInt32();
+
+ if (actualProcessCount <= processCount) {
+ for (auto i = 0; i < actualProcessCount; i++) {
+ processList[i] = reply.getInt32();
+ }
+ }
+
+ reply.assertEof();
+ rpc.success();
+ return actualProcessCount;
+ } API_CATCH(0)
+}
+
+WINPTY_API void winpty_free(winpty_t *wp) {
+ // At least in principle, CloseHandle can fail, so this deletion can
+ // fail. It won't throw an exception, but maybe there's an error that
+ // should be propagated?
+ delete wp;
+}
diff --git a/src/libs/3rdparty/winpty/src/shared/AgentMsg.h b/src/libs/3rdparty/winpty/src/shared/AgentMsg.h
new file mode 100644
index 00000000000..ab60c6b9619
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/AgentMsg.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2011-2012 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef WINPTY_SHARED_AGENT_MSG_H
+#define WINPTY_SHARED_AGENT_MSG_H
+
+struct AgentMsg
+{
+ enum Type {
+ StartProcess,
+ SetSize,
+ GetConsoleProcessList,
+ };
+};
+
+enum class StartProcessResult {
+ CreateProcessFailed,
+ ProcessCreated,
+};
+
+#endif // WINPTY_SHARED_AGENT_MSG_H
diff --git a/src/libs/3rdparty/winpty/src/shared/BackgroundDesktop.cc b/src/libs/3rdparty/winpty/src/shared/BackgroundDesktop.cc
new file mode 100644
index 00000000000..1bea7e53dd4
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/BackgroundDesktop.cc
@@ -0,0 +1,122 @@
+// Copyright (c) 2011-2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include "BackgroundDesktop.h"
+
+#include <memory>
+
+#include "DebugClient.h"
+#include "StringUtil.h"
+#include "WinptyException.h"
+
+namespace {
+
+static std::wstring getObjectName(HANDLE object) {
+ BOOL success;
+ DWORD lengthNeeded = 0;
+ GetUserObjectInformationW(object, UOI_NAME,
+ nullptr, 0,
+ &lengthNeeded);
+ ASSERT(lengthNeeded % sizeof(wchar_t) == 0);
+ std::unique_ptr<wchar_t[]> tmp(
+ new wchar_t[lengthNeeded / sizeof(wchar_t)]);
+ success = GetUserObjectInformationW(object, UOI_NAME,
+ tmp.get(), lengthNeeded,
+ nullptr);
+ if (!success) {
+ throwWindowsError(L"GetUserObjectInformationW failed");
+ }
+ return std::wstring(tmp.get());
+}
+
+static std::wstring getDesktopName(HWINSTA winsta, HDESK desk) {
+ return getObjectName(winsta) + L"\\" + getObjectName(desk);
+}
+
+} // anonymous namespace
+
+// Get a non-interactive window station for the agent.
+// TODO: review security w.r.t. windowstation and desktop.
+BackgroundDesktop::BackgroundDesktop() {
+ try {
+ m_originalStation = GetProcessWindowStation();
+ if (m_originalStation == nullptr) {
+ throwWindowsError(
+ L"BackgroundDesktop ctor: "
+ L"GetProcessWindowStation returned NULL");
+ }
+ m_newStation =
+ CreateWindowStationW(nullptr, 0, WINSTA_ALL_ACCESS, nullptr);
+ if (m_newStation == nullptr) {
+ throwWindowsError(
+ L"BackgroundDesktop ctor: CreateWindowStationW returned NULL");
+ }
+ if (!SetProcessWindowStation(m_newStation)) {
+ throwWindowsError(
+ L"BackgroundDesktop ctor: SetProcessWindowStation failed");
+ }
+ m_newDesktop = CreateDesktopW(
+ L"Default", nullptr, nullptr, 0, GENERIC_ALL, nullptr);
+ if (m_newDesktop == nullptr) {
+ throwWindowsError(
+ L"BackgroundDesktop ctor: CreateDesktopW failed");
+ }
+ m_newDesktopName = getDesktopName(m_newStation, m_newDesktop);
+ TRACE("Created background desktop: %s",
+ utf8FromWide(m_newDesktopName).c_str());
+ } catch (...) {
+ dispose();
+ throw;
+ }
+}
+
+void BackgroundDesktop::dispose() WINPTY_NOEXCEPT {
+ if (m_originalStation != nullptr) {
+ SetProcessWindowStation(m_originalStation);
+ m_originalStation = nullptr;
+ }
+ if (m_newDesktop != nullptr) {
+ CloseDesktop(m_newDesktop);
+ m_newDesktop = nullptr;
+ }
+ if (m_newStation != nullptr) {
+ CloseWindowStation(m_newStation);
+ m_newStation = nullptr;
+ }
+}
+
+std::wstring getCurrentDesktopName() {
+ // MSDN says that the handles returned by GetProcessWindowStation and
+ // GetThreadDesktop do not need to be passed to CloseWindowStation and
+ // CloseDesktop, respectively.
+ const HWINSTA winsta = GetProcessWindowStation();
+ if (winsta == nullptr) {
+ throwWindowsError(
+ L"getCurrentDesktopName: "
+ L"GetProcessWindowStation returned NULL");
+ }
+ const HDESK desk = GetThreadDesktop(GetCurrentThreadId());
+ if (desk == nullptr) {
+ throwWindowsError(
+ L"getCurrentDesktopName: "
+ L"GetThreadDesktop returned NULL");
+ }
+ return getDesktopName(winsta, desk);
+}
diff --git a/src/libs/3rdparty/winpty/src/shared/BackgroundDesktop.h b/src/libs/3rdparty/winpty/src/shared/BackgroundDesktop.h
new file mode 100644
index 00000000000..c692e57dc49
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/BackgroundDesktop.h
@@ -0,0 +1,73 @@
+// Copyright (c) 2011-2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef WINPTY_SHARED_BACKGROUND_DESKTOP_H
+#define WINPTY_SHARED_BACKGROUND_DESKTOP_H
+
+#include <windows.h>
+
+#include <string>
+
+#include "WinptyException.h"
+
+class BackgroundDesktop {
+public:
+ BackgroundDesktop();
+ ~BackgroundDesktop() { dispose(); }
+ void dispose() WINPTY_NOEXCEPT;
+ const std::wstring &desktopName() const { return m_newDesktopName; }
+
+ BackgroundDesktop(const BackgroundDesktop &other) = delete;
+ BackgroundDesktop &operator=(const BackgroundDesktop &other) = delete;
+
+ // We can't default the move constructor and assignment operator with
+ // MSVC 2013. We *could* if we required at least MSVC 2015 to build.
+
+ BackgroundDesktop(BackgroundDesktop &&other) :
+ m_originalStation(other.m_originalStation),
+ m_newStation(other.m_newStation),
+ m_newDesktop(other.m_newDesktop),
+ m_newDesktopName(std::move(other.m_newDesktopName)) {
+ other.m_originalStation = nullptr;
+ other.m_newStation = nullptr;
+ other.m_newDesktop = nullptr;
+ }
+ BackgroundDesktop &operator=(BackgroundDesktop &&other) {
+ dispose();
+ m_originalStation = other.m_originalStation;
+ m_newStation = other.m_newStation;
+ m_newDesktop = other.m_newDesktop;
+ m_newDesktopName = std::move(other.m_newDesktopName);
+ other.m_originalStation = nullptr;
+ other.m_newStation = nullptr;
+ other.m_newDesktop = nullptr;
+ return *this;
+ }
+
+private:
+ HWINSTA m_originalStation = nullptr;
+ HWINSTA m_newStation = nullptr;
+ HDESK m_newDesktop = nullptr;
+ std::wstring m_newDesktopName;
+};
+
+std::wstring getCurrentDesktopName();
+
+#endif // WINPTY_SHARED_BACKGROUND_DESKTOP_H
diff --git a/src/libs/3rdparty/winpty/src/shared/Buffer.cc b/src/libs/3rdparty/winpty/src/shared/Buffer.cc
new file mode 100644
index 00000000000..158a629d564
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/Buffer.cc
@@ -0,0 +1,103 @@
+// Copyright (c) 2011-2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include "Buffer.h"
+
+#include <stdint.h>
+
+#include "DebugClient.h"
+#include "WinptyAssert.h"
+
+// Define the READ_BUFFER_CHECK() macro. It *must* evaluate its condition,
+// exactly once.
+#define READ_BUFFER_CHECK(cond) \
+ do { \
+ if (!(cond)) { \
+ trace("decode error: %s", #cond); \
+ throw DecodeError(); \
+ } \
+ } while (false)
+
+enum class Piece : uint8_t { Int32, Int64, WString };
+
+void WriteBuffer::putRawData(const void *data, size_t len) {
+ const auto p = reinterpret_cast<const char*>(data);
+ m_buf.insert(m_buf.end(), p, p + len);
+}
+
+void WriteBuffer::replaceRawData(size_t pos, const void *data, size_t len) {
+ ASSERT(pos <= m_buf.size() && len <= m_buf.size() - pos);
+ const auto p = reinterpret_cast<const char*>(data);
+ std::copy(p, p + len, &m_buf[pos]);
+}
+
+void WriteBuffer::putInt32(int32_t i) {
+ putRawValue(Piece::Int32);
+ putRawValue(i);
+}
+
+void WriteBuffer::putInt64(int64_t i) {
+ putRawValue(Piece::Int64);
+ putRawValue(i);
+}
+
+// len is in characters, excluding NUL, i.e. the number of wchar_t elements
+void WriteBuffer::putWString(const wchar_t *str, size_t len) {
+ putRawValue(Piece::WString);
+ putRawValue(static_cast<uint64_t>(len));
+ putRawData(str, sizeof(wchar_t) * len);
+}
+
+void ReadBuffer::getRawData(void *data, size_t len) {
+ ASSERT(m_off <= m_buf.size());
+ READ_BUFFER_CHECK(len <= m_buf.size() - m_off);
+ const char *const inp = &m_buf[m_off];
+ std::copy(inp, inp + len, reinterpret_cast<char*>(data));
+ m_off += len;
+}
+
+int32_t ReadBuffer::getInt32() {
+ READ_BUFFER_CHECK(getRawValue<Piece>() == Piece::Int32);
+ return getRawValue<int32_t>();
+}
+
+int64_t ReadBuffer::getInt64() {
+ READ_BUFFER_CHECK(getRawValue<Piece>() == Piece::Int64);
+ return getRawValue<int64_t>();
+}
+
+std::wstring ReadBuffer::getWString() {
+ READ_BUFFER_CHECK(getRawValue<Piece>() == Piece::WString);
+ const uint64_t charLen = getRawValue<uint64_t>();
+ READ_BUFFER_CHECK(charLen <= SIZE_MAX / sizeof(wchar_t));
+ // To be strictly conforming, we can't use the convenient wstring
+ // constructor, because the string in m_buf mightn't be aligned.
+ std::wstring ret;
+ if (charLen > 0) {
+ const size_t byteLen = charLen * sizeof(wchar_t);
+ ret.resize(charLen);
+ getRawData(&ret[0], byteLen);
+ }
+ return ret;
+}
+
+void ReadBuffer::assertEof() {
+ READ_BUFFER_CHECK(m_off == m_buf.size());
+}
diff --git a/src/libs/3rdparty/winpty/src/shared/Buffer.h b/src/libs/3rdparty/winpty/src/shared/Buffer.h
new file mode 100644
index 00000000000..c2dd382e5b2
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/Buffer.h
@@ -0,0 +1,102 @@
+// Copyright (c) 2011-2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef WINPTY_SHARED_BUFFER_H
+#define WINPTY_SHARED_BUFFER_H
+
+#include <stdint.h>
+#include <string.h>
+
+#include <algorithm>
+#include <utility>
+#include <vector>
+#include <string>
+
+#include "WinptyException.h"
+
+class WriteBuffer {
+private:
+ std::vector<char> m_buf;
+
+public:
+ WriteBuffer() {}
+
+ template <typename T> void putRawValue(const T &t) {
+ putRawData(&t, sizeof(t));
+ }
+ template <typename T> void replaceRawValue(size_t pos, const T &t) {
+ replaceRawData(pos, &t, sizeof(t));
+ }
+
+ void putRawData(const void *data, size_t len);
+ void replaceRawData(size_t pos, const void *data, size_t len);
+ void putInt32(int32_t i);
+ void putInt64(int64_t i);
+ void putWString(const wchar_t *str, size_t len);
+ void putWString(const wchar_t *str) { putWString(str, wcslen(str)); }
+ void putWString(const std::wstring &str) { putWString(str.data(), str.size()); }
+ std::vector<char> &buf() { return m_buf; }
+
+ // MSVC 2013 does not generate these automatically, so help it out.
+ WriteBuffer(WriteBuffer &&other) : m_buf(std::move(other.m_buf)) {}
+ WriteBuffer &operator=(WriteBuffer &&other) {
+ m_buf = std::move(other.m_buf);
+ return *this;
+ }
+};
+
+class ReadBuffer {
+public:
+ class DecodeError : public WinptyException {
+ virtual const wchar_t *what() const WINPTY_NOEXCEPT override {
+ return L"DecodeError: RPC message decoding error";
+ }
+ };
+
+private:
+ std::vector<char> m_buf;
+ size_t m_off = 0;
+
+public:
+ explicit ReadBuffer(std::vector<char> &&buf) : m_buf(std::move(buf)) {}
+
+ template <typename T> T getRawValue() {
+ T ret = {};
+ getRawData(&ret, sizeof(ret));
+ return ret;
+ }
+
+ void getRawData(void *data, size_t len);
+ int32_t getInt32();
+ int64_t getInt64();
+ std::wstring getWString();
+ void assertEof();
+
+ // MSVC 2013 does not generate these automatically, so help it out.
+ ReadBuffer(ReadBuffer &&other) :
+ m_buf(std::move(other.m_buf)), m_off(other.m_off) {}
+ ReadBuffer &operator=(ReadBuffer &&other) {
+ m_buf = std::move(other.m_buf);
+ m_off = other.m_off;
+ return *this;
+ }
+};
+
+#endif // WINPTY_SHARED_BUFFER_H
diff --git a/src/libs/3rdparty/winpty/src/shared/DebugClient.cc b/src/libs/3rdparty/winpty/src/shared/DebugClient.cc
new file mode 100644
index 00000000000..bafe0c89541
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/DebugClient.cc
@@ -0,0 +1,187 @@
+// Copyright (c) 2011-2012 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include "DebugClient.h"
+
+#include <windows.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <algorithm>
+#include <string>
+
+#include "winpty_snprintf.h"
+
+const wchar_t *const kPipeName = L"\\\\.\\pipe\\DebugServer";
+
+void *volatile g_debugConfig;
+
+namespace {
+
+// It would be easy to accidentally trample on the Windows LastError value
+// by adding logging/debugging code. Ensure that can't happen by saving and
+// restoring the value. This saving and restoring doesn't happen along the
+// fast path.
+class PreserveLastError {
+public:
+ PreserveLastError() : m_lastError(GetLastError()) {}
+ ~PreserveLastError() { SetLastError(m_lastError); }
+private:
+ DWORD m_lastError;
+};
+
+} // anonymous namespace
+
+static void sendToDebugServer(const char *message)
+{
+ HANDLE tracePipe = INVALID_HANDLE_VALUE;
+
+ do {
+ // The default impersonation level is SECURITY_IMPERSONATION, which allows
+ // a sufficiently authorized named pipe server to impersonate the client.
+ // There's no need for impersonation in this debugging system, so reduce
+ // the impersonation level to SECURITY_IDENTIFICATION, which allows a
+ // server to merely identify us.
+ tracePipe = CreateFileW(
+ kPipeName,
+ GENERIC_READ | GENERIC_WRITE,
+ 0, NULL, OPEN_EXISTING,
+ SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION,
+ NULL);
+ } while (tracePipe == INVALID_HANDLE_VALUE &&
+ GetLastError() == ERROR_PIPE_BUSY &&
+ WaitNamedPipeW(kPipeName, NMPWAIT_WAIT_FOREVER));
+
+ if (tracePipe != INVALID_HANDLE_VALUE) {
+ DWORD newMode = PIPE_READMODE_MESSAGE;
+ SetNamedPipeHandleState(tracePipe, &newMode, NULL, NULL);
+ char response[16];
+ DWORD actual = 0;
+ TransactNamedPipe(tracePipe,
+ const_cast<char*>(message), strlen(message),
+ response, sizeof(response), &actual, NULL);
+ CloseHandle(tracePipe);
+ }
+}
+
+// Get the current UTC time as milliseconds from the epoch (ignoring leap
+// seconds). Use the Unix epoch for consistency with DebugClient.py. There
+// are 134774 days between 1601-01-01 (the Win32 epoch) and 1970-01-01 (the
+// Unix epoch).
+static long long unixTimeMillis()
+{
+ FILETIME fileTime;
+ GetSystemTimeAsFileTime(&fileTime);
+ long long msTime = (((long long)fileTime.dwHighDateTime << 32) +
+ fileTime.dwLowDateTime) / 10000;
+ return msTime - 134774LL * 24 * 3600 * 1000;
+}
+
+static const char *getDebugConfig()
+{
+ if (g_debugConfig == NULL) {
+ PreserveLastError preserve;
+ const int bufSize = 256;
+ char buf[bufSize];
+ DWORD actualSize =
+ GetEnvironmentVariableA("WINPTY_DEBUG", buf, bufSize);
+ if (actualSize == 0 || actualSize >= static_cast<DWORD>(bufSize)) {
+ buf[0] = '\0';
+ }
+ const size_t len = strlen(buf) + 1;
+ char *newConfig = new char[len];
+ std::copy(buf, buf + len, newConfig);
+ void *oldValue = InterlockedCompareExchangePointer(
+ &g_debugConfig, newConfig, NULL);
+ if (oldValue != NULL) {
+ delete [] newConfig;
+ }
+ }
+ return static_cast<const char*>(g_debugConfig);
+}
+
+bool isTracingEnabled()
+{
+ static bool disabled, enabled;
+ if (disabled) {
+ return false;
+ } else if (enabled) {
+ return true;
+ } else {
+ // Recognize WINPTY_DEBUG=1 for backwards compatibility.
+ PreserveLastError preserve;
+ bool value = hasDebugFlag("trace") || hasDebugFlag("1");
+ disabled = !value;
+ enabled = value;
+ return value;
+ }
+}
+
+bool hasDebugFlag(const char *flag)
+{
+ if (strchr(flag, ',') != NULL) {
+ trace("INTERNAL ERROR: hasDebugFlag flag has comma: '%s'", flag);
+ abort();
+ }
+ const char *const configCStr = getDebugConfig();
+ if (configCStr[0] == '\0') {
+ return false;
+ }
+ PreserveLastError preserve;
+ std::string config(configCStr);
+ std::string flagStr(flag);
+ config = "," + config + ",";
+ flagStr = "," + flagStr + ",";
+ return config.find(flagStr) != std::string::npos;
+}
+
+void trace(const char *format, ...)
+{
+ if (!isTracingEnabled())
+ return;
+
+ PreserveLastError preserve;
+ char message[1024];
+
+ va_list ap;
+ va_start(ap, format);
+ winpty_vsnprintf(message, format, ap);
+ message[sizeof(message) - 1] = '\0';
+ va_end(ap);
+
+ const int currentTime = (int)(unixTimeMillis() % (100000 * 1000));
+
+ char moduleName[1024];
+ moduleName[0] = '\0';
+ GetModuleFileNameA(NULL, moduleName, sizeof(moduleName));
+ const char *baseName = strrchr(moduleName, '\\');
+ baseName = (baseName != NULL) ? baseName + 1 : moduleName;
+
+ char fullMessage[1024];
+ winpty_snprintf(fullMessage,
+ "[%05d.%03d %s,p%04d,t%04d]: %s",
+ currentTime / 1000, currentTime % 1000,
+ baseName, (int)GetCurrentProcessId(), (int)GetCurrentThreadId(),
+ message);
+ fullMessage[sizeof(fullMessage) - 1] = '\0';
+
+ sendToDebugServer(fullMessage);
+}
diff --git a/src/libs/3rdparty/winpty/src/shared/DebugClient.h b/src/libs/3rdparty/winpty/src/shared/DebugClient.h
new file mode 100644
index 00000000000..b1260711301
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/DebugClient.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2011-2012 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef DEBUGCLIENT_H
+#define DEBUGCLIENT_H
+
+#include "winpty_snprintf.h"
+
+bool isTracingEnabled();
+bool hasDebugFlag(const char *flag);
+void trace(const char *format, ...) WINPTY_SNPRINTF_FORMAT(1, 2);
+
+// This macro calls trace without evaluating the arguments.
+#define TRACE(format, ...) \
+ do { \
+ if (isTracingEnabled()) { \
+ trace((format), ## __VA_ARGS__); \
+ } \
+ } while (false)
+
+#endif // DEBUGCLIENT_H
diff --git a/src/libs/3rdparty/winpty/src/shared/GenRandom.cc b/src/libs/3rdparty/winpty/src/shared/GenRandom.cc
new file mode 100644
index 00000000000..6d7920643af
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/GenRandom.cc
@@ -0,0 +1,138 @@
+// Copyright (c) 2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include "GenRandom.h"
+
+#include <stdint.h>
+#include <string.h>
+
+#include "DebugClient.h"
+#include "StringBuilder.h"
+
+static volatile LONG g_pipeCounter;
+
+GenRandom::GenRandom() : m_advapi32(L"advapi32.dll") {
+ // First try to use the pseudo-documented RtlGenRandom function from
+ // advapi32.dll. Creating a CryptoAPI context is slow, and RtlGenRandom
+ // avoids the overhead. It's documented in this blog post[1] and on
+ // MSDN[2] with a disclaimer about future breakage. This technique is
+ // apparently built-in into the MSVC CRT, though, for the rand_s function,
+ // so perhaps it is stable enough.
+ //
+ // [1] http://blogs.msdn.com/b/michael_howard/archive/2005/01/14/353379.aspx
+ // [2] https://msdn.microsoft.com/en-us/library/windows/desktop/aa387694(v=vs.85).aspx
+ //
+ // Both RtlGenRandom and the Crypto API functions exist in XP and up.
+ m_rtlGenRandom = reinterpret_cast<RtlGenRandom_t*>(
+ m_advapi32.proc("SystemFunction036"));
+ // The OsModule class logs an error message if the proc is nullptr.
+ if (m_rtlGenRandom != nullptr) {
+ return;
+ }
+
+ // Fall back to the crypto API.
+ m_cryptProvIsValid =
+ CryptAcquireContext(&m_cryptProv, nullptr, nullptr,
+ PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) != 0;
+ if (!m_cryptProvIsValid) {
+ trace("GenRandom: CryptAcquireContext failed: %u",
+ static_cast<unsigned>(GetLastError()));
+ }
+}
+
+GenRandom::~GenRandom() {
+ if (m_cryptProvIsValid) {
+ CryptReleaseContext(m_cryptProv, 0);
+ }
+}
+
+// Returns false if the context is invalid or the generation fails.
+bool GenRandom::fillBuffer(void *buffer, size_t size) {
+ memset(buffer, 0, size);
+ bool success = false;
+ if (m_rtlGenRandom != nullptr) {
+ success = m_rtlGenRandom(buffer, size) != 0;
+ if (!success) {
+ trace("GenRandom: RtlGenRandom/SystemFunction036 failed: %u",
+ static_cast<unsigned>(GetLastError()));
+ }
+ } else if (m_cryptProvIsValid) {
+ success =
+ CryptGenRandom(m_cryptProv, size,
+ reinterpret_cast<BYTE*>(buffer)) != 0;
+ if (!success) {
+ trace("GenRandom: CryptGenRandom failed, size=%d, lasterror=%u",
+ static_cast<int>(size),
+ static_cast<unsigned>(GetLastError()));
+ }
+ }
+ return success;
+}
+
+// Returns an empty string if either of CryptAcquireContext or CryptGenRandom
+// fail.
+std::string GenRandom::randomBytes(size_t numBytes) {
+ std::string ret(numBytes, '\0');
+ if (!fillBuffer(&ret[0], numBytes)) {
+ return std::string();
+ }
+ return ret;
+}
+
+std::wstring GenRandom::randomHexString(size_t numBytes) {
+ const std::string bytes = randomBytes(numBytes);
+ std::wstring ret(bytes.size() * 2, L'\0');
+ for (size_t i = 0; i < bytes.size(); ++i) {
+ static const wchar_t hex[] = L"0123456789abcdef";
+ ret[i * 2] = hex[static_cast<uint8_t>(bytes[i]) >> 4];
+ ret[i * 2 + 1] = hex[static_cast<uint8_t>(bytes[i]) & 0xF];
+ }
+ return ret;
+}
+
+// Returns a 64-bit value representing the number of 100-nanosecond intervals
+// since January 1, 1601.
+static uint64_t systemTimeAsUInt64() {
+ FILETIME monotonicTime = {};
+ GetSystemTimeAsFileTime(&monotonicTime);
+ return (static_cast<uint64_t>(monotonicTime.dwHighDateTime) << 32) |
+ static_cast<uint64_t>(monotonicTime.dwLowDateTime);
+}
+
+// Generates a unique and hard-to-guess case-insensitive string suitable for
+// use in a pipe filename or a Windows object name.
+std::wstring GenRandom::uniqueName() {
+ // First include enough information to avoid collisions assuming
+ // cooperative software. This code assumes that a process won't die and
+ // be replaced with a recycled PID within a single GetSystemTimeAsFileTime
+ // interval.
+ WStringBuilder sb(64);
+ sb << GetCurrentProcessId()
+ << L'-' << InterlockedIncrement(&g_pipeCounter)
+ << L'-' << whexOfInt(systemTimeAsUInt64());
+ // It isn't clear to me how the crypto APIs would fail. It *probably*
+ // doesn't matter that much anyway? In principle, a predictable pipe name
+ // is subject to a local denial-of-service attack.
+ auto random = randomHexString(16);
+ if (!random.empty()) {
+ sb << L'-' << random;
+ }
+ return sb.str_moved();
+}
diff --git a/src/libs/3rdparty/winpty/src/shared/GenRandom.h b/src/libs/3rdparty/winpty/src/shared/GenRandom.h
new file mode 100644
index 00000000000..746cb1ecf70
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/GenRandom.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef WINPTY_GEN_RANDOM_H
+#define WINPTY_GEN_RANDOM_H
+
+// The original MinGW requires that we include wincrypt.h. With MinGW-w64 and
+// MSVC, including windows.h is sufficient.
+#include <windows.h>
+#include <wincrypt.h>
+
+#include <string>
+
+#include "OsModule.h"
+
+class GenRandom {
+ typedef BOOLEAN WINAPI RtlGenRandom_t(PVOID, ULONG);
+
+ OsModule m_advapi32;
+ RtlGenRandom_t *m_rtlGenRandom = nullptr;
+ bool m_cryptProvIsValid = false;
+ HCRYPTPROV m_cryptProv = 0;
+
+public:
+ GenRandom();
+ ~GenRandom();
+ bool fillBuffer(void *buffer, size_t size);
+ std::string randomBytes(size_t numBytes);
+ std::wstring randomHexString(size_t numBytes);
+ std::wstring uniqueName();
+
+ // Return true if the crypto context was successfully initialized.
+ bool valid() const {
+ return m_rtlGenRandom != nullptr || m_cryptProvIsValid;
+ }
+};
+
+#endif // WINPTY_GEN_RANDOM_H
diff --git a/src/libs/3rdparty/winpty/src/shared/GetCommitHash.bat b/src/libs/3rdparty/winpty/src/shared/GetCommitHash.bat
new file mode 100644
index 00000000000..a9f8e9cef0f
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/GetCommitHash.bat
@@ -0,0 +1,13 @@
+@echo off
+
+REM -- Echo the git commit hash. If git isn't available for some reason,
+REM -- output nothing instead.
+
+git rev-parse HEAD >NUL 2>NUL && (
+ git rev-parse HEAD
+) || (
+ echo none
+)
+
+REM -- Set ERRORLEVEL to 0 using this cryptic syntax.
+(call )
diff --git a/src/libs/3rdparty/winpty/src/shared/Mutex.h b/src/libs/3rdparty/winpty/src/shared/Mutex.h
new file mode 100644
index 00000000000..98215365ad2
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/Mutex.h
@@ -0,0 +1,54 @@
+// Copyright (c) 2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+// Recent 4.x MinGW and MinGW-w64 gcc compilers lack std::mutex and
+// std::lock_guard. I have a 5.2.0 MinGW-w64 compiler packaged through MSYS2
+// that *is* new enough, but that's one compiler against several deficient
+// ones. Wrap CRITICAL_SECTION instead.
+
+#ifndef WINPTY_SHARED_MUTEX_H
+#define WINPTY_SHARED_MUTEX_H
+
+#include <windows.h>
+
+class Mutex {
+ CRITICAL_SECTION m_mutex;
+public:
+ Mutex() { InitializeCriticalSection(&m_mutex); }
+ ~Mutex() { DeleteCriticalSection(&m_mutex); }
+ void lock() { EnterCriticalSection(&m_mutex); }
+ void unlock() { LeaveCriticalSection(&m_mutex); }
+
+ Mutex(const Mutex &other) = delete;
+ Mutex &operator=(const Mutex &other) = delete;
+};
+
+template <typename T>
+class LockGuard {
+ T &m_lock;
+public:
+ LockGuard(T &lock) : m_lock(lock) { m_lock.lock(); }
+ ~LockGuard() { m_lock.unlock(); }
+
+ LockGuard(const LockGuard &other) = delete;
+ LockGuard &operator=(const LockGuard &other) = delete;
+};
+
+#endif // WINPTY_SHARED_MUTEX_H
diff --git a/src/libs/3rdparty/winpty/src/shared/OsModule.h b/src/libs/3rdparty/winpty/src/shared/OsModule.h
new file mode 100644
index 00000000000..9713fa2b2dd
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/OsModule.h
@@ -0,0 +1,63 @@
+// Copyright (c) 2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef WINPTY_SHARED_OS_MODULE_H
+#define WINPTY_SHARED_OS_MODULE_H
+
+#include <windows.h>
+
+#include <string>
+
+#include "DebugClient.h"
+#include "WinptyAssert.h"
+#include "WinptyException.h"
+
+class OsModule {
+ HMODULE m_module;
+public:
+ enum class LoadErrorBehavior { Abort, Throw };
+ OsModule(const wchar_t *fileName,
+ LoadErrorBehavior behavior=LoadErrorBehavior::Abort) {
+ m_module = LoadLibraryW(fileName);
+ if (behavior == LoadErrorBehavior::Abort) {
+ ASSERT(m_module != NULL);
+ } else {
+ if (m_module == nullptr) {
+ const auto err = GetLastError();
+ throwWindowsError(
+ (L"LoadLibraryW error: " + std::wstring(fileName)).c_str(),
+ err);
+ }
+ }
+ }
+ ~OsModule() {
+ FreeLibrary(m_module);
+ }
+ HMODULE handle() const { return m_module; }
+ FARPROC proc(const char *funcName) {
+ FARPROC ret = GetProcAddress(m_module, funcName);
+ if (ret == NULL) {
+ trace("GetProcAddress: %s is missing", funcName);
+ }
+ return ret;
+ }
+};
+
+#endif // WINPTY_SHARED_OS_MODULE_H
diff --git a/src/libs/3rdparty/winpty/src/shared/OwnedHandle.cc b/src/libs/3rdparty/winpty/src/shared/OwnedHandle.cc
new file mode 100644
index 00000000000..7b173536e6b
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/OwnedHandle.cc
@@ -0,0 +1,36 @@
+// Copyright (c) 2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include "OwnedHandle.h"
+
+#include "DebugClient.h"
+#include "WinptyException.h"
+
+void OwnedHandle::dispose(bool nothrow) {
+ if (m_h != nullptr && m_h != INVALID_HANDLE_VALUE) {
+ if (!CloseHandle(m_h)) {
+ trace("CloseHandle(%p) failed", m_h);
+ if (!nothrow) {
+ throwWindowsError(L"CloseHandle failed");
+ }
+ }
+ }
+ m_h = nullptr;
+}
diff --git a/src/libs/3rdparty/winpty/src/shared/OwnedHandle.h b/src/libs/3rdparty/winpty/src/shared/OwnedHandle.h
new file mode 100644
index 00000000000..70a8d6163a9
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/OwnedHandle.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef WINPTY_SHARED_OWNED_HANDLE_H
+#define WINPTY_SHARED_OWNED_HANDLE_H
+
+#include <windows.h>
+
+class OwnedHandle {
+ HANDLE m_h;
+public:
+ OwnedHandle() : m_h(nullptr) {}
+ explicit OwnedHandle(HANDLE h) : m_h(h) {}
+ ~OwnedHandle() { dispose(true); }
+ void dispose(bool nothrow=false);
+ HANDLE get() const { return m_h; }
+ HANDLE release() { HANDLE ret = m_h; m_h = nullptr; return ret; }
+ OwnedHandle(const OwnedHandle &other) = delete;
+ OwnedHandle(OwnedHandle &&other) : m_h(other.release()) {}
+ OwnedHandle &operator=(const OwnedHandle &other) = delete;
+ OwnedHandle &operator=(OwnedHandle &&other) {
+ dispose();
+ m_h = other.release();
+ return *this;
+ }
+};
+
+#endif // WINPTY_SHARED_OWNED_HANDLE_H
diff --git a/src/libs/3rdparty/winpty/src/shared/PrecompiledHeader.h b/src/libs/3rdparty/winpty/src/shared/PrecompiledHeader.h
new file mode 100644
index 00000000000..7d9b8f8b4af
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/PrecompiledHeader.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef WINPTY_PRECOMPILED_HEADER_H
+#define WINPTY_PRECOMPILED_HEADER_H
+
+#include <windows.h>
+
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+
+#include <array>
+#include <limits>
+#include <memory>
+#include <new>
+#include <string>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#endif // WINPTY_PRECOMPILED_HEADER_H
diff --git a/src/libs/3rdparty/winpty/src/shared/StringBuilder.h b/src/libs/3rdparty/winpty/src/shared/StringBuilder.h
new file mode 100644
index 00000000000..f3155bdd29e
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/StringBuilder.h
@@ -0,0 +1,227 @@
+// Copyright (c) 2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+// Efficient integer->string conversion and string concatenation. The
+// hexadecimal conversion may optionally have leading zeros. Other ways to
+// convert integers to strings in C++ suffer these drawbacks:
+//
+// * std::stringstream: Inefficient, even more so than stdio.
+//
+// * std::to_string: No hexadecimal output, tends to use heap allocation, not
+// supported on Cygwin.
+//
+// * stdio routines: Requires parsing a format string (inefficient). The
+// caller *must* know how large the content is for correctness. The
+// string-printf functions are extremely inconsistent on Windows. In
+// particular, 64-bit integers, wide strings, and return values are
+// problem areas.
+//
+// StringBuilderTest.cc is a standalone program that tests this header.
+
+#ifndef WINPTY_STRING_BUILDER_H
+#define WINPTY_STRING_BUILDER_H
+
+#include <array>
+#include <string>
+#include <type_traits>
+
+#ifdef STRING_BUILDER_TESTING
+#include <assert.h>
+#define STRING_BUILDER_CHECK(cond) assert(cond)
+#else
+#define STRING_BUILDER_CHECK(cond)
+#endif // STRING_BUILDER_TESTING
+
+#include "WinptyAssert.h"
+
+template <typename C, size_t sz>
+struct ValueString {
+ std::array<C, sz> m_array;
+ size_t m_offset;
+ size_t m_size;
+
+ const C *c_str() const { return m_array.data() + m_offset; }
+ const C *data() const { return m_array.data() + m_offset; }
+ size_t size() const { return m_size; }
+ std::basic_string<C> str() const {
+ return std::basic_string<C>(data(), m_size);
+ }
+};
+
+#ifdef _MSC_VER
+// Disable an MSVC /SDL error that forbids unsigned negation. Signed negation
+// invokes undefined behavior for INTxx_MIN, so unsigned negation is simpler to
+// reason about. (We assume twos-complement in any case.)
+#define STRING_BUILDER_ALLOW_UNSIGNED_NEGATE(x) \
+ ( \
+ __pragma(warning(push)) \
+ __pragma(warning(disable:4146)) \
+ (x) \
+ __pragma(warning(pop)) \
+ )
+#else
+#define STRING_BUILDER_ALLOW_UNSIGNED_NEGATE(x) (x)
+#endif
+
+// Formats an integer as decimal without leading zeros.
+template <typename C, typename I>
+ValueString<C, sizeof(I) * 3 + 1 + 1> gdecOfInt(const I value) {
+ typedef typename std::make_unsigned<I>::type U;
+ auto unsValue = static_cast<U>(value);
+ const bool isNegative = (value < 0);
+ if (isNegative) {
+ unsValue = STRING_BUILDER_ALLOW_UNSIGNED_NEGATE(-unsValue);
+ }
+ decltype(gdecOfInt<C, I>(value)) out;
+ auto &arr = out.m_array;
+ C *const endp = arr.data() + arr.size();
+ C *outp = endp;
+ *(--outp) = '\0';
+ STRING_BUILDER_CHECK(outp >= arr.data());
+ do {
+ const int digit = unsValue % 10;
+ unsValue /= 10;
+ *(--outp) = '0' + digit;
+ STRING_BUILDER_CHECK(outp >= arr.data());
+ } while (unsValue != 0);
+ if (isNegative) {
+ *(--outp) = '-';
+ STRING_BUILDER_CHECK(outp >= arr.data());
+ }
+ out.m_offset = outp - arr.data();
+ out.m_size = endp - outp - 1;
+ return out;
+}
+
+template <typename I> decltype(gdecOfInt<char, I>(0)) decOfInt(I i) {
+ return gdecOfInt<char>(i);
+}
+
+template <typename I> decltype(gdecOfInt<wchar_t, I>(0)) wdecOfInt(I i) {
+ return gdecOfInt<wchar_t>(i);
+}
+
+// Formats an integer as hexadecimal, with or without leading zeros.
+template <typename C, bool leadingZeros=false, typename I>
+ValueString<C, sizeof(I) * 2 + 1> ghexOfInt(const I value) {
+ typedef typename std::make_unsigned<I>::type U;
+ const auto unsValue = static_cast<U>(value);
+ static const C hex[16] = {'0','1','2','3','4','5','6','7',
+ '8','9','a','b','c','d','e','f'};
+ decltype(ghexOfInt<C, leadingZeros, I>(value)) out;
+ auto &arr = out.m_array;
+ C *outp = arr.data();
+ int inIndex = 0;
+ int shift = sizeof(I) * 8 - 4;
+ const int len = sizeof(I) * 2;
+ if (!leadingZeros) {
+ for (; inIndex < len - 1; ++inIndex, shift -= 4) {
+ STRING_BUILDER_CHECK(shift >= 0 && shift < sizeof(unsValue) * 8);
+ const int digit = (unsValue >> shift) & 0xF;
+ if (digit != 0) {
+ break;
+ }
+ }
+ }
+ for (; inIndex < len; ++inIndex, shift -= 4) {
+ const int digit = (unsValue >> shift) & 0xF;
+ *(outp++) = hex[digit];
+ STRING_BUILDER_CHECK(outp <= arr.data() + arr.size());
+ }
+ *(outp++) = '\0';
+ STRING_BUILDER_CHECK(outp <= arr.data() + arr.size());
+ out.m_offset = 0;
+ out.m_size = outp - arr.data() - 1;
+ return out;
+}
+
+template <bool leadingZeros=false, typename I>
+decltype(ghexOfInt<char, leadingZeros, I>(0)) hexOfInt(I i) {
+ return ghexOfInt<char, leadingZeros, I>(i);
+}
+
+template <bool leadingZeros=false, typename I>
+decltype(ghexOfInt<wchar_t, leadingZeros, I>(0)) whexOfInt(I i) {
+ return ghexOfInt<wchar_t, leadingZeros, I>(i);
+}
+
+template <typename C>
+class GStringBuilder {
+public:
+ typedef std::basic_string<C> StringType;
+
+ GStringBuilder() {}
+ GStringBuilder(size_t capacity) {
+ m_out.reserve(capacity);
+ }
+
+ GStringBuilder &operator<<(C ch) { m_out.push_back(ch); return *this; }
+ GStringBuilder &operator<<(const C *str) { m_out.append(str); return *this; }
+ GStringBuilder &operator<<(const StringType &str) { m_out.append(str); return *this; }
+
+ template <size_t sz>
+ GStringBuilder &operator<<(const ValueString<C, sz> &str) {
+ m_out.append(str.data(), str.size());
+ return *this;
+ }
+
+private:
+ // Forbid output of char/wchar_t for GStringBuilder if the type doesn't
+ // exactly match the builder element type. The code still allows
+ // signed char and unsigned char, but I'm a little worried about what
+ // happens if a user tries to output int8_t or uint8_t.
+ template <typename P>
+ typename std::enable_if<
+ (std::is_same<P, char>::value || std::is_same<P, wchar_t>::value) &&
+ !std::is_same<C, P>::value, GStringBuilder&>::type
+ operator<<(P ch) {
+ ASSERT(false && "Method was not supposed to be reachable.");
+ return *this;
+ }
+
+public:
+ GStringBuilder &operator<<(short i) { return *this << gdecOfInt<C>(i); }
+ GStringBuilder &operator<<(unsigned short i) { return *this << gdecOfInt<C>(i); }
+ GStringBuilder &operator<<(int i) { return *this << gdecOfInt<C>(i); }
+ GStringBuilder &operator<<(unsigned int i) { return *this << gdecOfInt<C>(i); }
+ GStringBuilder &operator<<(long i) { return *this << gdecOfInt<C>(i); }
+ GStringBuilder &operator<<(unsigned long i) { return *this << gdecOfInt<C>(i); }
+ GStringBuilder &operator<<(long long i) { return *this << gdecOfInt<C>(i); }
+ GStringBuilder &operator<<(unsigned long long i) { return *this << gdecOfInt<C>(i); }
+
+ GStringBuilder &operator<<(const void *p) {
+ m_out.push_back(static_cast<C>('0'));
+ m_out.push_back(static_cast<C>('x'));
+ *this << ghexOfInt<C>(reinterpret_cast<uintptr_t>(p));
+ return *this;
+ }
+
+ StringType str() { return m_out; }
+ StringType str_moved() { return std::move(m_out); }
+ const C *c_str() const { return m_out.c_str(); }
+
+private:
+ StringType m_out;
+};
+
+typedef GStringBuilder<char> StringBuilder;
+typedef GStringBuilder<wchar_t> WStringBuilder;
+
+#endif // WINPTY_STRING_BUILDER_H
diff --git a/src/libs/3rdparty/winpty/src/shared/StringBuilderTest.cc b/src/libs/3rdparty/winpty/src/shared/StringBuilderTest.cc
new file mode 100644
index 00000000000..e6c2d3138c8
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/StringBuilderTest.cc
@@ -0,0 +1,114 @@
+// Copyright (c) 2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#define STRING_BUILDER_TESTING
+
+#include "StringBuilder.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include <iomanip>
+#include <sstream>
+
+void display(const std::string &str) { fprintf(stderr, "%s", str.c_str()); }
+void display(const std::wstring &str) { fprintf(stderr, "%ls", str.c_str()); }
+
+#define CHECK_EQ(x, y) \
+ do { \
+ const auto xval = (x); \
+ const auto yval = (y); \
+ if (xval != yval) { \
+ fprintf(stderr, "error: %s:%d: %s != %s: ", \
+ __FILE__, __LINE__, #x, #y); \
+ display(xval); \
+ fprintf(stderr, " != "); \
+ display(yval); \
+ fprintf(stderr, "\n"); \
+ } \
+ } while(0)
+
+template <typename C, typename I>
+std::basic_string<C> decOfIntSS(const I value) {
+ // std::to_string and std::to_wstring are missing in Cygwin as of this
+ // writing (early 2016).
+ std::basic_stringstream<C> ss;
+ ss << +value; // We must promote char to print it as an integer.
+ return ss.str();
+}
+
+
+template <typename C, bool leadingZeros=false, typename I>
+std::basic_string<C> hexOfIntSS(const I value) {
+ typedef typename std::make_unsigned<I>::type U;
+ const unsigned long long u64Value = value & static_cast<U>(~0);
+ std::basic_stringstream<C> ss;
+ if (leadingZeros) {
+ ss << std::setfill(static_cast<C>('0')) << std::setw(sizeof(I) * 2);
+ }
+ ss << std::hex << u64Value;
+ return ss.str();
+}
+
+template <typename I>
+void testValue(I value) {
+ CHECK_EQ(decOfInt(value).str(), (decOfIntSS<char>(value)));
+ CHECK_EQ(wdecOfInt(value).str(), (decOfIntSS<wchar_t>(value)));
+ CHECK_EQ((hexOfInt<false>(value).str()), (hexOfIntSS<char, false>(value)));
+ CHECK_EQ((hexOfInt<true>(value).str()), (hexOfIntSS<char, true>(value)));
+ CHECK_EQ((whexOfInt<false>(value).str()), (hexOfIntSS<wchar_t, false>(value)));
+ CHECK_EQ((whexOfInt<true>(value).str()), (hexOfIntSS<wchar_t, true>(value)));
+}
+
+template <typename I>
+void testType() {
+ typedef typename std::make_unsigned<I>::type U;
+ const U quarter = static_cast<U>(1) << (sizeof(U) * 8 - 2);
+ for (unsigned quarterIndex = 0; quarterIndex < 4; ++quarterIndex) {
+ for (int offset = -18; offset <= 18; ++offset) {
+ const I value = quarter * quarterIndex + static_cast<U>(offset);
+ testValue(value);
+ }
+ }
+ testValue(static_cast<I>(42));
+ testValue(static_cast<I>(123456));
+ testValue(static_cast<I>(0xdeadfacecafebeefull));
+}
+
+int main() {
+ testType<char>();
+
+ testType<signed char>();
+ testType<signed short>();
+ testType<signed int>();
+ testType<signed long>();
+ testType<signed long long>();
+
+ testType<unsigned char>();
+ testType<unsigned short>();
+ testType<unsigned int>();
+ testType<unsigned long>();
+ testType<unsigned long long>();
+
+ StringBuilder() << static_cast<const void*>("TEST");
+ WStringBuilder() << static_cast<const void*>("TEST");
+
+ fprintf(stderr, "All tests completed!\n");
+}
diff --git a/src/libs/3rdparty/winpty/src/shared/StringUtil.cc b/src/libs/3rdparty/winpty/src/shared/StringUtil.cc
new file mode 100644
index 00000000000..3a85a3ec941
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/StringUtil.cc
@@ -0,0 +1,55 @@
+// Copyright (c) 2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include "StringUtil.h"
+
+#include <windows.h>
+
+#include "WinptyAssert.h"
+
+// Workaround. MinGW (from mingw.org) does not have wcsnlen. MinGW-w64 *does*
+// have wcsnlen, but use this function for consistency.
+size_t winpty_wcsnlen(const wchar_t *s, size_t maxlen) {
+ ASSERT(s != NULL);
+ for (size_t i = 0; i < maxlen; ++i) {
+ if (s[i] == L'\0') {
+ return i;
+ }
+ }
+ return maxlen;
+}
+
+std::string utf8FromWide(const std::wstring &input) {
+ int mblen = WideCharToMultiByte(
+ CP_UTF8, 0,
+ input.data(), input.size(),
+ NULL, 0, NULL, NULL);
+ if (mblen <= 0) {
+ return std::string();
+ }
+ std::vector<char> tmp(mblen);
+ int mblen2 = WideCharToMultiByte(
+ CP_UTF8, 0,
+ input.data(), input.size(),
+ tmp.data(), tmp.size(),
+ NULL, NULL);
+ ASSERT(mblen2 == mblen);
+ return std::string(tmp.data(), tmp.size());
+}
diff --git a/src/libs/3rdparty/winpty/src/shared/StringUtil.h b/src/libs/3rdparty/winpty/src/shared/StringUtil.h
new file mode 100644
index 00000000000..e4bf3c91212
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/StringUtil.h
@@ -0,0 +1,80 @@
+// Copyright (c) 2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef WINPTY_SHARED_STRING_UTIL_H
+#define WINPTY_SHARED_STRING_UTIL_H
+
+#include <stdlib.h>
+#include <string.h>
+#include <wchar.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+#include "WinptyAssert.h"
+
+size_t winpty_wcsnlen(const wchar_t *s, size_t maxlen);
+std::string utf8FromWide(const std::wstring &input);
+
+// Return a vector containing each character in the string.
+template <typename T>
+std::vector<T> vectorFromString(const std::basic_string<T> &str) {
+ return std::vector<T>(str.begin(), str.end());
+}
+
+// Return a vector containing each character in the string, followed by a
+// NUL terminator.
+template <typename T>
+std::vector<T> vectorWithNulFromString(const std::basic_string<T> &str) {
+ std::vector<T> ret;
+ ret.reserve(str.size() + 1);
+ ret.insert(ret.begin(), str.begin(), str.end());
+ ret.push_back('\0');
+ return ret;
+}
+
+// A safer(?) version of wcsncpy that is accepted by MSVC's /SDL mode.
+template <size_t N>
+wchar_t *winpty_wcsncpy(wchar_t (&d)[N], const wchar_t *s) {
+ ASSERT(s != nullptr);
+ size_t i = 0;
+ for (; i < N; ++i) {
+ if (s[i] == L'\0') {
+ break;
+ }
+ d[i] = s[i];
+ }
+ for (; i < N; ++i) {
+ d[i] = L'\0';
+ }
+ return d;
+}
+
+// Like wcsncpy, but ensure that the destination buffer is NUL-terminated.
+template <size_t N>
+wchar_t *winpty_wcsncpy_nul(wchar_t (&d)[N], const wchar_t *s) {
+ static_assert(N > 0, "array cannot be 0-size");
+ winpty_wcsncpy(d, s);
+ d[N - 1] = L'\0';
+ return d;
+}
+
+#endif // WINPTY_SHARED_STRING_UTIL_H
diff --git a/src/libs/3rdparty/winpty/src/shared/TimeMeasurement.h b/src/libs/3rdparty/winpty/src/shared/TimeMeasurement.h
new file mode 100644
index 00000000000..716a027fcbd
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/TimeMeasurement.h
@@ -0,0 +1,63 @@
+// Copyright (c) 2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+// Convenience header library for using the high-resolution performance counter
+// to measure how long some process takes.
+
+#ifndef TIME_MEASUREMENT_H
+#define TIME_MEASUREMENT_H
+
+#include <windows.h>
+#include <assert.h>
+#include <stdint.h>
+
+class TimeMeasurement {
+public:
+ TimeMeasurement() {
+ static double freq = static_cast<double>(getFrequency());
+ m_freq = freq;
+ m_start = value();
+ }
+
+ double elapsed() {
+ uint64_t elapsedTicks = value() - m_start;
+ return static_cast<double>(elapsedTicks) / m_freq;
+ }
+
+private:
+ uint64_t getFrequency() {
+ LARGE_INTEGER freq;
+ BOOL success = QueryPerformanceFrequency(&freq);
+ assert(success && "QueryPerformanceFrequency failed");
+ return freq.QuadPart;
+ }
+
+ uint64_t value() {
+ LARGE_INTEGER ret;
+ BOOL success = QueryPerformanceCounter(&ret);
+ assert(success && "QueryPerformanceCounter failed");
+ return ret.QuadPart;
+ }
+
+ uint64_t m_start;
+ double m_freq;
+};
+
+#endif // TIME_MEASUREMENT_H
diff --git a/src/libs/3rdparty/winpty/src/shared/UnixCtrlChars.h b/src/libs/3rdparty/winpty/src/shared/UnixCtrlChars.h
new file mode 100644
index 00000000000..39dfa62ec9f
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/UnixCtrlChars.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef UNIX_CTRL_CHARS_H
+#define UNIX_CTRL_CHARS_H
+
+inline char decodeUnixCtrlChar(char ch) {
+ const char ctrlKeys[] = {
+ /* 0x00 */ '@', /* 0x01 */ 'A', /* 0x02 */ 'B', /* 0x03 */ 'C',
+ /* 0x04 */ 'D', /* 0x05 */ 'E', /* 0x06 */ 'F', /* 0x07 */ 'G',
+ /* 0x08 */ 'H', /* 0x09 */ 'I', /* 0x0A */ 'J', /* 0x0B */ 'K',
+ /* 0x0C */ 'L', /* 0x0D */ 'M', /* 0x0E */ 'N', /* 0x0F */ 'O',
+ /* 0x10 */ 'P', /* 0x11 */ 'Q', /* 0x12 */ 'R', /* 0x13 */ 'S',
+ /* 0x14 */ 'T', /* 0x15 */ 'U', /* 0x16 */ 'V', /* 0x17 */ 'W',
+ /* 0x18 */ 'X', /* 0x19 */ 'Y', /* 0x1A */ 'Z', /* 0x1B */ '[',
+ /* 0x1C */ '\\', /* 0x1D */ ']', /* 0x1E */ '^', /* 0x1F */ '_',
+ };
+ unsigned char uch = ch;
+ if (uch < 32) {
+ return ctrlKeys[uch];
+ } else if (uch == 127) {
+ return '?';
+ } else {
+ return '\0';
+ }
+}
+
+#endif // UNIX_CTRL_CHARS_H
diff --git a/src/libs/3rdparty/winpty/src/shared/UpdateGenVersion.bat b/src/libs/3rdparty/winpty/src/shared/UpdateGenVersion.bat
new file mode 100644
index 00000000000..ea2a7d64ed5
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/UpdateGenVersion.bat
@@ -0,0 +1,20 @@
+@echo off
+
+rem -- Echo the git commit hash. If git isn't available for some reason,
+rem -- output nothing instead.
+
+mkdir ..\gen 2>nul
+
+set /p VERSION=<..\..\VERSION.txt
+set COMMIT=%1
+
+echo // AUTO-GENERATED BY %0 %*>..\gen\GenVersion.h
+echo const char GenVersion_Version[] = "%VERSION%";>>..\gen\GenVersion.h
+echo const char GenVersion_Commit[] = "%COMMIT%";>>..\gen\GenVersion.h
+
+rem -- The winpty.gyp file expects the script to output the include directory,
+rem -- relative to src.
+echo gen
+
+rem -- Set ERRORLEVEL to 0 using this cryptic syntax.
+(call )
diff --git a/src/libs/3rdparty/winpty/src/shared/WindowsSecurity.cc b/src/libs/3rdparty/winpty/src/shared/WindowsSecurity.cc
new file mode 100644
index 00000000000..711a8637c8c
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/WindowsSecurity.cc
@@ -0,0 +1,460 @@
+// Copyright (c) 2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include "WindowsSecurity.h"
+
+#include <array>
+
+#include "DebugClient.h"
+#include "OsModule.h"
+#include "OwnedHandle.h"
+#include "StringBuilder.h"
+#include "WindowsVersion.h"
+#include "WinptyAssert.h"
+#include "WinptyException.h"
+
+namespace {
+
+struct LocalFreer {
+ void operator()(void *ptr) {
+ if (ptr != nullptr) {
+ LocalFree(reinterpret_cast<HLOCAL>(ptr));
+ }
+ }
+};
+
+typedef std::unique_ptr<void, LocalFreer> PointerLocal;
+
+template <typename T>
+SecurityItem<T> localItem(typename T::type v) {
+ typedef typename T::type P;
+ struct Impl : SecurityItem<T>::Impl {
+ P m_v;
+ Impl(P v) : m_v(v) {}
+ virtual ~Impl() {
+ LocalFree(reinterpret_cast<HLOCAL>(m_v));
+ }
+ };
+ return SecurityItem<T>(v, std::unique_ptr<Impl>(new Impl { v }));
+}
+
+Sid allocatedSid(PSID v) {
+ struct Impl : Sid::Impl {
+ PSID m_v;
+ Impl(PSID v) : m_v(v) {}
+ virtual ~Impl() {
+ if (m_v != nullptr) {
+ FreeSid(m_v);
+ }
+ }
+ };
+ return Sid(v, std::unique_ptr<Impl>(new Impl { v }));
+}
+
+} // anonymous namespace
+
+// Returns a handle to the thread's effective security token. If the thread
+// is impersonating another user, its token is returned, and otherwise, the
+// process' security token is opened. The handle is opened with TOKEN_QUERY.
+static OwnedHandle openSecurityTokenForQuery() {
+ HANDLE token = nullptr;
+ // It is unclear to me whether OpenAsSelf matters for winpty, or what the
+ // most appropriate value is.
+ if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY,
+ /*OpenAsSelf=*/FALSE, &token)) {
+ if (GetLastError() != ERROR_NO_TOKEN) {
+ throwWindowsError(L"OpenThreadToken failed");
+ }
+ if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
+ throwWindowsError(L"OpenProcessToken failed");
+ }
+ }
+ ASSERT(token != nullptr &&
+ "OpenThreadToken/OpenProcessToken token is NULL");
+ return OwnedHandle(token);
+}
+
+// Returns the TokenOwner of the thread's effective security token.
+Sid getOwnerSid() {
+ struct Impl : Sid::Impl {
+ std::unique_ptr<char[]> buffer;
+ };
+
+ OwnedHandle token = openSecurityTokenForQuery();
+ DWORD actual = 0;
+ BOOL success;
+ success = GetTokenInformation(token.get(), TokenOwner,
+ nullptr, 0, &actual);
+ if (success) {
+ throwWinptyException(L"getOwnerSid: GetTokenInformation: "
+ L"expected ERROR_INSUFFICIENT_BUFFER");
+ } else if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ throwWindowsError(L"getOwnerSid: GetTokenInformation: "
+ L"expected ERROR_INSUFFICIENT_BUFFER");
+ }
+ std::unique_ptr<Impl> impl(new Impl);
+ impl->buffer = std::unique_ptr<char[]>(new char[actual]);
+ success = GetTokenInformation(token.get(), TokenOwner,
+ impl->buffer.get(), actual, &actual);
+ if (!success) {
+ throwWindowsError(L"getOwnerSid: GetTokenInformation");
+ }
+ TOKEN_OWNER tmp;
+ ASSERT(actual >= sizeof(tmp));
+ std::copy(
+ impl->buffer.get(),
+ impl->buffer.get() + sizeof(tmp),
+ reinterpret_cast<char*>(&tmp));
+ return Sid(tmp.Owner, std::move(impl));
+}
+
+Sid wellKnownSid(
+ const wchar_t *debuggingName,
+ SID_IDENTIFIER_AUTHORITY authority,
+ BYTE authorityCount,
+ DWORD subAuthority0/*=0*/,
+ DWORD subAuthority1/*=0*/) {
+ PSID psid = nullptr;
+ if (!AllocateAndInitializeSid(&authority, authorityCount,
+ subAuthority0,
+ subAuthority1,
+ 0, 0, 0, 0, 0, 0,
+ &psid)) {
+ const auto err = GetLastError();
+ const auto msg =
+ std::wstring(L"wellKnownSid: error getting ") +
+ debuggingName + L" SID";
+ throwWindowsError(msg.c_str(), err);
+ }
+ return allocatedSid(psid);
+}
+
+Sid builtinAdminsSid() {
+ // S-1-5-32-544
+ SID_IDENTIFIER_AUTHORITY authority = { SECURITY_NT_AUTHORITY };
+ return wellKnownSid(L"BUILTIN\\Administrators group",
+ authority, 2,
+ SECURITY_BUILTIN_DOMAIN_RID, // 32
+ DOMAIN_ALIAS_RID_ADMINS); // 544
+}
+
+Sid localSystemSid() {
+ // S-1-5-18
+ SID_IDENTIFIER_AUTHORITY authority = { SECURITY_NT_AUTHORITY };
+ return wellKnownSid(L"LocalSystem account",
+ authority, 1,
+ SECURITY_LOCAL_SYSTEM_RID); // 18
+}
+
+Sid everyoneSid() {
+ // S-1-1-0
+ SID_IDENTIFIER_AUTHORITY authority = { SECURITY_WORLD_SID_AUTHORITY };
+ return wellKnownSid(L"Everyone account",
+ authority, 1,
+ SECURITY_WORLD_RID); // 0
+}
+
+static SecurityDescriptor finishSecurityDescriptor(
+ size_t daclEntryCount,
+ EXPLICIT_ACCESSW *daclEntries,
+ Acl &outAcl) {
+ {
+ PACL aclRaw = nullptr;
+ DWORD aclError =
+ SetEntriesInAclW(daclEntryCount,
+ daclEntries,
+ nullptr, &aclRaw);
+ if (aclError != ERROR_SUCCESS) {
+ WStringBuilder sb(64);
+ sb << L"finishSecurityDescriptor: "
+ << L"SetEntriesInAcl failed: " << aclError;
+ throwWinptyException(sb.c_str());
+ }
+ outAcl = localItem<AclTag>(aclRaw);
+ }
+
+ const PSECURITY_DESCRIPTOR sdRaw =
+ reinterpret_cast<PSECURITY_DESCRIPTOR>(
+ LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH));
+ if (sdRaw == nullptr) {
+ throwWinptyException(L"finishSecurityDescriptor: LocalAlloc failed");
+ }
+ SecurityDescriptor sd = localItem<SecurityDescriptorTag>(sdRaw);
+ if (!InitializeSecurityDescriptor(sdRaw, SECURITY_DESCRIPTOR_REVISION)) {
+ throwWindowsError(
+ L"finishSecurityDescriptor: InitializeSecurityDescriptor");
+ }
+ if (!SetSecurityDescriptorDacl(sdRaw, TRUE, outAcl.get(), FALSE)) {
+ throwWindowsError(
+ L"finishSecurityDescriptor: SetSecurityDescriptorDacl");
+ }
+
+ return std::move(sd);
+}
+
+// Create a security descriptor that grants full control to the local system
+// account, built-in administrators, and the owner.
+SecurityDescriptor
+createPipeSecurityDescriptorOwnerFullControl() {
+
+ struct Impl : SecurityDescriptor::Impl {
+ Sid localSystem;
+ Sid builtinAdmins;
+ Sid owner;
+ std::array<EXPLICIT_ACCESSW, 3> daclEntries = {};
+ Acl dacl;
+ SecurityDescriptor value;
+ };
+
+ std::unique_ptr<Impl> impl(new Impl);
+ impl->localSystem = localSystemSid();
+ impl->builtinAdmins = builtinAdminsSid();
+ impl->owner = getOwnerSid();
+
+ for (auto &ea : impl->daclEntries) {
+ ea.grfAccessPermissions = GENERIC_ALL;
+ ea.grfAccessMode = SET_ACCESS;
+ ea.grfInheritance = NO_INHERITANCE;
+ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID;
+ }
+ impl->daclEntries[0].Trustee.ptstrName =
+ reinterpret_cast<LPWSTR>(impl->localSystem.get());
+ impl->daclEntries[1].Trustee.ptstrName =
+ reinterpret_cast<LPWSTR>(impl->builtinAdmins.get());
+ impl->daclEntries[2].Trustee.ptstrName =
+ reinterpret_cast<LPWSTR>(impl->owner.get());
+
+ impl->value = finishSecurityDescriptor(
+ impl->daclEntries.size(),
+ impl->daclEntries.data(),
+ impl->dacl);
+
+ const auto retValue = impl->value.get();
+ return SecurityDescriptor(retValue, std::move(impl));
+}
+
+SecurityDescriptor
+createPipeSecurityDescriptorOwnerFullControlEveryoneWrite() {
+
+ struct Impl : SecurityDescriptor::Impl {
+ Sid localSystem;
+ Sid builtinAdmins;
+ Sid owner;
+ Sid everyone;
+ std::array<EXPLICIT_ACCESSW, 4> daclEntries = {};
+ Acl dacl;
+ SecurityDescriptor value;
+ };
+
+ std::unique_ptr<Impl> impl(new Impl);
+ impl->localSystem = localSystemSid();
+ impl->builtinAdmins = builtinAdminsSid();
+ impl->owner = getOwnerSid();
+ impl->everyone = everyoneSid();
+
+ for (auto &ea : impl->daclEntries) {
+ ea.grfAccessPermissions = GENERIC_ALL;
+ ea.grfAccessMode = SET_ACCESS;
+ ea.grfInheritance = NO_INHERITANCE;
+ ea.Trustee.TrusteeForm = TRUSTEE_IS_SID;
+ }
+ impl->daclEntries[0].Trustee.ptstrName =
+ reinterpret_cast<LPWSTR>(impl->localSystem.get());
+ impl->daclEntries[1].Trustee.ptstrName =
+ reinterpret_cast<LPWSTR>(impl->builtinAdmins.get());
+ impl->daclEntries[2].Trustee.ptstrName =
+ reinterpret_cast<LPWSTR>(impl->owner.get());
+ impl->daclEntries[3].Trustee.ptstrName =
+ reinterpret_cast<LPWSTR>(impl->everyone.get());
+ // Avoid using FILE_GENERIC_WRITE because it includes FILE_APPEND_DATA,
+ // which is equal to FILE_CREATE_PIPE_INSTANCE. Instead, include all the
+ // flags that comprise FILE_GENERIC_WRITE, except for the one.
+ impl->daclEntries[3].grfAccessPermissions =
+ FILE_GENERIC_READ |
+ FILE_WRITE_ATTRIBUTES | FILE_WRITE_DATA | FILE_WRITE_EA |
+ STANDARD_RIGHTS_WRITE | SYNCHRONIZE;
+
+ impl->value = finishSecurityDescriptor(
+ impl->daclEntries.size(),
+ impl->daclEntries.data(),
+ impl->dacl);
+
+ const auto retValue = impl->value.get();
+ return SecurityDescriptor(retValue, std::move(impl));
+}
+
+SecurityDescriptor getObjectSecurityDescriptor(HANDLE handle) {
+ PACL dacl = nullptr;
+ PSECURITY_DESCRIPTOR sd = nullptr;
+ const DWORD errCode = GetSecurityInfo(handle, SE_KERNEL_OBJECT,
+ OWNER_SECURITY_INFORMATION |
+ GROUP_SECURITY_INFORMATION |
+ DACL_SECURITY_INFORMATION,
+ nullptr, nullptr, &dacl, nullptr, &sd);
+ if (errCode != ERROR_SUCCESS) {
+ throwWindowsError(L"GetSecurityInfo failed");
+ }
+ return localItem<SecurityDescriptorTag>(sd);
+}
+
+// The (SID/SD)<->string conversion APIs are useful for testing/debugging, so
+// create convenient accessor functions for them. They're too slow for
+// ordinary use. The APIs exist in XP and up, but the MinGW headers only
+// declare the SID<->string APIs, not the SD APIs. MinGW also gets the
+// prototype wrong for ConvertStringSidToSidW (LPWSTR instead of LPCWSTR) and
+// requires WINVER to be defined. MSVC and MinGW-w64 get everything right, but
+// for consistency, use LoadLibrary/GetProcAddress for all four APIs.
+
+typedef BOOL WINAPI ConvertStringSidToSidW_t(
+ LPCWSTR StringSid,
+ PSID *Sid);
+
+typedef BOOL WINAPI ConvertSidToStringSidW_t(
+ PSID Sid,
+ LPWSTR *StringSid);
+
+typedef BOOL WINAPI ConvertStringSecurityDescriptorToSecurityDescriptorW_t(
+ LPCWSTR StringSecurityDescriptor,
+ DWORD StringSDRevision,
+ PSECURITY_DESCRIPTOR *SecurityDescriptor,
+ PULONG SecurityDescriptorSize);
+
+typedef BOOL WINAPI ConvertSecurityDescriptorToStringSecurityDescriptorW_t(
+ PSECURITY_DESCRIPTOR SecurityDescriptor,
+ DWORD RequestedStringSDRevision,
+ SECURITY_INFORMATION SecurityInformation,
+ LPWSTR *StringSecurityDescriptor,
+ PULONG StringSecurityDescriptorLen);
+
+#define GET_MODULE_PROC(mod, funcName) \
+ const auto p##funcName = \
+ reinterpret_cast<funcName##_t*>( \
+ mod.proc(#funcName)); \
+ if (p##funcName == nullptr) { \
+ throwWinptyException( \
+ L"" L ## #funcName L" API is missing from ADVAPI32.DLL"); \
+ }
+
+const DWORD kSDDL_REVISION_1 = 1;
+
+std::wstring sidToString(PSID sid) {
+ OsModule advapi32(L"advapi32.dll");
+ GET_MODULE_PROC(advapi32, ConvertSidToStringSidW);
+ wchar_t *sidString = NULL;
+ BOOL success = pConvertSidToStringSidW(sid, &sidString);
+ if (!success) {
+ throwWindowsError(L"ConvertSidToStringSidW failed");
+ }
+ PointerLocal freer(sidString);
+ return std::wstring(sidString);
+}
+
+Sid stringToSid(const std::wstring &str) {
+ // Cast the string from const wchar_t* to LPWSTR because the function is
+ // incorrectly prototyped in the MinGW sddl.h header. The API does not
+ // modify the string -- it is correctly prototyped as taking LPCWSTR in
+ // MinGW-w64, MSVC, and MSDN.
+ OsModule advapi32(L"advapi32.dll");
+ GET_MODULE_PROC(advapi32, ConvertStringSidToSidW);
+ PSID psid = nullptr;
+ BOOL success = pConvertStringSidToSidW(const_cast<LPWSTR>(str.c_str()),
+ &psid);
+ if (!success) {
+ const auto err = GetLastError();
+ throwWindowsError(
+ (std::wstring(L"ConvertStringSidToSidW failed on \"") +
+ str + L'"').c_str(),
+ err);
+ }
+ return localItem<SidTag>(psid);
+}
+
+SecurityDescriptor stringToSd(const std::wstring &str) {
+ OsModule advapi32(L"advapi32.dll");
+ GET_MODULE_PROC(advapi32, ConvertStringSecurityDescriptorToSecurityDescriptorW);
+ PSECURITY_DESCRIPTOR desc = nullptr;
+ if (!pConvertStringSecurityDescriptorToSecurityDescriptorW(
+ str.c_str(), kSDDL_REVISION_1, &desc, nullptr)) {
+ const auto err = GetLastError();
+ throwWindowsError(
+ (std::wstring(L"ConvertStringSecurityDescriptorToSecurityDescriptorW failed on \"") +
+ str + L'"').c_str(),
+ err);
+ }
+ return localItem<SecurityDescriptorTag>(desc);
+}
+
+std::wstring sdToString(PSECURITY_DESCRIPTOR sd) {
+ OsModule advapi32(L"advapi32.dll");
+ GET_MODULE_PROC(advapi32, ConvertSecurityDescriptorToStringSecurityDescriptorW);
+ wchar_t *sdString = nullptr;
+ if (!pConvertSecurityDescriptorToStringSecurityDescriptorW(
+ sd,
+ kSDDL_REVISION_1,
+ OWNER_SECURITY_INFORMATION |
+ GROUP_SECURITY_INFORMATION |
+ DACL_SECURITY_INFORMATION,
+ &sdString,
+ nullptr)) {
+ throwWindowsError(
+ L"ConvertSecurityDescriptorToStringSecurityDescriptor failed");
+ }
+ PointerLocal freer(sdString);
+ return std::wstring(sdString);
+}
+
+// Vista added a useful flag to CreateNamedPipe, PIPE_REJECT_REMOTE_CLIENTS,
+// that rejects remote connections. Return this flag on Vista, or return 0
+// otherwise.
+DWORD rejectRemoteClientsPipeFlag() {
+ if (isAtLeastWindowsVista()) {
+ // MinGW lacks this flag; MinGW-w64 has it.
+ const DWORD kPIPE_REJECT_REMOTE_CLIENTS = 8;
+ return kPIPE_REJECT_REMOTE_CLIENTS;
+ } else {
+ trace("Omitting PIPE_REJECT_REMOTE_CLIENTS on pre-Vista OS");
+ return 0;
+ }
+}
+
+typedef BOOL WINAPI GetNamedPipeClientProcessId_t(
+ HANDLE Pipe,
+ PULONG ClientProcessId);
+
+std::tuple<GetNamedPipeClientProcessId_Result, DWORD, DWORD>
+getNamedPipeClientProcessId(HANDLE serverPipe) {
+ OsModule kernel32(L"kernel32.dll");
+ const auto pGetNamedPipeClientProcessId =
+ reinterpret_cast<GetNamedPipeClientProcessId_t*>(
+ kernel32.proc("GetNamedPipeClientProcessId"));
+ if (pGetNamedPipeClientProcessId == nullptr) {
+ return std::make_tuple(
+ GetNamedPipeClientProcessId_Result::UnsupportedOs, 0, 0);
+ }
+ ULONG pid = 0;
+ if (!pGetNamedPipeClientProcessId(serverPipe, &pid)) {
+ return std::make_tuple(
+ GetNamedPipeClientProcessId_Result::Failure, 0, GetLastError());
+ }
+ return std::make_tuple(
+ GetNamedPipeClientProcessId_Result::Success,
+ static_cast<DWORD>(pid),
+ 0);
+}
diff --git a/src/libs/3rdparty/winpty/src/shared/WindowsSecurity.h b/src/libs/3rdparty/winpty/src/shared/WindowsSecurity.h
new file mode 100644
index 00000000000..5f9d53aff6d
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/WindowsSecurity.h
@@ -0,0 +1,104 @@
+// Copyright (c) 2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef WINPTY_WINDOWS_SECURITY_H
+#define WINPTY_WINDOWS_SECURITY_H
+
+#include <windows.h>
+#include <aclapi.h>
+
+#include <memory>
+#include <string>
+#include <tuple>
+#include <utility>
+
+// PSID and PSECURITY_DESCRIPTOR are both pointers to void, but we want
+// Sid and SecurityDescriptor to be different types.
+struct SidTag { typedef PSID type; };
+struct AclTag { typedef PACL type; };
+struct SecurityDescriptorTag { typedef PSECURITY_DESCRIPTOR type; };
+
+template <typename T>
+class SecurityItem {
+public:
+ struct Impl {
+ virtual ~Impl() {}
+ };
+
+private:
+ typedef typename T::type P;
+ P m_v;
+ std::unique_ptr<Impl> m_pimpl;
+
+public:
+ P get() const { return m_v; }
+ operator bool() const { return m_v != nullptr; }
+
+ SecurityItem() : m_v(nullptr) {}
+ SecurityItem(P v, std::unique_ptr<Impl> &&pimpl) :
+ m_v(v), m_pimpl(std::move(pimpl)) {}
+ SecurityItem(SecurityItem &&other) :
+ m_v(other.m_v), m_pimpl(std::move(other.m_pimpl)) {
+ other.m_v = nullptr;
+ }
+ SecurityItem &operator=(SecurityItem &&other) {
+ m_v = other.m_v;
+ other.m_v = nullptr;
+ m_pimpl = std::move(other.m_pimpl);
+ return *this;
+ }
+};
+
+typedef SecurityItem<SidTag> Sid;
+typedef SecurityItem<AclTag> Acl;
+typedef SecurityItem<SecurityDescriptorTag> SecurityDescriptor;
+
+Sid getOwnerSid();
+Sid wellKnownSid(
+ const wchar_t *debuggingName,
+ SID_IDENTIFIER_AUTHORITY authority,
+ BYTE authorityCount,
+ DWORD subAuthority0=0,
+ DWORD subAuthority1=0);
+Sid builtinAdminsSid();
+Sid localSystemSid();
+Sid everyoneSid();
+
+SecurityDescriptor createPipeSecurityDescriptorOwnerFullControl();
+SecurityDescriptor createPipeSecurityDescriptorOwnerFullControlEveryoneWrite();
+SecurityDescriptor getObjectSecurityDescriptor(HANDLE handle);
+
+std::wstring sidToString(PSID sid);
+Sid stringToSid(const std::wstring &str);
+SecurityDescriptor stringToSd(const std::wstring &str);
+std::wstring sdToString(PSECURITY_DESCRIPTOR sd);
+
+DWORD rejectRemoteClientsPipeFlag();
+
+enum class GetNamedPipeClientProcessId_Result {
+ Success,
+ Failure,
+ UnsupportedOs,
+};
+
+std::tuple<GetNamedPipeClientProcessId_Result, DWORD, DWORD>
+getNamedPipeClientProcessId(HANDLE serverPipe);
+
+#endif // WINPTY_WINDOWS_SECURITY_H
diff --git a/src/libs/3rdparty/winpty/src/shared/WindowsVersion.cc b/src/libs/3rdparty/winpty/src/shared/WindowsVersion.cc
new file mode 100644
index 00000000000..d89b00d8382
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/WindowsVersion.cc
@@ -0,0 +1,252 @@
+// Copyright (c) 2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include "WindowsVersion.h"
+
+#include <windows.h>
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <tuple>
+
+#include "DebugClient.h"
+#include "OsModule.h"
+#include "StringBuilder.h"
+#include "StringUtil.h"
+#include "WinptyAssert.h"
+#include "WinptyException.h"
+
+namespace {
+
+typedef std::tuple<DWORD, DWORD> Version;
+
+// This function can only return a version up to 6.2 unless the executable is
+// manifested for a newer version of Windows. See the MSDN documentation for
+// GetVersionEx.
+OSVERSIONINFOEX getWindowsVersionInfo() {
+ // Allow use of deprecated functions (i.e. GetVersionEx). We need to use
+ // GetVersionEx for the old MinGW toolchain and with MSVC when it targets XP.
+ // Having two code paths makes code harder to test, and it's not obvious how
+ // to detect the presence of a new enough SDK. (Including ntverp.h and
+ // examining VER_PRODUCTBUILD apparently works, but even then, MinGW-w64 and
+ // MSVC seem to use different version numbers.)
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable:4996)
+#endif
+ OSVERSIONINFOEX info = {};
+ info.dwOSVersionInfoSize = sizeof(info);
+ const auto success = GetVersionEx(reinterpret_cast<OSVERSIONINFO*>(&info));
+ ASSERT(success && "GetVersionEx failed");
+ return info;
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+}
+
+Version getWindowsVersion() {
+ const auto info = getWindowsVersionInfo();
+ return Version(info.dwMajorVersion, info.dwMinorVersion);
+}
+
+struct ModuleNotFound : WinptyException {
+ virtual const wchar_t *what() const WINPTY_NOEXCEPT override {
+ return L"ModuleNotFound";
+ }
+};
+
+// Throws WinptyException on error.
+std::wstring getSystemDirectory() {
+ wchar_t systemDirectory[MAX_PATH];
+ const UINT size = GetSystemDirectoryW(systemDirectory, MAX_PATH);
+ if (size == 0) {
+ throwWindowsError(L"GetSystemDirectory failed");
+ } else if (size >= MAX_PATH) {
+ throwWinptyException(
+ L"GetSystemDirectory: path is longer than MAX_PATH");
+ }
+ return systemDirectory;
+}
+
+#define GET_VERSION_DLL_API(name) \
+ const auto p ## name = \
+ reinterpret_cast<decltype(name)*>( \
+ versionDll.proc(#name)); \
+ if (p ## name == nullptr) { \
+ throwWinptyException(L ## #name L" is missing"); \
+ }
+
+// Throws WinptyException on error.
+VS_FIXEDFILEINFO getFixedFileInfo(const std::wstring &path) {
+ // version.dll is not a conventional KnownDll, so if we link to it, there's
+ // a danger of accidentally loading a malicious DLL. In a more typical
+ // application, perhaps we'd guard against this security issue by
+ // controlling which directories this code runs in (e.g. *not* the
+ // "Downloads" directory), but that's harder for the winpty library.
+ OsModule versionDll(
+ (getSystemDirectory() + L"\\version.dll").c_str(),
+ OsModule::LoadErrorBehavior::Throw);
+ GET_VERSION_DLL_API(GetFileVersionInfoSizeW);
+ GET_VERSION_DLL_API(GetFileVersionInfoW);
+ GET_VERSION_DLL_API(VerQueryValueW);
+ DWORD size = pGetFileVersionInfoSizeW(path.c_str(), nullptr);
+ if (!size) {
+ // I see ERROR_FILE_NOT_FOUND on Win7 and
+ // ERROR_RESOURCE_DATA_NOT_FOUND on WinXP.
+ if (GetLastError() == ERROR_FILE_NOT_FOUND ||
+ GetLastError() == ERROR_RESOURCE_DATA_NOT_FOUND) {
+ throw ModuleNotFound();
+ } else {
+ throwWindowsError(
+ (L"GetFileVersionInfoSizeW failed on " + path).c_str());
+ }
+ }
+ std::unique_ptr<char[]> versionBuffer(new char[size]);
+ if (!pGetFileVersionInfoW(path.c_str(), 0, size, versionBuffer.get())) {
+ throwWindowsError((L"GetFileVersionInfoW failed on " + path).c_str());
+ }
+ VS_FIXEDFILEINFO *versionInfo = nullptr;
+ UINT versionInfoSize = 0;
+ if (!pVerQueryValueW(
+ versionBuffer.get(), L"\\",
+ reinterpret_cast<void**>(&versionInfo), &versionInfoSize) ||
+ versionInfo == nullptr ||
+ versionInfoSize != sizeof(VS_FIXEDFILEINFO) ||
+ versionInfo->dwSignature != 0xFEEF04BD) {
+ throwWinptyException((L"VerQueryValueW failed on " + path).c_str());
+ }
+ return *versionInfo;
+}
+
+uint64_t productVersionFromInfo(const VS_FIXEDFILEINFO &info) {
+ return (static_cast<uint64_t>(info.dwProductVersionMS) << 32) |
+ (static_cast<uint64_t>(info.dwProductVersionLS));
+}
+
+uint64_t fileVersionFromInfo(const VS_FIXEDFILEINFO &info) {
+ return (static_cast<uint64_t>(info.dwFileVersionMS) << 32) |
+ (static_cast<uint64_t>(info.dwFileVersionLS));
+}
+
+std::string versionToString(uint64_t version) {
+ StringBuilder b(32);
+ b << ((uint16_t)(version >> 48));
+ b << '.';
+ b << ((uint16_t)(version >> 32));
+ b << '.';
+ b << ((uint16_t)(version >> 16));
+ b << '.';
+ b << ((uint16_t)(version >> 0));
+ return b.str_moved();
+}
+
+} // anonymous namespace
+
+// Returns true for Windows Vista (or Windows Server 2008) or newer.
+bool isAtLeastWindowsVista() {
+ return getWindowsVersion() >= Version(6, 0);
+}
+
+// Returns true for Windows 7 (or Windows Server 2008 R2) or newer.
+bool isAtLeastWindows7() {
+ return getWindowsVersion() >= Version(6, 1);
+}
+
+// Returns true for Windows 8 (or Windows Server 2012) or newer.
+bool isAtLeastWindows8() {
+ return getWindowsVersion() >= Version(6, 2);
+}
+
+#define WINPTY_IA32 1
+#define WINPTY_X64 2
+
+#if defined(_M_IX86) || defined(__i386__)
+#define WINPTY_ARCH WINPTY_IA32
+#elif defined(_M_X64) || defined(__x86_64__)
+#define WINPTY_ARCH WINPTY_X64
+#endif
+
+typedef BOOL WINAPI IsWow64Process_t(HANDLE hProcess, PBOOL Wow64Process);
+
+void dumpWindowsVersion() {
+ if (!isTracingEnabled()) {
+ return;
+ }
+ const auto info = getWindowsVersionInfo();
+ StringBuilder b;
+ b << info.dwMajorVersion << '.' << info.dwMinorVersion
+ << '.' << info.dwBuildNumber << ' '
+ << "SP" << info.wServicePackMajor << '.' << info.wServicePackMinor
+ << ' ';
+ switch (info.wProductType) {
+ case VER_NT_WORKSTATION: b << "Client"; break;
+ case VER_NT_DOMAIN_CONTROLLER: b << "DomainController"; break;
+ case VER_NT_SERVER: b << "Server"; break;
+ default:
+ b << "product=" << info.wProductType; break;
+ }
+ b << ' ';
+#if WINPTY_ARCH == WINPTY_IA32
+ b << "IA32";
+ OsModule kernel32(L"kernel32.dll");
+ IsWow64Process_t *pIsWow64Process =
+ reinterpret_cast<IsWow64Process_t*>(
+ kernel32.proc("IsWow64Process"));
+ if (pIsWow64Process != nullptr) {
+ BOOL result = false;
+ const BOOL success = pIsWow64Process(GetCurrentProcess(), &result);
+ if (!success) {
+ b << " WOW64:error";
+ } else if (success && result) {
+ b << " WOW64";
+ }
+ } else {
+ b << " WOW64:missingapi";
+ }
+#elif WINPTY_ARCH == WINPTY_X64
+ b << "X64";
+#endif
+ const auto dllVersion = [](const wchar_t *dllPath) -> std::string {
+ try {
+ const auto info = getFixedFileInfo(dllPath);
+ StringBuilder fb(64);
+ fb << utf8FromWide(dllPath) << ':';
+ fb << "F:" << versionToString(fileVersionFromInfo(info)) << '/'
+ << "P:" << versionToString(productVersionFromInfo(info));
+ return fb.str_moved();
+ } catch (const ModuleNotFound&) {
+ return utf8FromWide(dllPath) + ":none";
+ } catch (const WinptyException &e) {
+ trace("Error getting %s version: %s",
+ utf8FromWide(dllPath).c_str(), utf8FromWide(e.what()).c_str());
+ return utf8FromWide(dllPath) + ":error";
+ }
+ };
+ b << ' ' << dllVersion(L"kernel32.dll");
+ // ConEmu provides a DLL that hooks many Windows APIs, especially console
+ // APIs. Its existence and version number could be useful in debugging.
+#if WINPTY_ARCH == WINPTY_IA32
+ b << ' ' << dllVersion(L"ConEmuHk.dll");
+#elif WINPTY_ARCH == WINPTY_X64
+ b << ' ' << dllVersion(L"ConEmuHk64.dll");
+#endif
+ trace("Windows version: %s", b.c_str());
+}
diff --git a/src/libs/3rdparty/winpty/src/shared/WindowsVersion.h b/src/libs/3rdparty/winpty/src/shared/WindowsVersion.h
new file mode 100644
index 00000000000..a80798417eb
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/WindowsVersion.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef WINPTY_SHARED_WINDOWS_VERSION_H
+#define WINPTY_SHARED_WINDOWS_VERSION_H
+
+bool isAtLeastWindowsVista();
+bool isAtLeastWindows7();
+bool isAtLeastWindows8();
+void dumpWindowsVersion();
+
+#endif // WINPTY_SHARED_WINDOWS_VERSION_H
diff --git a/src/libs/3rdparty/winpty/src/shared/WinptyAssert.cc b/src/libs/3rdparty/winpty/src/shared/WinptyAssert.cc
new file mode 100644
index 00000000000..1ff0de475ab
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/WinptyAssert.cc
@@ -0,0 +1,55 @@
+// Copyright (c) 2011-2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include "WinptyAssert.h"
+
+#include <windows.h>
+#include <stdlib.h>
+
+#include "DebugClient.h"
+
+void assertTrace(const char *file, int line, const char *cond) {
+ trace("Assertion failed: %s, file %s, line %d",
+ cond, file, line);
+}
+
+#ifdef WINPTY_AGENT_ASSERT
+
+void agentShutdown() {
+ HWND hwnd = GetConsoleWindow();
+ if (hwnd != NULL) {
+ PostMessage(hwnd, WM_CLOSE, 0, 0);
+ Sleep(30000);
+ trace("Agent shutdown: WM_CLOSE did not end agent process");
+ } else {
+ trace("Agent shutdown: GetConsoleWindow() is NULL");
+ }
+ // abort() prints a message to the console, and if it is frozen, then the
+ // process would hang, so instead use exit(). (We shouldn't ever get here,
+ // though, because the WM_CLOSE message should have ended this process.)
+ exit(1);
+}
+
+void agentAssertFail(const char *file, int line, const char *cond) {
+ assertTrace(file, line, cond);
+ agentShutdown();
+}
+
+#endif
diff --git a/src/libs/3rdparty/winpty/src/shared/WinptyAssert.h b/src/libs/3rdparty/winpty/src/shared/WinptyAssert.h
new file mode 100644
index 00000000000..b2b8b5e64c6
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/WinptyAssert.h
@@ -0,0 +1,64 @@
+// Copyright (c) 2011-2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef WINPTY_ASSERT_H
+#define WINPTY_ASSERT_H
+
+#ifdef WINPTY_AGENT_ASSERT
+
+void agentShutdown();
+void agentAssertFail(const char *file, int line, const char *cond);
+
+// Calling the standard assert() function does not work in the agent because
+// the error message would be printed to the console, and the only way the
+// user can see the console is via a working agent! Moreover, the console may
+// be frozen, so attempting to write to it would block forever. This custom
+// assert function instead sends the message to the DebugServer, then attempts
+// to close the console, then quietly exits.
+#define ASSERT(cond) \
+ do { \
+ if (!(cond)) { \
+ agentAssertFail(__FILE__, __LINE__, #cond); \
+ } \
+ } while(0)
+
+#else
+
+void assertTrace(const char *file, int line, const char *cond);
+
+// In the other targets, log the assert failure to the debugserver, then fail
+// using the ordinary assert mechanism. In case assert is compiled out, fail
+// using abort. The amount of code inlined is unfortunate, but asserts aren't
+// used much outside the agent.
+#include <assert.h>
+#include <stdlib.h>
+#define ASSERT_CONDITION(cond) (false && (cond))
+#define ASSERT(cond) \
+ do { \
+ if (!(cond)) { \
+ assertTrace(__FILE__, __LINE__, #cond); \
+ assert(ASSERT_CONDITION(#cond)); \
+ abort(); \
+ } \
+ } while(0)
+
+#endif
+
+#endif // WINPTY_ASSERT_H
diff --git a/src/libs/3rdparty/winpty/src/shared/WinptyException.cc b/src/libs/3rdparty/winpty/src/shared/WinptyException.cc
new file mode 100644
index 00000000000..d0d48823d22
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/WinptyException.cc
@@ -0,0 +1,57 @@
+// Copyright (c) 2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include "WinptyException.h"
+
+#include <memory>
+#include <string>
+
+#include "StringBuilder.h"
+
+namespace {
+
+class ExceptionImpl : public WinptyException {
+public:
+ ExceptionImpl(const wchar_t *what) :
+ m_what(std::make_shared<std::wstring>(what)) {}
+ virtual const wchar_t *what() const WINPTY_NOEXCEPT override {
+ return m_what->c_str();
+ }
+private:
+ // Using a shared_ptr ensures that copying the object raises no exception.
+ std::shared_ptr<std::wstring> m_what;
+};
+
+} // anonymous namespace
+
+void throwWinptyException(const wchar_t *what) {
+ throw ExceptionImpl(what);
+}
+
+void throwWindowsError(const wchar_t *prefix, DWORD errorCode) {
+ WStringBuilder sb(64);
+ if (prefix != nullptr) {
+ sb << prefix << L": ";
+ }
+ // It might make sense to use FormatMessage here, but IIRC, its API is hard
+ // to figure out.
+ sb << L"Windows error " << errorCode;
+ throwWinptyException(sb.c_str());
+}
diff --git a/src/libs/3rdparty/winpty/src/shared/WinptyException.h b/src/libs/3rdparty/winpty/src/shared/WinptyException.h
new file mode 100644
index 00000000000..ec353369e5b
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/WinptyException.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef WINPTY_EXCEPTION_H
+#define WINPTY_EXCEPTION_H
+
+#include <windows.h>
+
+#if defined(__GNUC__)
+#define WINPTY_NOEXCEPT noexcept
+#elif defined(_MSC_VER) && _MSC_VER >= 1900
+#define WINPTY_NOEXCEPT noexcept
+#else
+#define WINPTY_NOEXCEPT
+#endif
+
+class WinptyException {
+public:
+ virtual const wchar_t *what() const WINPTY_NOEXCEPT = 0;
+ virtual ~WinptyException() {}
+};
+
+void throwWinptyException(const wchar_t *what);
+void throwWindowsError(const wchar_t *prefix, DWORD error=GetLastError());
+
+#endif // WINPTY_EXCEPTION_H
diff --git a/src/libs/3rdparty/winpty/src/shared/WinptyVersion.cc b/src/libs/3rdparty/winpty/src/shared/WinptyVersion.cc
new file mode 100644
index 00000000000..76bb8a584d2
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/WinptyVersion.cc
@@ -0,0 +1,42 @@
+// Copyright (c) 2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include "WinptyVersion.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include "DebugClient.h"
+
+// This header is auto-generated by either the Makefile (Unix) or
+// UpdateGenVersion.bat (gyp). It is placed in a 'gen' directory, which is
+// added to the search path.
+#include "GenVersion.h"
+
+void dumpVersionToStdout() {
+ printf("winpty version %s\n", GenVersion_Version);
+ printf("commit %s\n", GenVersion_Commit);
+}
+
+void dumpVersionToTrace() {
+ trace("winpty version %s (commit %s)",
+ GenVersion_Version,
+ GenVersion_Commit);
+}
diff --git a/src/libs/3rdparty/winpty/src/shared/WinptyVersion.h b/src/libs/3rdparty/winpty/src/shared/WinptyVersion.h
new file mode 100644
index 00000000000..e6224d7b847
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/WinptyVersion.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef WINPTY_VERSION_H
+#define WINPTY_VERSION_H
+
+void dumpVersionToStdout();
+void dumpVersionToTrace();
+
+#endif // WINPTY_VERSION_H
diff --git a/src/libs/3rdparty/winpty/src/shared/winpty_snprintf.h b/src/libs/3rdparty/winpty/src/shared/winpty_snprintf.h
new file mode 100644
index 00000000000..e716f245e8c
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/shared/winpty_snprintf.h
@@ -0,0 +1,99 @@
+// Copyright (c) 2016 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef WINPTY_SNPRINTF_H
+#define WINPTY_SNPRINTF_H
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "WinptyAssert.h"
+
+#if defined(__CYGWIN__) || defined(__MSYS__)
+#define WINPTY_SNPRINTF_FORMAT(fmtarg, vararg) \
+ __attribute__((format(printf, (fmtarg), ((vararg)))))
+#elif defined(__GNUC__)
+#define WINPTY_SNPRINTF_FORMAT(fmtarg, vararg) \
+ __attribute__((format(ms_printf, (fmtarg), ((vararg)))))
+#else
+#define WINPTY_SNPRINTF_FORMAT(fmtarg, vararg)
+#endif
+
+// Returns a value between 0 and size - 1 (inclusive) on success. Returns -1
+// on failure (including truncation). The output buffer is always
+// NUL-terminated.
+inline int
+winpty_vsnprintf(char *out, size_t size, const char *fmt, va_list ap) {
+ ASSERT(size > 0);
+ out[0] = '\0';
+#if defined(_MSC_VER) && _MSC_VER < 1900
+ // MSVC 2015 added a C99-conforming vsnprintf.
+ int count = _vsnprintf_s(out, size, _TRUNCATE, fmt, ap);
+#else
+ // MinGW configurations frequently provide a vsnprintf function that simply
+ // calls one of the MS _vsnprintf* functions, which are not C99 conformant.
+ int count = vsnprintf(out, size, fmt, ap);
+#endif
+ if (count < 0 || static_cast<size_t>(count) >= size) {
+ // On truncation, some *printf* implementations return the
+ // non-truncated size, but other implementations returns -1. Return
+ // -1 for consistency.
+ count = -1;
+ // Guarantee NUL termination.
+ out[size - 1] = '\0';
+ } else {
+ // Guarantee NUL termination.
+ out[count] = '\0';
+ }
+ return count;
+}
+
+// Wraps winpty_vsnprintf.
+inline int winpty_snprintf(char *out, size_t size, const char *fmt, ...)
+ WINPTY_SNPRINTF_FORMAT(3, 4);
+inline int winpty_snprintf(char *out, size_t size, const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ const int count = winpty_vsnprintf(out, size, fmt, ap);
+ va_end(ap);
+ return count;
+}
+
+// Wraps winpty_vsnprintf with automatic size determination.
+template <size_t size>
+int winpty_vsnprintf(char (&out)[size], const char *fmt, va_list ap) {
+ return winpty_vsnprintf(out, size, fmt, ap);
+}
+
+// Wraps winpty_vsnprintf with automatic size determination.
+template <size_t size>
+int winpty_snprintf(char (&out)[size], const char *fmt, ...)
+ WINPTY_SNPRINTF_FORMAT(2, 3);
+template <size_t size>
+int winpty_snprintf(char (&out)[size], const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ const int count = winpty_vsnprintf(out, size, fmt, ap);
+ va_end(ap);
+ return count;
+}
+
+#endif // WINPTY_SNPRINTF_H
diff --git a/src/libs/3rdparty/winpty/src/subdir.mk b/src/libs/3rdparty/winpty/src/subdir.mk
new file mode 100644
index 00000000000..9ae8031b084
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/subdir.mk
@@ -0,0 +1,5 @@
+include src/agent/subdir.mk
+include src/debugserver/subdir.mk
+include src/libwinpty/subdir.mk
+include src/tests/subdir.mk
+include src/unix-adapter/subdir.mk
diff --git a/src/libs/3rdparty/winpty/src/tests/subdir.mk b/src/libs/3rdparty/winpty/src/tests/subdir.mk
new file mode 100644
index 00000000000..18799c4a5a0
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/tests/subdir.mk
@@ -0,0 +1,28 @@
+# Copyright (c) 2015 Ryan Prichard
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+build/%.exe : src/tests/%.cc build/winpty.dll
+ $(info Building $@)
+ @$(MINGW_CXX) $(MINGW_CXXFLAGS) $(MINGW_LDFLAGS) -o $@ $^
+
+TEST_PROGRAMS = \
+ build/trivial_test.exe
+
+-include $(TEST_PROGRAMS:.exe=.d)
diff --git a/src/libs/3rdparty/winpty/src/tests/trivial_test.cc b/src/libs/3rdparty/winpty/src/tests/trivial_test.cc
new file mode 100644
index 00000000000..2188a4befb1
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/tests/trivial_test.cc
@@ -0,0 +1,158 @@
+// Copyright (c) 2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include <windows.h>
+
+#include <cassert>
+#include <cctype>
+#include <cstdio>
+#include <cstdlib>
+#include <cwchar>
+#include <vector>
+
+#include "../include/winpty.h"
+#include "../shared/DebugClient.h"
+
+static std::vector<unsigned char> filterContent(
+ const std::vector<unsigned char> &content) {
+ std::vector<unsigned char> result;
+ auto it = content.begin();
+ const auto itEnd = content.end();
+ while (it < itEnd) {
+ if (*it == '\r') {
+ // Filter out carriage returns. Sometimes the output starts with
+ // a single CR; other times, it has multiple CRs.
+ it++;
+ } else if (*it == '\x1b' && (it + 1) < itEnd && *(it + 1) == '[') {
+ // Filter out escape sequences. They have no interior letters and
+ // end with a single letter.
+ it += 2;
+ while (it < itEnd && !isalpha(*it)) {
+ it++;
+ }
+ it++;
+ } else {
+ // Let everything else through.
+ result.push_back(*it);
+ it++;
+ }
+ }
+ return result;
+}
+
+// Read bytes from the non-overlapped file handle until the file is closed or
+// until an I/O error occurs.
+static std::vector<unsigned char> readAll(HANDLE handle) {
+ unsigned char buf[1024];
+ std::vector<unsigned char> result;
+ while (true) {
+ DWORD amount = 0;
+ BOOL ret = ReadFile(handle, buf, sizeof(buf), &amount, nullptr);
+ if (!ret || amount == 0) {
+ break;
+ }
+ result.insert(result.end(), buf, buf + amount);
+ }
+ return result;
+}
+
+static void parentTest() {
+ wchar_t program[1024];
+ wchar_t cmdline[1024];
+ GetModuleFileNameW(nullptr, program, 1024);
+
+ {
+ // XXX: We'd like to use swprintf, which is part of C99 and takes a
+ // size_t maxlen argument. MinGW-w64 has this function, as does MSVC.
+ // The old MinGW doesn't, though -- instead, it apparently provides an
+ // swprintf taking no maxlen argument. This *might* be a regression?
+ // (There is also no swnprintf, but that function is obsolescent with a
+ // correct swprintf, and it isn't in POSIX or ISO C.)
+ //
+ // Visual C++ 6 also provided this non-conformant swprintf, and I'm
+ // guessing MSVCRT.DLL does too. (My impression is that the old MinGW
+ // prefers to rely on MSVCRT.DLL for convenience?)
+ //
+ // I could compile differently for old MinGW, but what if it fixes its
+ // function later? Instead, use a workaround. It's starting to make
+ // sense to drop MinGW support in favor of MinGW-w64. This is too
+ // annoying.
+ //
+ // grepbait: OLD-MINGW / WINPTY_TARGET_MSYS1
+ cmdline[0] = L'\0';
+ wcscat(cmdline, L"\"");
+ wcscat(cmdline, program);
+ wcscat(cmdline, L"\" CHILD");
+ }
+ // swnprintf(cmdline, sizeof(cmdline) / sizeof(cmdline[0]),
+ // L"\"%ls\" CHILD", program);
+
+ auto agentCfg = winpty_config_new(0, nullptr);
+ assert(agentCfg != nullptr);
+ auto pty = winpty_open(agentCfg, nullptr);
+ assert(pty != nullptr);
+ winpty_config_free(agentCfg);
+
+ HANDLE conin = CreateFileW(
+ winpty_conin_name(pty),
+ GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr);
+ HANDLE conout = CreateFileW(
+ winpty_conout_name(pty),
+ GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, nullptr);
+ assert(conin != INVALID_HANDLE_VALUE);
+ assert(conout != INVALID_HANDLE_VALUE);
+
+ auto spawnCfg = winpty_spawn_config_new(
+ WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN, program, cmdline,
+ nullptr, nullptr, nullptr);
+ assert(spawnCfg != nullptr);
+ HANDLE process = nullptr;
+ BOOL spawnSuccess = winpty_spawn(
+ pty, spawnCfg, &process, nullptr, nullptr, nullptr);
+ assert(spawnSuccess && process != nullptr);
+
+ auto content = readAll(conout);
+ content = filterContent(content);
+
+ std::vector<unsigned char> expectedContent = {
+ 'H', 'I', '\n', 'X', 'Y', '\n'
+ };
+ DWORD exitCode = 0;
+ assert(GetExitCodeProcess(process, &exitCode) && exitCode == 42);
+ CloseHandle(process);
+ CloseHandle(conin);
+ CloseHandle(conout);
+ assert(content == expectedContent);
+ winpty_free(pty);
+}
+
+static void childTest() {
+ printf("HI\nXY\n");
+ exit(42);
+}
+
+int main(int argc, char *argv[]) {
+ if (argc == 1) {
+ parentTest();
+ } else {
+ childTest();
+ }
+ return 0;
+}
diff --git a/src/libs/3rdparty/winpty/src/unix-adapter/InputHandler.cc b/src/libs/3rdparty/winpty/src/unix-adapter/InputHandler.cc
new file mode 100644
index 00000000000..39f1e096850
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/unix-adapter/InputHandler.cc
@@ -0,0 +1,114 @@
+// Copyright (c) 2011-2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include "InputHandler.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <sys/select.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <vector>
+
+#include "../shared/DebugClient.h"
+#include "Util.h"
+#include "WakeupFd.h"
+
+InputHandler::InputHandler(
+ HANDLE conin, int inputfd, WakeupFd &completionWakeup) :
+ m_conin(conin),
+ m_inputfd(inputfd),
+ m_completionWakeup(completionWakeup),
+ m_threadHasBeenJoined(false),
+ m_shouldShutdown(0),
+ m_threadCompleted(0)
+{
+ pthread_create(&m_thread, NULL, InputHandler::threadProcS, this);
+}
+
+void InputHandler::shutdown() {
+ startShutdown();
+ if (!m_threadHasBeenJoined) {
+ int ret = pthread_join(m_thread, NULL);
+ assert(ret == 0 && "pthread_join failed");
+ m_threadHasBeenJoined = true;
+ }
+}
+
+void InputHandler::threadProc() {
+ std::vector<char> buffer(4096);
+ fd_set readfds;
+ FD_ZERO(&readfds);
+ while (true) {
+ // Handle shutdown.
+ m_wakeup.reset();
+ if (m_shouldShutdown) {
+ trace("InputHandler: shutting down");
+ break;
+ }
+
+ // Block until data arrives.
+ {
+ const int max_fd = std::max(m_inputfd, m_wakeup.fd());
+ FD_SET(m_inputfd, &readfds);
+ FD_SET(m_wakeup.fd(), &readfds);
+ selectWrapper("InputHandler", max_fd + 1, &readfds);
+ if (!FD_ISSET(m_inputfd, &readfds)) {
+ continue;
+ }
+ }
+
+ const int numRead = read(m_inputfd, &buffer[0], buffer.size());
+ if (numRead == -1 && errno == EINTR) {
+ // Apparently, this read is interrupted on Cygwin 1.7 by a SIGWINCH
+ // signal even though I set the SA_RESTART flag on the handler.
+ continue;
+ }
+
+ // tty is closed, or the read failed for some unexpected reason.
+ if (numRead <= 0) {
+ trace("InputHandler: tty read failed: numRead=%d", numRead);
+ break;
+ }
+
+ DWORD written = 0;
+ BOOL ret = WriteFile(m_conin,
+ &buffer[0], numRead,
+ &written, NULL);
+ if (!ret || written != static_cast<DWORD>(numRead)) {
+ if (!ret && GetLastError() == ERROR_BROKEN_PIPE) {
+ trace("InputHandler: pipe closed: written=%u",
+ static_cast<unsigned int>(written));
+ } else {
+ trace("InputHandler: write failed: "
+ "ret=%d lastError=0x%x numRead=%d written=%u",
+ ret,
+ static_cast<unsigned int>(GetLastError()),
+ numRead,
+ static_cast<unsigned int>(written));
+ }
+ break;
+ }
+ }
+ m_threadCompleted = 1;
+ m_completionWakeup.set();
+}
diff --git a/src/libs/3rdparty/winpty/src/unix-adapter/InputHandler.h b/src/libs/3rdparty/winpty/src/unix-adapter/InputHandler.h
new file mode 100644
index 00000000000..9c3f540d634
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/unix-adapter/InputHandler.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2011-2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef UNIX_ADAPTER_INPUT_HANDLER_H
+#define UNIX_ADAPTER_INPUT_HANDLER_H
+
+#include <windows.h>
+#include <pthread.h>
+#include <signal.h>
+
+#include "WakeupFd.h"
+
+// Connect a Cygwin blocking fd to winpty CONIN.
+class InputHandler {
+public:
+ InputHandler(HANDLE conin, int inputfd, WakeupFd &completionWakeup);
+ ~InputHandler() { shutdown(); }
+ bool isComplete() { return m_threadCompleted; }
+ void startShutdown() { m_shouldShutdown = 1; m_wakeup.set(); }
+ void shutdown();
+
+private:
+ static void *threadProcS(void *pvthis) {
+ reinterpret_cast<InputHandler*>(pvthis)->threadProc();
+ return NULL;
+ }
+ void threadProc();
+
+ HANDLE m_conin;
+ int m_inputfd;
+ pthread_t m_thread;
+ WakeupFd &m_completionWakeup;
+ WakeupFd m_wakeup;
+ bool m_threadHasBeenJoined;
+ volatile sig_atomic_t m_shouldShutdown;
+ volatile sig_atomic_t m_threadCompleted;
+};
+
+#endif // UNIX_ADAPTER_INPUT_HANDLER_H
diff --git a/src/libs/3rdparty/winpty/src/unix-adapter/OutputHandler.cc b/src/libs/3rdparty/winpty/src/unix-adapter/OutputHandler.cc
new file mode 100644
index 00000000000..573b8adced3
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/unix-adapter/OutputHandler.cc
@@ -0,0 +1,80 @@
+// Copyright (c) 2011-2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include "OutputHandler.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <sys/select.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <vector>
+
+#include "../shared/DebugClient.h"
+#include "Util.h"
+#include "WakeupFd.h"
+
+OutputHandler::OutputHandler(
+ HANDLE conout, int outputfd, WakeupFd &completionWakeup) :
+ m_conout(conout),
+ m_outputfd(outputfd),
+ m_completionWakeup(completionWakeup),
+ m_threadHasBeenJoined(false),
+ m_threadCompleted(0)
+{
+ pthread_create(&m_thread, NULL, OutputHandler::threadProcS, this);
+}
+
+void OutputHandler::shutdown() {
+ if (!m_threadHasBeenJoined) {
+ int ret = pthread_join(m_thread, NULL);
+ assert(ret == 0 && "pthread_join failed");
+ m_threadHasBeenJoined = true;
+ }
+}
+
+void OutputHandler::threadProc() {
+ std::vector<char> buffer(4096);
+ while (true) {
+ DWORD numRead = 0;
+ BOOL ret = ReadFile(m_conout,
+ &buffer[0], buffer.size(),
+ &numRead, NULL);
+ if (!ret || numRead == 0) {
+ if (!ret && GetLastError() == ERROR_BROKEN_PIPE) {
+ trace("OutputHandler: pipe closed: numRead=%u",
+ static_cast<unsigned int>(numRead));
+ } else {
+ trace("OutputHandler: read failed: "
+ "ret=%d lastError=0x%x numRead=%u",
+ ret,
+ static_cast<unsigned int>(GetLastError()),
+ static_cast<unsigned int>(numRead));
+ }
+ break;
+ }
+ if (!writeAll(m_outputfd, &buffer[0], numRead)) {
+ break;
+ }
+ }
+ m_threadCompleted = 1;
+ m_completionWakeup.set();
+}
diff --git a/src/libs/3rdparty/winpty/src/unix-adapter/OutputHandler.h b/src/libs/3rdparty/winpty/src/unix-adapter/OutputHandler.h
new file mode 100644
index 00000000000..48241c55387
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/unix-adapter/OutputHandler.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2011-2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef UNIX_ADAPTER_OUTPUT_HANDLER_H
+#define UNIX_ADAPTER_OUTPUT_HANDLER_H
+
+#include <windows.h>
+#include <pthread.h>
+#include <signal.h>
+
+#include "WakeupFd.h"
+
+// Connect winpty CONOUT/CONERR to a Cygwin blocking fd.
+class OutputHandler {
+public:
+ OutputHandler(HANDLE conout, int outputfd, WakeupFd &completionWakeup);
+ ~OutputHandler() { shutdown(); }
+ bool isComplete() { return m_threadCompleted; }
+ void shutdown();
+
+private:
+ static void *threadProcS(void *pvthis) {
+ reinterpret_cast<OutputHandler*>(pvthis)->threadProc();
+ return NULL;
+ }
+ void threadProc();
+
+ HANDLE m_conout;
+ int m_outputfd;
+ pthread_t m_thread;
+ WakeupFd &m_completionWakeup;
+ bool m_threadHasBeenJoined;
+ volatile sig_atomic_t m_threadCompleted;
+};
+
+#endif // UNIX_ADAPTER_OUTPUT_HANDLER_H
diff --git a/src/libs/3rdparty/winpty/src/unix-adapter/Util.cc b/src/libs/3rdparty/winpty/src/unix-adapter/Util.cc
new file mode 100644
index 00000000000..e13f84a5299
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/unix-adapter/Util.cc
@@ -0,0 +1,86 @@
+// Copyright (c) 2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include "Util.h"
+
+#include <assert.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "../shared/DebugClient.h"
+
+// Write the entire buffer, restarting it as necessary.
+bool writeAll(int fd, const void *buffer, size_t size) {
+ size_t written = 0;
+ while (written < size) {
+ int ret = write(fd,
+ reinterpret_cast<const char*>(buffer) + written,
+ size - written);
+ if (ret == -1 && errno == EINTR) {
+ continue;
+ }
+ if (ret <= 0) {
+ trace("write failed: "
+ "fd=%d errno=%d size=%u written=%d ret=%d",
+ fd,
+ errno,
+ static_cast<unsigned int>(size),
+ static_cast<unsigned int>(written),
+ ret);
+ return false;
+ }
+ assert(static_cast<size_t>(ret) <= size - written);
+ written += ret;
+ }
+ assert(written == size);
+ return true;
+}
+
+bool writeStr(int fd, const char *str) {
+ return writeAll(fd, str, strlen(str));
+}
+
+void selectWrapper(const char *diagName, int nfds, fd_set *readfds) {
+ int ret = select(nfds, readfds, NULL, NULL, NULL);
+ if (ret < 0) {
+ if (errno == EINTR) {
+ FD_ZERO(readfds);
+ return;
+ }
+#ifdef WINPTY_TARGET_MSYS1
+ // The select system call sometimes fails with EAGAIN instead of EINTR.
+ // This apparantly only happens with the old Cygwin fork "MSYS" used in
+ // the mingw.org project. select is not supposed to fail with EAGAIN,
+ // and EAGAIN does not make much sense as an error code. (The whole
+ // point of select is to block.)
+ if (errno == EAGAIN) {
+ trace("%s select returned EAGAIN: interpreting like EINTR",
+ diagName);
+ FD_ZERO(readfds);
+ return;
+ }
+#endif
+ fprintf(stderr, "Internal error: %s select failed: "
+ "error %d", diagName, errno);
+ abort();
+ }
+}
diff --git a/src/libs/3rdparty/winpty/src/unix-adapter/Util.h b/src/libs/3rdparty/winpty/src/unix-adapter/Util.h
new file mode 100644
index 00000000000..cadb4c82a96
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/unix-adapter/Util.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef UNIX_ADAPTER_UTIL_H
+#define UNIX_ADAPTER_UTIL_H
+
+#include <stdlib.h>
+#include <sys/select.h>
+
+bool writeAll(int fd, const void *buffer, size_t size);
+bool writeStr(int fd, const char *str);
+void selectWrapper(const char *diagName, int nfds, fd_set *readfds);
+
+#endif // UNIX_ADAPTER_UTIL_H
diff --git a/src/libs/3rdparty/winpty/src/unix-adapter/WakeupFd.cc b/src/libs/3rdparty/winpty/src/unix-adapter/WakeupFd.cc
new file mode 100644
index 00000000000..6b473790153
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/unix-adapter/WakeupFd.cc
@@ -0,0 +1,70 @@
+// Copyright (c) 2011-2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include "WakeupFd.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+static void setFdNonBlock(int fd) {
+ int status = fcntl(fd, F_GETFL);
+ fcntl(fd, F_SETFL, status | O_NONBLOCK);
+}
+
+WakeupFd::WakeupFd() {
+ int pipeFd[2];
+ if (pipe(pipeFd) != 0) {
+ perror("Could not create internal wakeup pipe");
+ abort();
+ }
+ m_pipeReadFd = pipeFd[0];
+ m_pipeWriteFd = pipeFd[1];
+ setFdNonBlock(m_pipeReadFd);
+ setFdNonBlock(m_pipeWriteFd);
+}
+
+WakeupFd::~WakeupFd() {
+ close(m_pipeReadFd);
+ close(m_pipeWriteFd);
+}
+
+void WakeupFd::set() {
+ char dummy = 0;
+ int ret;
+ do {
+ ret = write(m_pipeWriteFd, &dummy, 1);
+ } while (ret < 0 && errno == EINTR);
+}
+
+void WakeupFd::reset() {
+ char tmpBuf[256];
+ while (true) {
+ int amount = read(m_pipeReadFd, tmpBuf, sizeof(tmpBuf));
+ if (amount < 0 && errno == EAGAIN) {
+ break;
+ } else if (amount <= 0) {
+ perror("error reading from internal wakeup pipe");
+ abort();
+ }
+ }
+}
diff --git a/src/libs/3rdparty/winpty/src/unix-adapter/WakeupFd.h b/src/libs/3rdparty/winpty/src/unix-adapter/WakeupFd.h
new file mode 100644
index 00000000000..dd8d362aa10
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/unix-adapter/WakeupFd.h
@@ -0,0 +1,42 @@
+// Copyright (c) 2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#ifndef UNIX_ADAPTER_WAKEUP_FD_H
+#define UNIX_ADAPTER_WAKEUP_FD_H
+
+class WakeupFd {
+public:
+ WakeupFd();
+ ~WakeupFd();
+ int fd() { return m_pipeReadFd; }
+ void set();
+ void reset();
+
+private:
+ // Do not allow copying the WakeupFd object.
+ WakeupFd(const WakeupFd &other);
+ WakeupFd &operator=(const WakeupFd &other);
+
+private:
+ int m_pipeReadFd;
+ int m_pipeWriteFd;
+};
+
+#endif // UNIX_ADAPTER_WAKEUP_FD_H
diff --git a/src/libs/3rdparty/winpty/src/unix-adapter/main.cc b/src/libs/3rdparty/winpty/src/unix-adapter/main.cc
new file mode 100644
index 00000000000..992cb70e449
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/unix-adapter/main.cc
@@ -0,0 +1,729 @@
+// Copyright (c) 2011-2015 Ryan Prichard
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+// MSYS's sys/cygwin.h header only declares cygwin_internal if WINVER is
+// defined, which is defined in windows.h. Therefore, include windows.h early.
+#include <windows.h>
+
+#include <assert.h>
+#include <cygwin/version.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/select.h>
+#include <sys/cygwin.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <winpty.h>
+#include "../shared/DebugClient.h"
+#include "../shared/UnixCtrlChars.h"
+#include "../shared/WinptyVersion.h"
+#include "InputHandler.h"
+#include "OutputHandler.h"
+#include "Util.h"
+#include "WakeupFd.h"
+
+#define CSI "\x1b["
+
+static WakeupFd *g_mainWakeup = NULL;
+
+static WakeupFd &mainWakeup()
+{
+ if (g_mainWakeup == NULL) {
+ static const char msg[] = "Internal error: g_mainWakeup is NULL\r\n";
+ write(STDERR_FILENO, msg, sizeof(msg) - 1);
+ abort();
+ }
+ return *g_mainWakeup;
+}
+
+struct SavedTermiosMode {
+ int count;
+ bool valid[3];
+ termios mode[3];
+};
+
+// Put the input terminal into non-canonical mode.
+static SavedTermiosMode setRawTerminalMode(
+ bool allowNonTtys, bool setStdout, bool setStderr)
+{
+ SavedTermiosMode ret;
+ const char *const kNames[3] = { "stdin", "stdout", "stderr" };
+
+ ret.valid[0] = true;
+ ret.valid[1] = setStdout;
+ ret.valid[2] = setStderr;
+
+ for (int i = 0; i < 3; ++i) {
+ if (!ret.valid[i]) {
+ continue;
+ }
+ if (!isatty(i)) {
+ ret.valid[i] = false;
+ if (!allowNonTtys) {
+ fprintf(stderr, "%s is not a tty\n", kNames[i]);
+ exit(1);
+ }
+ } else {
+ ret.valid[i] = true;
+ if (tcgetattr(i, &ret.mode[i]) < 0) {
+ perror("tcgetattr failed");
+ exit(1);
+ }
+ }
+ }
+
+ if (ret.valid[STDIN_FILENO]) {
+ termios buf;
+ if (tcgetattr(STDIN_FILENO, &buf) < 0) {
+ perror("tcgetattr failed");
+ exit(1);
+ }
+ buf.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
+ buf.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
+ buf.c_cflag &= ~(CSIZE | PARENB);
+ buf.c_cflag |= CS8;
+ buf.c_cc[VMIN] = 1; // blocking read
+ buf.c_cc[VTIME] = 0;
+ if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &buf) < 0) {
+ fprintf(stderr, "tcsetattr failed\n");
+ exit(1);
+ }
+ }
+
+ for (int i = STDOUT_FILENO; i <= STDERR_FILENO; ++i) {
+ if (!ret.valid[i]) {
+ continue;
+ }
+ termios buf;
+ if (tcgetattr(i, &buf) < 0) {
+ perror("tcgetattr failed");
+ exit(1);
+ }
+ buf.c_cflag &= ~(CSIZE | PARENB);
+ buf.c_cflag |= CS8;
+ buf.c_oflag &= ~OPOST;
+ if (tcsetattr(i, TCSAFLUSH, &buf) < 0) {
+ fprintf(stderr, "tcsetattr failed\n");
+ exit(1);
+ }
+ }
+
+ return ret;
+}
+
+static void restoreTerminalMode(const SavedTermiosMode &original)
+{
+ for (int i = 0; i < 3; ++i) {
+ if (!original.valid[i]) {
+ continue;
+ }
+ if (tcsetattr(i, TCSAFLUSH, &original.mode[i]) < 0) {
+ perror("error restoring terminal mode");
+ exit(1);
+ }
+ }
+}
+
+static void debugShowKey(bool allowNonTtys)
+{
+ printf("\nPress any keys -- Ctrl-D exits\n\n");
+ const SavedTermiosMode saved =
+ setRawTerminalMode(allowNonTtys, false, false);
+ char buf[128];
+ while (true) {
+ const ssize_t len = read(STDIN_FILENO, buf, sizeof(buf));
+ if (len <= 0) {
+ break;
+ }
+ for (int i = 0; i < len; ++i) {
+ char ctrl = decodeUnixCtrlChar(buf[i]);
+ if (ctrl == '\0') {
+ putchar(buf[i]);
+ } else {
+ putchar('^');
+ putchar(ctrl);
+ }
+ }
+ for (int i = 0; i < len; ++i) {
+ unsigned char uch = buf[i];
+ printf("\t%3d %04o 0x%02x\n", uch, uch, uch);
+ fflush(stdout);
+ }
+ if (buf[0] == 4) {
+ // Ctrl-D
+ break;
+ }
+ }
+ restoreTerminalMode(saved);
+}
+
+static void terminalResized(int signo)
+{
+ mainWakeup().set();
+}
+
+static void registerResizeSignalHandler()
+{
+ struct sigaction resizeSigAct;
+ memset(&resizeSigAct, 0, sizeof(resizeSigAct));
+ resizeSigAct.sa_handler = terminalResized;
+ resizeSigAct.sa_flags = SA_RESTART;
+ sigaction(SIGWINCH, &resizeSigAct, NULL);
+}
+
+// Convert the path to a Win32 path if it is a POSIX path, and convert slashes
+// to backslashes.
+static std::string convertPosixPathToWin(const std::string &path)
+{
+ char *tmp;
+#if defined(CYGWIN_VERSION_CYGWIN_CONV) && \
+ CYGWIN_VERSION_API_MINOR >= CYGWIN_VERSION_CYGWIN_CONV
+ // MSYS2 and versions of Cygwin released after 2009 or so use this API.
+ // The original MSYS still lacks this API.
+ ssize_t newSize = cygwin_conv_path(CCP_POSIX_TO_WIN_A | CCP_ABSOLUTE,
+ path.c_str(), NULL, 0);
+ assert(newSize >= 0);
+ tmp = new char[newSize + 1];
+ ssize_t success = cygwin_conv_path(CCP_POSIX_TO_WIN_A | CCP_ABSOLUTE,
+ path.c_str(), tmp, newSize + 1);
+ assert(success == 0);
+#else
+ // In the current Cygwin header file, this API is documented as deprecated
+ // because it's restricted to paths of MAX_PATH length. In the CVS version
+ // of MSYS, the newer API doesn't exist, and this older API is implemented
+ // using msys_p2w, which seems like it would handle paths larger than
+ // MAX_PATH, but there's no way to query how large the new path is.
+ // Hopefully, this is large enough.
+ tmp = new char[MAX_PATH + path.size()];
+ cygwin_conv_to_win32_path(path.c_str(), tmp);
+#endif
+ for (int i = 0; tmp[i] != '\0'; ++i) {
+ if (tmp[i] == '/')
+ tmp[i] = '\\';
+ }
+ std::string ret(tmp);
+ delete [] tmp;
+ return ret;
+}
+
+static std::string resolvePath(const std::string &path)
+{
+ char ret[PATH_MAX];
+ ret[0] = '\0';
+ if (realpath(path.c_str(), ret) != ret) {
+ return std::string();
+ }
+ return ret;
+}
+
+template <size_t N>
+static bool endsWith(const std::string &path, const char (&suf)[N])
+{
+ const size_t suffixLen = N - 1;
+ char actualSuf[N];
+ if (path.size() < suffixLen) {
+ return false;
+ }
+ strcpy(actualSuf, &path.c_str()[path.size() - suffixLen]);
+ for (size_t i = 0; i < suffixLen; ++i) {
+ actualSuf[i] = tolower(actualSuf[i]);
+ }
+ return !strcmp(actualSuf, suf);
+}
+
+static std::string findProgram(
+ const char *winptyProgName,
+ const std::string &prog)
+{
+ std::string candidate;
+ if (prog.find('/') == std::string::npos &&
+ prog.find('\\') == std::string::npos) {
+ // XXX: It would be nice to use a lambda here (once/if old MSYS support
+ // is dropped).
+ // Search the PATH.
+ const char *const pathVar = getenv("PATH");
+ const std::string pathList(pathVar ? pathVar : "");
+ size_t elpos = 0;
+ while (true) {
+ const size_t elend = pathList.find(':', elpos);
+ candidate = pathList.substr(elpos, elend - elpos);
+ if (!candidate.empty() && *(candidate.end() - 1) != '/') {
+ candidate += '/';
+ }
+ candidate += prog;
+ candidate = resolvePath(candidate);
+ if (!candidate.empty()) {
+ int perm = X_OK;
+ if (endsWith(candidate, ".bat") || endsWith(candidate, ".cmd")) {
+#ifdef __MSYS__
+ // In MSYS/MSYS2, batch files don't have the execute bit
+ // set, so just check that they're readable.
+ perm = R_OK;
+#endif
+ } else if (endsWith(candidate, ".com") || endsWith(candidate, ".exe")) {
+ // Do nothing.
+ } else {
+ // Make the exe extension explicit so that we don't try to
+ // run shell scripts with CreateProcess/winpty_spawn.
+ candidate += ".exe";
+ }
+ if (!access(candidate.c_str(), perm)) {
+ break;
+ }
+ }
+ if (elend == std::string::npos) {
+ fprintf(stderr, "%s: error: cannot start '%s': Not found in PATH\n",
+ winptyProgName, prog.c_str());
+ exit(1);
+ } else {
+ elpos = elend + 1;
+ }
+ }
+ } else {
+ candidate = resolvePath(prog);
+ if (candidate.empty()) {
+ std::string errstr(strerror(errno));
+ fprintf(stderr, "%s: error: cannot start '%s': %s\n",
+ winptyProgName, prog.c_str(), errstr.c_str());
+ exit(1);
+ }
+ }
+ return convertPosixPathToWin(candidate);
+}
+
+// Convert argc/argv into a Win32 command-line following the escaping convention
+// documented on MSDN. (e.g. see CommandLineToArgvW documentation)
+static std::string argvToCommandLine(const std::vector<std::string> &argv)
+{
+ std::string result;
+ for (size_t argIndex = 0; argIndex < argv.size(); ++argIndex) {
+ if (argIndex > 0)
+ result.push_back(' ');
+ const char *arg = argv[argIndex].c_str();
+ const bool quote =
+ strchr(arg, ' ') != NULL ||
+ strchr(arg, '\t') != NULL ||
+ *arg == '\0';
+ if (quote)
+ result.push_back('\"');
+ int bsCount = 0;
+ for (const char *p = arg; *p != '\0'; ++p) {
+ if (*p == '\\') {
+ bsCount++;
+ } else if (*p == '\"') {
+ result.append(bsCount * 2 + 1, '\\');
+ result.push_back('\"');
+ bsCount = 0;
+ } else {
+ result.append(bsCount, '\\');
+ bsCount = 0;
+ result.push_back(*p);
+ }
+ }
+ if (quote) {
+ result.append(bsCount * 2, '\\');
+ result.push_back('\"');
+ } else {
+ result.append(bsCount, '\\');
+ }
+ }
+ return result;
+}
+
+static wchar_t *heapMbsToWcs(const char *text)
+{
+ // Calling mbstowcs with a NULL first argument seems to be broken on MSYS.
+ // Instead of returning the size of the converted string, it returns 0.
+ // Using strlen(text) * 2 is probably big enough.
+ size_t maxLen = strlen(text) * 2 + 1;
+ wchar_t *ret = new wchar_t[maxLen];
+ size_t len = mbstowcs(ret, text, maxLen);
+ assert(len != (size_t)-1 && len < maxLen);
+ return ret;
+}
+
+static char *heapWcsToMbs(const wchar_t *text)
+{
+ // Calling wcstombs with a NULL first argument seems to be broken on MSYS.
+ // Instead of returning the size of the converted string, it returns 0.
+ // Using wcslen(text) * 3 is big enough for UTF-8 and probably other
+ // encodings. For UTF-8, codepoints that fit in a single wchar
+ // (U+0000 to U+FFFF) are encoded using 1-3 bytes. The remaining code
+ // points needs two wchar's and are encoded using 4 bytes.
+ size_t maxLen = wcslen(text) * 3 + 1;
+ char *ret = new char[maxLen];
+ size_t len = wcstombs(ret, text, maxLen);
+ if (len == (size_t)-1 || len >= maxLen) {
+ delete [] ret;
+ return NULL;
+ } else {
+ return ret;
+ }
+}
+
+static std::string wcsToMbs(const wchar_t *text)
+{
+ std::string ret;
+ const char *ptr = heapWcsToMbs(text);
+ if (ptr != NULL) {
+ ret = ptr;
+ delete [] ptr;
+ }
+ return ret;
+}
+
+void setupWin32Environment()
+{
+ std::map<std::string, std::string> varsToCopy;
+ const char *vars[] = {
+ "WINPTY_DEBUG",
+ "WINPTY_SHOW_CONSOLE",
+ NULL
+ };
+ for (int i = 0; vars[i] != NULL; ++i) {
+ const char *cstr = getenv(vars[i]);
+ if (cstr != NULL && cstr[0] != '\0') {
+ varsToCopy[vars[i]] = cstr;
+ }
+ }
+
+#if defined(__MSYS__) && CYGWIN_VERSION_API_MINOR >= 48 || \
+ !defined(__MSYS__) && CYGWIN_VERSION_API_MINOR >= 153
+ // Use CW_SYNC_WINENV to copy the Unix environment to the Win32
+ // environment. The command performs special translation on some variables
+ // (such as PATH and TMP). It also copies the debugging environment
+ // variables.
+ //
+ // Note that the API minor versions have diverged in Cygwin and MSYS.
+ // CW_SYNC_WINENV was added to Cygwin in version 153. (Cygwin's
+ // include/cygwin/version.h says that CW_SETUP_WINENV was added in 153.
+ // The flag was renamed 8 days after it was added, but the API docs weren't
+ // updated.) The flag was added to MSYS in version 48.
+ //
+ // Also, in my limited testing, this call seems to be necessary with Cygwin
+ // but unnecessary with MSYS. Perhaps MSYS is automatically syncing the
+ // Unix environment with the Win32 environment before starting console.exe?
+ // It shouldn't hurt to call it for MSYS.
+ cygwin_internal(CW_SYNC_WINENV);
+#endif
+
+ // Copy debugging environment variables from the Cygwin environment
+ // to the Win32 environment so the agent will inherit it.
+ for (std::map<std::string, std::string>::iterator it = varsToCopy.begin();
+ it != varsToCopy.end();
+ ++it) {
+ wchar_t *nameW = heapMbsToWcs(it->first.c_str());
+ wchar_t *valueW = heapMbsToWcs(it->second.c_str());
+ SetEnvironmentVariableW(nameW, valueW);
+ delete [] nameW;
+ delete [] valueW;
+ }
+
+ // Clear the TERM variable. The child process's immediate console/terminal
+ // environment is a Windows console, not the terminal that winpty is
+ // communicating with. Leaving the TERM variable set can break programs in
+ // various ways. (e.g. arrows keys broken in Cygwin less, IronPython's
+ // help(...) function doesn't start, misc programs decide they should
+ // output color escape codes on pre-Win10). See
+ // https://github.com/rprichard/winpty/issues/43.
+ SetEnvironmentVariableW(L"TERM", NULL);
+}
+
+static void usage(const char *program, int exitCode)
+{
+ printf("Usage: %s [options] [--] program [args]\n", program);
+ printf("\n");
+ printf("Options:\n");
+ printf(" -h, --help Show this help message\n");
+ printf(" --mouse Enable terminal mouse input\n");
+ printf(" --showkey Dump STDIN escape sequences\n");
+ printf(" --version Show the winpty version number\n");
+ exit(exitCode);
+}
+
+struct Arguments {
+ std::vector<std::string> childArgv;
+ bool mouseInput;
+ bool testAllowNonTtys;
+ bool testConerr;
+ bool testPlainOutput;
+ bool testColorEscapes;
+};
+
+static void parseArguments(int argc, char *argv[], Arguments &out)
+{
+ out.mouseInput = false;
+ out.testAllowNonTtys = false;
+ out.testConerr = false;
+ out.testPlainOutput = false;
+ out.testColorEscapes = false;
+ bool doShowKeys = false;
+ const char *const program = argc >= 1 ? argv[0] : "<program>";
+ int argi = 1;
+ while (argi < argc) {
+ std::string arg(argv[argi++]);
+ if (arg.size() >= 1 && arg[0] == '-') {
+ if (arg == "-h" || arg == "--help") {
+ usage(program, 0);
+ } else if (arg == "--mouse") {
+ out.mouseInput = true;
+ } else if (arg == "--showkey") {
+ doShowKeys = true;
+ } else if (arg == "--version") {
+ dumpVersionToStdout();
+ exit(0);
+ } else if (arg == "-Xallow-non-tty") {
+ out.testAllowNonTtys = true;
+ } else if (arg == "-Xconerr") {
+ out.testConerr = true;
+ } else if (arg == "-Xplain") {
+ out.testPlainOutput = true;
+ } else if (arg == "-Xcolor") {
+ out.testColorEscapes = true;
+ } else if (arg == "--") {
+ break;
+ } else {
+ fprintf(stderr, "Error: unrecognized option: '%s'\n",
+ arg.c_str());
+ exit(1);
+ }
+ } else {
+ out.childArgv.push_back(arg);
+ break;
+ }
+ }
+ for (; argi < argc; ++argi) {
+ out.childArgv.push_back(argv[argi]);
+ }
+ if (doShowKeys) {
+ debugShowKey(out.testAllowNonTtys);
+ exit(0);
+ }
+ if (out.childArgv.size() == 0) {
+ usage(program, 1);
+ }
+}
+
+static std::string errorMessageToString(DWORD err)
+{
+ // Use FormatMessageW rather than FormatMessageA, because we want to use
+ // wcstombs to convert to the Cygwin locale, which might not match the
+ // codepage FormatMessageA would use. We need to convert using wcstombs,
+ // rather than print using %ls, because %ls doesn't work in the original
+ // MSYS.
+ wchar_t *wideMsgPtr = NULL;
+ const DWORD formatRet = FormatMessageW(
+ FORMAT_MESSAGE_FROM_SYSTEM |
+ FORMAT_MESSAGE_ALLOCATE_BUFFER |
+ FORMAT_MESSAGE_IGNORE_INSERTS,
+ NULL,
+ err,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ reinterpret_cast<wchar_t*>(&wideMsgPtr),
+ 0,
+ NULL);
+ if (formatRet == 0 || wideMsgPtr == NULL) {
+ return std::string();
+ }
+ std::string msg = wcsToMbs(wideMsgPtr);
+ LocalFree(wideMsgPtr);
+ const size_t pos = msg.find_last_not_of(" \r\n\t");
+ if (pos == std::string::npos) {
+ msg.clear();
+ } else {
+ msg.erase(pos + 1);
+ }
+ return msg;
+}
+
+static std::string formatErrorMessage(DWORD err)
+{
+ char buf[64];
+ sprintf(buf, "error %#x", static_cast<unsigned int>(err));
+ std::string ret = errorMessageToString(err);
+ if (ret.empty()) {
+ ret += buf;
+ } else {
+ ret += " (";
+ ret += buf;
+ ret += ")";
+ }
+ return ret;
+}
+
+int main(int argc, char *argv[])
+{
+ setlocale(LC_ALL, "");
+
+ g_mainWakeup = new WakeupFd();
+
+ Arguments args;
+ parseArguments(argc, argv, args);
+
+ setupWin32Environment();
+
+ winsize sz = { 0 };
+ sz.ws_col = 80;
+ sz.ws_row = 25;
+ ioctl(STDIN_FILENO, TIOCGWINSZ, &sz);
+
+ DWORD agentFlags = WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION;
+ if (args.testConerr) { agentFlags |= WINPTY_FLAG_CONERR; }
+ if (args.testPlainOutput) { agentFlags |= WINPTY_FLAG_PLAIN_OUTPUT; }
+ if (args.testColorEscapes) { agentFlags |= WINPTY_FLAG_COLOR_ESCAPES; }
+ winpty_config_t *agentCfg = winpty_config_new(agentFlags, NULL);
+ assert(agentCfg != NULL);
+ winpty_config_set_initial_size(agentCfg, sz.ws_col, sz.ws_row);
+ if (args.mouseInput) {
+ winpty_config_set_mouse_mode(agentCfg, WINPTY_MOUSE_MODE_FORCE);
+ }
+
+ winpty_error_ptr_t openErr = NULL;
+ winpty_t *wp = winpty_open(agentCfg, &openErr);
+ if (wp == NULL) {
+ fprintf(stderr, "Error creating winpty: %s\n",
+ wcsToMbs(winpty_error_msg(openErr)).c_str());
+ exit(1);
+ }
+ winpty_config_free(agentCfg);
+ winpty_error_free(openErr);
+
+ HANDLE conin = CreateFileW(winpty_conin_name(wp), GENERIC_WRITE, 0, NULL,
+ OPEN_EXISTING, 0, NULL);
+ HANDLE conout = CreateFileW(winpty_conout_name(wp), GENERIC_READ, 0, NULL,
+ OPEN_EXISTING, 0, NULL);
+ assert(conin != INVALID_HANDLE_VALUE);
+ assert(conout != INVALID_HANDLE_VALUE);
+ HANDLE conerr = NULL;
+ if (args.testConerr) {
+ conerr = CreateFileW(winpty_conerr_name(wp), GENERIC_READ, 0, NULL,
+ OPEN_EXISTING, 0, NULL);
+ assert(conerr != INVALID_HANDLE_VALUE);
+ }
+
+ HANDLE childHandle = NULL;
+
+ {
+ // Start the child process under the console.
+ args.childArgv[0] = findProgram(argv[0], args.childArgv[0]);
+ std::string cmdLine = argvToCommandLine(args.childArgv);
+ wchar_t *cmdLineW = heapMbsToWcs(cmdLine.c_str());
+
+ winpty_spawn_config_t *spawnCfg = winpty_spawn_config_new(
+ WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN,
+ NULL, cmdLineW, NULL, NULL, NULL);
+ assert(spawnCfg != NULL);
+
+ winpty_error_ptr_t spawnErr = NULL;
+ DWORD lastError = 0;
+ BOOL spawnRet = winpty_spawn(wp, spawnCfg, &childHandle, NULL,
+ &lastError, &spawnErr);
+ winpty_spawn_config_free(spawnCfg);
+
+ if (!spawnRet) {
+ winpty_result_t spawnCode = winpty_error_code(spawnErr);
+ if (spawnCode == WINPTY_ERROR_SPAWN_CREATE_PROCESS_FAILED) {
+ fprintf(stderr, "%s: error: cannot start '%s': %s\n",
+ argv[0],
+ cmdLine.c_str(),
+ formatErrorMessage(lastError).c_str());
+ } else {
+ fprintf(stderr, "%s: error: cannot start '%s': internal error: %s\n",
+ argv[0],
+ cmdLine.c_str(),
+ wcsToMbs(winpty_error_msg(spawnErr)).c_str());
+ }
+ exit(1);
+ }
+ winpty_error_free(spawnErr);
+ delete [] cmdLineW;
+ }
+
+ registerResizeSignalHandler();
+ SavedTermiosMode mode =
+ setRawTerminalMode(args.testAllowNonTtys, true, args.testConerr);
+
+ InputHandler inputHandler(conin, STDIN_FILENO, mainWakeup());
+ OutputHandler outputHandler(conout, STDOUT_FILENO, mainWakeup());
+ OutputHandler *errorHandler = NULL;
+ if (args.testConerr) {
+ errorHandler = new OutputHandler(conerr, STDERR_FILENO, mainWakeup());
+ }
+
+ while (true) {
+ fd_set readfds;
+ FD_ZERO(&readfds);
+ FD_SET(mainWakeup().fd(), &readfds);
+ selectWrapper("main thread", mainWakeup().fd() + 1, &readfds);
+ mainWakeup().reset();
+
+ // Check for terminal resize.
+ {
+ winsize sz2;
+ ioctl(STDIN_FILENO, TIOCGWINSZ, &sz2);
+ if (memcmp(&sz, &sz2, sizeof(sz)) != 0) {
+ sz = sz2;
+ winpty_set_size(wp, sz.ws_col, sz.ws_row, NULL);
+ }
+ }
+
+ // Check for an I/O handler shutting down (possibly indicating that the
+ // child process has exited).
+ if (inputHandler.isComplete() || outputHandler.isComplete() ||
+ (errorHandler != NULL && errorHandler->isComplete())) {
+ break;
+ }
+ }
+
+ // Kill the agent connection. This will kill the agent, closing the CONIN
+ // and CONOUT pipes on the agent pipe, prompting our I/O handler to shut
+ // down.
+ winpty_free(wp);
+
+ inputHandler.shutdown();
+ outputHandler.shutdown();
+ CloseHandle(conin);
+ CloseHandle(conout);
+
+ if (errorHandler != NULL) {
+ errorHandler->shutdown();
+ delete errorHandler;
+ CloseHandle(conerr);
+ }
+
+ restoreTerminalMode(mode);
+
+ DWORD exitCode = 0;
+ if (!GetExitCodeProcess(childHandle, &exitCode)) {
+ exitCode = 1;
+ }
+ CloseHandle(childHandle);
+ return exitCode;
+}
diff --git a/src/libs/3rdparty/winpty/src/unix-adapter/subdir.mk b/src/libs/3rdparty/winpty/src/unix-adapter/subdir.mk
new file mode 100644
index 00000000000..200193a1b15
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/unix-adapter/subdir.mk
@@ -0,0 +1,41 @@
+# Copyright (c) 2011-2015 Ryan Prichard
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+ALL_TARGETS += build/$(UNIX_ADAPTER_EXE)
+
+$(eval $(call def_unix_target,unix-adapter,))
+
+UNIX_ADAPTER_OBJECTS = \
+ build/unix-adapter/unix-adapter/InputHandler.o \
+ build/unix-adapter/unix-adapter/OutputHandler.o \
+ build/unix-adapter/unix-adapter/Util.o \
+ build/unix-adapter/unix-adapter/WakeupFd.o \
+ build/unix-adapter/unix-adapter/main.o \
+ build/unix-adapter/shared/DebugClient.o \
+ build/unix-adapter/shared/WinptyAssert.o \
+ build/unix-adapter/shared/WinptyVersion.o
+
+build/unix-adapter/shared/WinptyVersion.o : build/gen/GenVersion.h
+
+build/$(UNIX_ADAPTER_EXE) : $(UNIX_ADAPTER_OBJECTS) build/winpty.dll
+ $(info Linking $@)
+ @$(UNIX_CXX) $(UNIX_LDFLAGS) -o $@ $^
+
+-include $(UNIX_ADAPTER_OBJECTS:.o=.d)
diff --git a/src/libs/3rdparty/winpty/src/winpty.gyp b/src/libs/3rdparty/winpty/src/winpty.gyp
new file mode 100644
index 00000000000..7ee68d55e60
--- /dev/null
+++ b/src/libs/3rdparty/winpty/src/winpty.gyp
@@ -0,0 +1,206 @@
+{
+ # The MSVC generator is the default. Select the compiler version by
+ # passing -G msvs_version=<ver> to gyp. <ver> is a string like 2013e.
+ # See gyp\pylib\gyp\MSVSVersion.py for sample version strings. You
+ # can also pass configurations.gypi to gyp for 32-bit and 64-bit builds.
+ # See that file for details.
+ #
+ # Pass --format=make to gyp to generate a Makefile instead. The Makefile
+ # can be configured by passing variables to make, e.g.:
+ # make -j4 CXX=i686-w64-mingw32-g++ LDFLAGS="-static -static-libgcc -static-libstdc++"
+
+ 'variables': {
+ 'WINPTY_COMMIT_HASH%': '<!(cmd /c "cd shared && GetCommitHash.bat")',
+ },
+ 'target_defaults' : {
+ 'defines' : [
+ 'UNICODE',
+ '_UNICODE',
+ '_WIN32_WINNT=0x0501',
+ 'NOMINMAX',
+ ],
+ 'include_dirs': [
+ # Add the 'src/gen' directory to the include path and force gyp to
+ # run the script (re)generating the version header.
+ '<!(cmd /c "cd shared && UpdateGenVersion.bat <(WINPTY_COMMIT_HASH)")',
+ ],
+ },
+ 'targets' : [
+ {
+ 'target_name' : 'winpty-agent',
+ 'type' : 'executable',
+ 'include_dirs' : [
+ 'include',
+ ],
+ 'defines' : [
+ 'WINPTY_AGENT_ASSERT',
+ ],
+ 'libraries' : [
+ '-ladvapi32',
+ '-lshell32',
+ '-luser32',
+ ],
+ 'msvs_settings': {
+ # Specify this setting here to override a setting from somewhere
+ # else, such as node's common.gypi.
+ 'VCCLCompilerTool': {
+ 'ExceptionHandling': '1', # /EHsc
+ },
+ },
+ 'sources' : [
+ 'agent/Agent.h',
+ 'agent/Agent.cc',
+ 'agent/AgentCreateDesktop.h',
+ 'agent/AgentCreateDesktop.cc',
+ 'agent/ConsoleFont.cc',
+ 'agent/ConsoleFont.h',
+ 'agent/ConsoleInput.cc',
+ 'agent/ConsoleInput.h',
+ 'agent/ConsoleInputReencoding.cc',
+ 'agent/ConsoleInputReencoding.h',
+ 'agent/ConsoleLine.cc',
+ 'agent/ConsoleLine.h',
+ 'agent/Coord.h',
+ 'agent/DebugShowInput.h',
+ 'agent/DebugShowInput.cc',
+ 'agent/DefaultInputMap.h',
+ 'agent/DefaultInputMap.cc',
+ 'agent/DsrSender.h',
+ 'agent/EventLoop.h',
+ 'agent/EventLoop.cc',
+ 'agent/InputMap.h',
+ 'agent/InputMap.cc',
+ 'agent/LargeConsoleRead.h',
+ 'agent/LargeConsoleRead.cc',
+ 'agent/NamedPipe.h',
+ 'agent/NamedPipe.cc',
+ 'agent/Scraper.h',
+ 'agent/Scraper.cc',
+ 'agent/SimplePool.h',
+ 'agent/SmallRect.h',
+ 'agent/Terminal.h',
+ 'agent/Terminal.cc',
+ 'agent/UnicodeEncoding.h',
+ 'agent/Win32Console.cc',
+ 'agent/Win32Console.h',
+ 'agent/Win32ConsoleBuffer.cc',
+ 'agent/Win32ConsoleBuffer.h',
+ 'agent/main.cc',
+ 'shared/AgentMsg.h',
+ 'shared/BackgroundDesktop.h',
+ 'shared/BackgroundDesktop.cc',
+ 'shared/Buffer.h',
+ 'shared/Buffer.cc',
+ 'shared/DebugClient.h',
+ 'shared/DebugClient.cc',
+ 'shared/GenRandom.h',
+ 'shared/GenRandom.cc',
+ 'shared/OsModule.h',
+ 'shared/OwnedHandle.h',
+ 'shared/OwnedHandle.cc',
+ 'shared/StringBuilder.h',
+ 'shared/StringUtil.cc',
+ 'shared/StringUtil.h',
+ 'shared/UnixCtrlChars.h',
+ 'shared/WindowsSecurity.cc',
+ 'shared/WindowsSecurity.h',
+ 'shared/WindowsVersion.h',
+ 'shared/WindowsVersion.cc',
+ 'shared/WinptyAssert.h',
+ 'shared/WinptyAssert.cc',
+ 'shared/WinptyException.h',
+ 'shared/WinptyException.cc',
+ 'shared/WinptyVersion.h',
+ 'shared/WinptyVersion.cc',
+ 'shared/winpty_snprintf.h',
+ ],
+ },
+ {
+ 'target_name' : 'winpty',
+ 'type' : 'shared_library',
+ 'include_dirs' : [
+ 'include',
+ ],
+ 'defines' : [
+ 'COMPILING_WINPTY_DLL',
+ ],
+ 'libraries' : [
+ '-ladvapi32',
+ '-luser32',
+ ],
+ 'msvs_settings': {
+ # Specify this setting here to override a setting from somewhere
+ # else, such as node's common.gypi.
+ 'VCCLCompilerTool': {
+ 'ExceptionHandling': '1', # /EHsc
+ },
+ },
+ 'sources' : [
+ 'include/winpty.h',
+ 'libwinpty/AgentLocation.cc',
+ 'libwinpty/AgentLocation.h',
+ 'libwinpty/winpty.cc',
+ 'shared/AgentMsg.h',
+ 'shared/BackgroundDesktop.h',
+ 'shared/BackgroundDesktop.cc',
+ 'shared/Buffer.h',
+ 'shared/Buffer.cc',
+ 'shared/DebugClient.h',
+ 'shared/DebugClient.cc',
+ 'shared/GenRandom.h',
+ 'shared/GenRandom.cc',
+ 'shared/OsModule.h',
+ 'shared/OwnedHandle.h',
+ 'shared/OwnedHandle.cc',
+ 'shared/StringBuilder.h',
+ 'shared/StringUtil.cc',
+ 'shared/StringUtil.h',
+ 'shared/WindowsSecurity.cc',
+ 'shared/WindowsSecurity.h',
+ 'shared/WindowsVersion.h',
+ 'shared/WindowsVersion.cc',
+ 'shared/WinptyAssert.h',
+ 'shared/WinptyAssert.cc',
+ 'shared/WinptyException.h',
+ 'shared/WinptyException.cc',
+ 'shared/WinptyVersion.h',
+ 'shared/WinptyVersion.cc',
+ 'shared/winpty_snprintf.h',
+ ],
+ },
+ {
+ 'target_name' : 'winpty-debugserver',
+ 'type' : 'executable',
+ 'msvs_settings': {
+ # Specify this setting here to override a setting from somewhere
+ # else, such as node's common.gypi.
+ 'VCCLCompilerTool': {
+ 'ExceptionHandling': '1', # /EHsc
+ },
+ },
+ 'sources' : [
+ 'debugserver/DebugServer.cc',
+ 'shared/DebugClient.h',
+ 'shared/DebugClient.cc',
+ 'shared/OwnedHandle.h',
+ 'shared/OwnedHandle.cc',
+ 'shared/OsModule.h',
+ 'shared/StringBuilder.h',
+ 'shared/StringUtil.cc',
+ 'shared/StringUtil.h',
+ 'shared/WindowsSecurity.h',
+ 'shared/WindowsSecurity.cc',
+ 'shared/WindowsVersion.h',
+ 'shared/WindowsVersion.cc',
+ 'shared/WinptyAssert.h',
+ 'shared/WinptyAssert.cc',
+ 'shared/WinptyException.h',
+ 'shared/WinptyException.cc',
+ 'shared/winpty_snprintf.h',
+ ],
+ 'libraries' : [
+ '-ladvapi32',
+ ],
+ }
+ ],
+}
diff --git a/src/libs/3rdparty/winpty/vcbuild.bat b/src/libs/3rdparty/winpty/vcbuild.bat
new file mode 100644
index 00000000000..f3787a20f13
--- /dev/null
+++ b/src/libs/3rdparty/winpty/vcbuild.bat
@@ -0,0 +1,83 @@
+@echo off
+
+REM -- Script requirements:
+REM --
+REM -- * git This program must be in the Path to check out
+REM -- build-gyp. If that directory already exists, then
+REM -- git isn't necessary, but if it is missing, no
+REM -- commit hash will be embedded into binaries.
+REM --
+REM -- * python A non-Cygwin Python 2 python.exe must be in the
+REM -- Path to run gyp.
+REM --
+REM -- * msbuild msbuild must be in the Path. It is probably
+REM -- important to have msbuild from the correct MSVC
+REM -- release.
+REM --
+REM -- The script's output binaries are in the src/Release/{Win32,x64}
+REM -- directory.
+
+REM -------------------------------------------------------------------------
+REM -- Parse arguments
+
+setlocal
+cd %~dp0
+set GYP_ARGS=
+set MSVC_PLATFORM=x64
+
+:ParamLoop
+if "%1" == "" goto :ParamDone
+if "%1" == "--msvc-platform" (
+ REM -- One of Win32 or x64.
+ set MSVC_PLATFORM=%2
+ shift && shift
+ goto :ParamLoop
+)
+if "%1" == "--gyp-msvs-version" (
+ set GYP_ARGS=%GYP_ARGS% -G msvs_version=%2
+ shift && shift
+ goto :ParamLoop
+)
+if "%1" == "--toolset" (
+ set GYP_ARGS=%GYP_ARGS% -D WINPTY_MSBUILD_TOOLSET=%2
+ shift && shift
+ goto :ParamLoop
+)
+if "%1" == "--commit-hash" (
+ set GYP_ARGS=%GYP_ARGS% -D WINPTY_COMMIT_HASH=%2
+ shift && shift
+ goto :ParamLoop
+)
+echo error: Unrecognized argument: %1
+exit /b 1
+:ParamDone
+
+REM -------------------------------------------------------------------------
+REM -- Check out GYP. GYP doesn't seem to have releases, so just use the
+REM -- current master commit.
+
+if not exist build-gyp (
+ git clone https://chromium.googlesource.com/external/gyp build-gyp || (
+ echo error: GYP clone failed
+ exit /b 1
+ )
+)
+
+REM -------------------------------------------------------------------------
+REM -- Run gyp to generate MSVC project files.
+
+cd src
+
+call ..\build-gyp\gyp.bat winpty.gyp -I configurations.gypi %GYP_ARGS%
+if errorlevel 1 (
+ echo error: GYP failed
+ exit /b 1
+)
+
+REM -------------------------------------------------------------------------
+REM -- Compile the project.
+
+msbuild winpty.sln /m /p:Platform=%MSVC_PLATFORM% || (
+ echo error: msbuild failed
+ exit /b 1
+)
diff --git a/src/libs/3rdparty/winpty/winpty.qbs b/src/libs/3rdparty/winpty/winpty.qbs
new file mode 100644
index 00000000000..63d76113646
--- /dev/null
+++ b/src/libs/3rdparty/winpty/winpty.qbs
@@ -0,0 +1,207 @@
+import qbs
+import qbs.TextFile
+
+Project {
+ name: "Winpty"
+ condition: qbs.targetOS.contains("windows")
+
+
+ Product {
+ name: "winpty_genversion_header"
+ type: "hpp"
+
+ Group {
+ files: "VERSION.txt"
+ fileTags: "txt.in"
+ }
+
+ Rule {
+ inputs: "txt.in"
+ Artifact {
+ filePath: "GenVersion.h"
+ fileTags: "hpp"
+ }
+ prepare: {
+ var cmd = new JavaScriptCommand();
+ cmd.description = "generating GenVersion.h";
+ cmd.highlight = "codegen";
+ cmd.sourceCode = function() {
+ var inFile = new TextFile(input.filePath);
+ var versionTxt = inFile.readAll();
+ inFile.close();
+ // remove any line endings
+ versionTxt = versionTxt.replace(/[\r\n]/g, "");
+
+ var content = 'const char GenVersion_Version[] = "@VERSION@";\n'
+ + 'const char GenVersion_Commit[] = "@COMMIT_HASH@";\n';
+ content = content.replace(/@VERSION@/g, versionTxt);
+
+ var outFile = new TextFile(output.filePath, TextFile.WriteOnly);
+ outFile.truncate();
+ outFile.write(content);
+ outFile.close();
+ }
+ return cmd;
+ }
+ }
+
+ Export {
+ Depends { name: "cpp" }
+ cpp.includePaths: exportingProduct.buildDirectory
+ }
+ }
+
+ QtcTool {
+ name: "winpty-agent"
+ Depends { name: "winpty_genversion_header" }
+ Depends { name: "cpp" }
+
+ useNonGuiPchFile: false
+ useGuiPchFile: false
+
+ cpp.includePaths: base.concat([sourceDirectory + "/include", buildDirectory])
+ cpp.defines: base.concat(["WINPTY_AGENT_ASSERT",
+ "NOMINMAX", "UNICODE", "_UNICODE"
+ ])
+ cpp.dynamicLibraries: ["user32", "shell32", "advapi32"]
+
+ files: [
+ "src/agent/Agent.h",
+ "src/agent/Agent.cc",
+ "src/agent/AgentCreateDesktop.h",
+ "src/agent/AgentCreateDesktop.cc",
+ "src/agent/ConsoleFont.cc",
+ "src/agent/ConsoleFont.h",
+ "src/agent/ConsoleInput.cc",
+ "src/agent/ConsoleInput.h",
+ "src/agent/ConsoleInputReencoding.cc",
+ "src/agent/ConsoleInputReencoding.h",
+ "src/agent/ConsoleLine.cc",
+ "src/agent/ConsoleLine.h",
+ "src/agent/Coord.h",
+ "src/agent/DebugShowInput.h",
+ "src/agent/DebugShowInput.cc",
+ "src/agent/DefaultInputMap.h",
+ "src/agent/DefaultInputMap.cc",
+ "src/agent/DsrSender.h",
+ "src/agent/EventLoop.h",
+ "src/agent/EventLoop.cc",
+ "src/agent/InputMap.h",
+ "src/agent/InputMap.cc",
+ "src/agent/LargeConsoleRead.h",
+ "src/agent/LargeConsoleRead.cc",
+ "src/agent/NamedPipe.h",
+ "src/agent/NamedPipe.cc",
+ "src/agent/Scraper.h",
+ "src/agent/Scraper.cc",
+ "src/agent/SimplePool.h",
+ "src/agent/SmallRect.h",
+ "src/agent/Terminal.h",
+ "src/agent/Terminal.cc",
+ "src/agent/UnicodeEncoding.h",
+ "src/agent/Win32Console.cc",
+ "src/agent/Win32Console.h",
+ "src/agent/Win32ConsoleBuffer.cc",
+ "src/agent/Win32ConsoleBuffer.h",
+ "src/agent/main.cc",
+ ]
+
+ Group {
+ name: "Shared sources"
+ prefix: "src/shared/"
+ files: [
+ "AgentMsg.h",
+ "BackgroundDesktop.h",
+ "BackgroundDesktop.cc",
+ "Buffer.h",
+ "Buffer.cc",
+ "DebugClient.h",
+ "DebugClient.cc",
+ "GenRandom.h",
+ "GenRandom.cc",
+ "OsModule.h",
+ "OwnedHandle.h",
+ "OwnedHandle.cc",
+ "StringBuilder.h",
+ "StringUtil.cc",
+ "StringUtil.h",
+ "UnixCtrlChars.h",
+ "WindowsSecurity.cc",
+ "WindowsSecurity.h",
+ "WindowsVersion.h",
+ "WindowsVersion.cc",
+ "WinptyAssert.h",
+ "WinptyAssert.cc",
+ "WinptyException.h",
+ "WinptyException.cc",
+ "WinptyVersion.h",
+ "WinptyVersion.cc",
+ "winpty_snprintf.h",
+ ]
+ }
+ }
+
+ QtcLibrary {
+ name: "winpty"
+ type: "staticlibrary"
+
+ Depends { name: "winpty_genversion_header" }
+ Depends { name: "cpp" }
+
+ useNonGuiPchFile: false
+ useGuiPchFile: false
+
+ cpp.defines: base.concat(["COMPILING_WINPTY_DLL",
+ "NOMINMAX", "UNICODE", "_UNICODE"
+ ])
+ cpp.dynamicLibraries: ["user32", "shell32", "advapi32"]
+ cpp.includePaths: base.concat(sourceDirectory + "/src/include")
+
+
+ files: [
+ "src/libwinpty/AgentLocation.cc",
+ "src/libwinpty/AgentLocation.h",
+ "src/libwinpty/winpty.cc",
+ ]
+
+ Group {
+ name: "Shared sources" // FIXME duplication
+ prefix: "src/shared/"
+ files: [
+ "AgentMsg.h",
+ "BackgroundDesktop.h",
+ "BackgroundDesktop.cc",
+ "Buffer.h",
+ "Buffer.cc",
+ "DebugClient.h",
+ "DebugClient.cc",
+ "GenRandom.h",
+ "GenRandom.cc",
+ "OsModule.h",
+ "OwnedHandle.h",
+ "OwnedHandle.cc",
+ "StringBuilder.h",
+ "StringUtil.cc",
+ "StringUtil.h",
+ "UnixCtrlChars.h",
+ "WindowsSecurity.cc",
+ "WindowsSecurity.h",
+ "WindowsVersion.h",
+ "WindowsVersion.cc",
+ "WinptyAssert.h",
+ "WinptyAssert.cc",
+ "WinptyException.h",
+ "WinptyException.cc",
+ "WinptyVersion.h",
+ "WinptyVersion.cc",
+ "winpty_snprintf.h",
+ ]
+ }
+
+ Export {
+ Depends { name: "cpp" }
+ cpp.defines: base.concat("COMPILING_WINPTY_DLL")
+ cpp.includePaths: base.concat(exportingProduct.sourceDirectory + "/src/include")
+ }
+ }
+}
diff --git a/src/libs/advanceddockingsystem/ads_globals_p.h b/src/libs/advanceddockingsystem/ads_globals_p.h
new file mode 100644
index 00000000000..e4b32eb2015
--- /dev/null
+++ b/src/libs/advanceddockingsystem/ads_globals_p.h
@@ -0,0 +1,8 @@
+// Copyright (C) 2023
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later
+
+#pragma once
+
+#include <QLoggingCategory>
+
+Q_DECLARE_LOGGING_CATEGORY(adsLog)
diff --git a/src/libs/advanceddockingsystem/dockareatabbar.cpp b/src/libs/advanceddockingsystem/dockareatabbar.cpp
index ec1bf782ff0..62f0b80b499 100644
--- a/src/libs/advanceddockingsystem/dockareatabbar.cpp
+++ b/src/libs/advanceddockingsystem/dockareatabbar.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later
#include "dockareatabbar.h"
+#include "ads_globals_p.h"
#include "dockareawidget.h"
#include "dockwidget.h"
@@ -16,8 +17,6 @@
#include <iostream>
-static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg)
-
namespace ADS
{
/**
diff --git a/src/libs/advanceddockingsystem/dockareatitlebar.cpp b/src/libs/advanceddockingsystem/dockareatitlebar.cpp
index ed1d76b67ad..178170777d5 100644
--- a/src/libs/advanceddockingsystem/dockareatitlebar.cpp
+++ b/src/libs/advanceddockingsystem/dockareatitlebar.cpp
@@ -4,6 +4,7 @@
#include "dockareatitlebar.h"
#include "ads_globals.h"
+#include "ads_globals_p.h"
#include "advanceddockingsystemtr.h"
#include "dockareatabbar.h"
#include "dockareawidget.h"
@@ -26,8 +27,6 @@
#include <iostream>
-static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg)
-
namespace ADS
{
/**
diff --git a/src/libs/advanceddockingsystem/dockareawidget.cpp b/src/libs/advanceddockingsystem/dockareawidget.cpp
index b4c21f71198..522b6b985ee 100644
--- a/src/libs/advanceddockingsystem/dockareawidget.cpp
+++ b/src/libs/advanceddockingsystem/dockareawidget.cpp
@@ -3,6 +3,7 @@
#include "dockareawidget.h"
+#include "ads_globals_p.h"
#include "dockareatabbar.h"
#include "dockareatitlebar.h"
#include "dockcomponentsfactory.h"
@@ -29,8 +30,6 @@
#include <iostream>
-static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg)
-
namespace ADS
{
static const char *const INDEX_PROPERTY = "index";
diff --git a/src/libs/advanceddockingsystem/dockcontainerwidget.cpp b/src/libs/advanceddockingsystem/dockcontainerwidget.cpp
index 80393a46713..253937c4d17 100644
--- a/src/libs/advanceddockingsystem/dockcontainerwidget.cpp
+++ b/src/libs/advanceddockingsystem/dockcontainerwidget.cpp
@@ -4,6 +4,7 @@
#include "dockcontainerwidget.h"
#include "ads_globals.h"
+#include "ads_globals_p.h"
#include "dockareawidget.h"
#include "dockingstatereader.h"
#include "dockmanager.h"
@@ -25,8 +26,6 @@
#include <functional>
#include <iostream>
-static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg)
-
namespace ADS
{
static unsigned int zOrderCounter = 0;
diff --git a/src/libs/advanceddockingsystem/dockmanager.cpp b/src/libs/advanceddockingsystem/dockmanager.cpp
index 59fc2f64fc3..2694f2986ea 100644
--- a/src/libs/advanceddockingsystem/dockmanager.cpp
+++ b/src/libs/advanceddockingsystem/dockmanager.cpp
@@ -4,6 +4,7 @@
#include "dockmanager.h"
#include "ads_globals.h"
+#include "ads_globals_p.h"
#include "dockareawidget.h"
#include "dockfocuscontroller.h"
#include "dockingstatereader.h"
@@ -38,7 +39,7 @@
#include <QVariant>
#include <QXmlStreamWriter>
-static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg);
+Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg);
using namespace Utils;
diff --git a/src/libs/advanceddockingsystem/docksplitter.cpp b/src/libs/advanceddockingsystem/docksplitter.cpp
index e8537dbd775..22efb632040 100644
--- a/src/libs/advanceddockingsystem/docksplitter.cpp
+++ b/src/libs/advanceddockingsystem/docksplitter.cpp
@@ -3,14 +3,13 @@
#include "docksplitter.h"
+#include "ads_globals_p.h"
#include "dockareawidget.h"
#include <QChildEvent>
#include <QLoggingCategory>
#include <QVariant>
-static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg)
-
namespace ADS
{
/**
diff --git a/src/libs/advanceddockingsystem/dockwidget.cpp b/src/libs/advanceddockingsystem/dockwidget.cpp
index b39c58f4c2d..64269ebfdb3 100644
--- a/src/libs/advanceddockingsystem/dockwidget.cpp
+++ b/src/libs/advanceddockingsystem/dockwidget.cpp
@@ -4,6 +4,7 @@
#include "dockwidget.h"
#include "ads_globals.h"
+#include "ads_globals_p.h"
#include "dockareawidget.h"
#include "dockcomponentsfactory.h"
#include "dockcontainerwidget.h"
@@ -25,8 +26,6 @@
#include <QXmlStreamWriter>
#include <QWindow>
-static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg)
-
namespace ADS
{
/**
diff --git a/src/libs/advanceddockingsystem/dockwidgettab.cpp b/src/libs/advanceddockingsystem/dockwidgettab.cpp
index 67754784975..4531e0da2da 100644
--- a/src/libs/advanceddockingsystem/dockwidgettab.cpp
+++ b/src/libs/advanceddockingsystem/dockwidgettab.cpp
@@ -4,6 +4,7 @@
#include "dockwidgettab.h"
#include "ads_globals.h"
+#include "ads_globals_p.h"
#include "advanceddockingsystemtr.h"
#include "dockareawidget.h"
#include "dockmanager.h"
@@ -32,8 +33,6 @@
#include <iostream>
-static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg)
-
namespace ADS
{
using TabLabelType = ElidingLabel;
diff --git a/src/libs/advanceddockingsystem/floatingdockcontainer.cpp b/src/libs/advanceddockingsystem/floatingdockcontainer.cpp
index 4b8ae0913dd..c99044a3d5a 100644
--- a/src/libs/advanceddockingsystem/floatingdockcontainer.cpp
+++ b/src/libs/advanceddockingsystem/floatingdockcontainer.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later
#include "floatingdockcontainer.h"
+#include "ads_globals_p.h"
#include "dockareawidget.h"
#include "dockcontainerwidget.h"
@@ -29,8 +30,6 @@
#include <QMouseEvent>
#include <QPointer>
-static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg)
-
namespace ADS
{
#ifdef Q_OS_WIN
diff --git a/src/libs/advanceddockingsystem/floatingdragpreview.cpp b/src/libs/advanceddockingsystem/floatingdragpreview.cpp
index f427ab85edf..3111e46c6bd 100644
--- a/src/libs/advanceddockingsystem/floatingdragpreview.cpp
+++ b/src/libs/advanceddockingsystem/floatingdragpreview.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-2.1-or-later OR GPL-3.0-or-later
#include "floatingdragpreview.h"
+#include "ads_globals_p.h"
#include "dockareawidget.h"
#include "dockcontainerwidget.h"
@@ -19,8 +20,6 @@
#include <iostream>
-static Q_LOGGING_CATEGORY(adsLog, "qtc.qmldesigner.advanceddockingsystem", QtWarningMsg)
-
namespace ADS
{
/**
diff --git a/src/libs/cplusplus/CppDocument.cpp b/src/libs/cplusplus/CppDocument.cpp
index 6e241fb29b0..42616498977 100644
--- a/src/libs/cplusplus/CppDocument.cpp
+++ b/src/libs/cplusplus/CppDocument.cpp
@@ -821,16 +821,10 @@ FilePaths Snapshot::filesDependingOn(const FilePath &filePath) const
return m_deps.filesDependingOn(filePath);
}
-void Snapshot::updateDependencyTable() const
-{
- QFutureInterfaceBase futureInterface;
- updateDependencyTable(futureInterface);
-}
-
-void Snapshot::updateDependencyTable(QFutureInterfaceBase &futureInterface) const
+void Snapshot::updateDependencyTable(const std::optional<QFuture<void>> &future) const
{
if (m_deps.files.isEmpty())
- m_deps.build(futureInterface, *this);
+ m_deps.build(future, *this);
}
bool Snapshot::operator==(const Snapshot &other) const
diff --git a/src/libs/cplusplus/CppDocument.h b/src/libs/cplusplus/CppDocument.h
index cfbad3be1ea..679663f2d99 100644
--- a/src/libs/cplusplus/CppDocument.h
+++ b/src/libs/cplusplus/CppDocument.h
@@ -15,12 +15,9 @@
#include <QDateTime>
#include <QHash>
#include <QFileInfo>
+#include <QFuture>
#include <QAtomicInt>
-QT_BEGIN_NAMESPACE
-class QFutureInterfaceBase;
-QT_END_NAMESPACE
-
namespace CPlusPlus {
class Macro;
@@ -406,8 +403,7 @@ public:
Utils::FilePaths filesDependingOn(const Utils::FilePath &filePath) const;
- void updateDependencyTable() const;
- void updateDependencyTable(QFutureInterfaceBase &futureInterface) const;
+ void updateDependencyTable(const std::optional<QFuture<void>> &future = {}) const;
bool operator==(const Snapshot &other) const;
diff --git a/src/libs/cplusplus/DependencyTable.cpp b/src/libs/cplusplus/DependencyTable.cpp
index 00aca7dc65a..5960957330b 100644
--- a/src/libs/cplusplus/DependencyTable.cpp
+++ b/src/libs/cplusplus/DependencyTable.cpp
@@ -4,7 +4,7 @@
#include "CppDocument.h"
#include <QDebug>
-#include <QFutureInterface>
+#include <QFuture>
using namespace Utils;
@@ -28,14 +28,14 @@ Utils::FilePaths DependencyTable::filesDependingOn(const Utils::FilePath &fileNa
return deps;
}
-void DependencyTable::build(QFutureInterfaceBase &futureInterface, const Snapshot &snapshot)
+void DependencyTable::build(const std::optional<QFuture<void>> &future, const Snapshot &snapshot)
{
files.clear();
fileIndex.clear();
includes.clear();
includeMap.clear();
- if (futureInterface.isCanceled())
+ if (future && future->isCanceled())
return;
const int documentCount = snapshot.size();
@@ -49,7 +49,7 @@ void DependencyTable::build(QFutureInterfaceBase &futureInterface, const Snapsho
fileIndex[it.key()] = i;
}
- if (futureInterface.isCanceled())
+ if (future && future->isCanceled())
return;
for (int i = 0; i < files.size(); ++i) {
@@ -68,13 +68,13 @@ void DependencyTable::build(QFutureInterfaceBase &futureInterface, const Snapsho
directIncludes.append(index);
bitmap.setBit(index, true);
- if (futureInterface.isCanceled())
+ if (future && future->isCanceled())
return;
}
includeMap[i] = bitmap;
includes[i] = directIncludes;
- if (futureInterface.isCanceled())
+ if (future && future->isCanceled())
return;
}
}
@@ -91,7 +91,7 @@ void DependencyTable::build(QFutureInterfaceBase &futureInterface, const Snapsho
const QList<int> includedFileIndexes = includes.value(i);
for (const int includedFileIndex : includedFileIndexes) {
bitmap |= includeMap.value(includedFileIndex);
- if (futureInterface.isCanceled())
+ if (future && future->isCanceled())
return;
}
@@ -99,10 +99,10 @@ void DependencyTable::build(QFutureInterfaceBase &futureInterface, const Snapsho
includeMap[i] = bitmap;
changed = true;
}
- if (futureInterface.isCanceled())
+ if (future && future->isCanceled())
return;
}
- if (futureInterface.isCanceled())
+ if (future && future->isCanceled())
return;
} while (changed);
}
diff --git a/src/libs/cplusplus/DependencyTable.h b/src/libs/cplusplus/DependencyTable.h
index d9057844330..d460ebeb7f3 100644
--- a/src/libs/cplusplus/DependencyTable.h
+++ b/src/libs/cplusplus/DependencyTable.h
@@ -14,7 +14,8 @@
#include <QVector>
QT_BEGIN_NAMESPACE
-class QFutureInterfaceBase;
+template <typename T>
+class QFuture;
QT_END_NAMESPACE
namespace CPlusPlus {
@@ -25,7 +26,7 @@ class CPLUSPLUS_EXPORT DependencyTable
{
private:
friend class Snapshot;
- void build(QFutureInterfaceBase &futureInterface, const Snapshot &snapshot);
+ void build(const std::optional<QFuture<void>> &future, const Snapshot &snapshot);
Utils::FilePaths filesDependingOn(const Utils::FilePath &fileName) const;
QVector<Utils::FilePath> files;
diff --git a/src/libs/cplusplus/cplusplus.qbs b/src/libs/cplusplus/cplusplus.qbs
index 80c6174ba22..0aed0ab438f 100644
--- a/src/libs/cplusplus/cplusplus.qbs
+++ b/src/libs/cplusplus/cplusplus.qbs
@@ -41,6 +41,7 @@ Project {
"FullySpecifiedType.cpp",
"FullySpecifiedType.h",
"Keywords.cpp",
+ "Keywords.kwgen",
"Lexer.cpp",
"Lexer.h",
"LiteralTable.h",
diff --git a/src/libs/cplusplus/pp-engine.cpp b/src/libs/cplusplus/pp-engine.cpp
index d8221b151f6..ef27d9eace8 100644
--- a/src/libs/cplusplus/pp-engine.cpp
+++ b/src/libs/cplusplus/pp-engine.cpp
@@ -37,20 +37,17 @@
#include <QDebug>
#include <QList>
#include <QDate>
+#include <QLoggingCategory>
#include <QTime>
#include <QPair>
#include <cctype>
+#include <deque>
#include <list>
#include <algorithm>
-#define NO_DEBUG
-
-#ifndef NO_DEBUG
-# include <iostream>
-#endif // NO_DEBUG
-
-#include <deque>
+// FIXME: This is used for errors that should appear in the editor.
+static Q_LOGGING_CATEGORY(lexerLog, "qtc.cpp.lexer", QtWarningMsg)
using namespace Utils;
@@ -119,13 +116,6 @@ static bool isQtReservedWord(const char *name, int size)
return false;
}
-static void nestingTooDeep()
-{
-#ifndef NO_DEBUG
- std::cerr << "*** WARNING #if / #ifdef nesting exceeded the max level " << MAX_LEVEL << std::endl;
-#endif
-}
-
} // anonymous namespace
namespace CPlusPlus {
@@ -1680,10 +1670,7 @@ void Preprocessor::handleIncludeDirective(PPToken *tk, bool includeNext)
GuardLocker depthLocker(m_includeDepthGuard);
if (m_includeDepthGuard.lockCount() > MAX_INCLUDE_DEPTH) {
- // FIXME: Categorized logging!
-#ifndef NO_DEBUG
- std::cerr << "Maximum include depth exceeded" << m_state.m_currentFileName << std::endl;
-#endif
+ qCWarning(lexerLog) << "Maximum include depth exceeded" << m_state.m_currentFileName;
return;
}
@@ -1929,10 +1916,8 @@ void Preprocessor::handleIfDirective(PPToken *tk)
Value result;
const PPToken lastExpressionToken = evalExpression(tk, result);
- if (m_state.m_ifLevel >= MAX_LEVEL - 1) {
- nestingTooDeep();
+ if (!checkConditionalNesting())
return;
- }
const bool value = !result.is_zero();
@@ -1953,7 +1938,7 @@ void Preprocessor::handleIfDirective(PPToken *tk)
void Preprocessor::handleElifDirective(PPToken *tk, const PPToken &poundToken)
{
if (m_state.m_ifLevel == 0) {
-// std::cerr << "*** WARNING #elif without #if" << std::endl;
+ qCWarning(lexerLog) << "#elif without #if";
handleIfDirective(tk);
} else {
lex(tk); // consume "elif" token
@@ -2000,22 +1985,18 @@ void Preprocessor::handleElseDirective(PPToken *tk, const PPToken &poundToken)
else if (m_client && !wasSkipping && startSkipping)
startSkippingBlocks(poundToken);
}
-#ifndef NO_DEBUG
} else {
- std::cerr << "*** WARNING #else without #if" << std::endl;
-#endif // NO_DEBUG
+ qCWarning(lexerLog) << "#else without #if";
}
}
void Preprocessor::handleEndIfDirective(PPToken *tk, const PPToken &poundToken)
{
if (m_state.m_ifLevel == 0) {
-#ifndef NO_DEBUG
- std::cerr << "*** WARNING #endif without #if";
+ qCWarning(lexerLog) << "#endif without #if";
if (!tk->generated())
- std::cerr << " on line " << tk->lineno << " of file " << m_state.m_currentFileName.toUtf8().constData();
- std::cerr << std::endl;
-#endif // NO_DEBUG
+ qCWarning(lexerLog) << "on line" << tk->lineno << "of file"
+ << m_state.m_currentFileName.toUtf8().constData();
} else {
bool wasSkipping = m_state.m_skipping[m_state.m_ifLevel];
m_state.m_skipping[m_state.m_ifLevel] = false;
@@ -2061,22 +2042,18 @@ void Preprocessor::handleIfDefDirective(bool checkUndefined, PPToken *tk)
const bool wasSkipping = m_state.m_skipping[m_state.m_ifLevel];
- if (m_state.m_ifLevel < MAX_LEVEL - 1) {
+ if (checkConditionalNesting()) {
++m_state.m_ifLevel;
m_state.m_trueTest[m_state.m_ifLevel] = value;
m_state.m_skipping[m_state.m_ifLevel] = wasSkipping ? wasSkipping : !value;
if (m_client && !wasSkipping && !value)
startSkippingBlocks(*tk);
- } else {
- nestingTooDeep();
}
lex(tk); // consume the identifier
-#ifndef NO_DEBUG
} else {
- std::cerr << "*** WARNING #ifdef without identifier" << std::endl;
-#endif // NO_DEBUG
+ qCWarning(lexerLog) << "#ifdef without identifier";
}
}
@@ -2103,10 +2080,8 @@ void Preprocessor::handleUndefDirective(PPToken *tk)
m_client->macroAdded(*macro);
}
lex(tk); // consume macro name
-#ifndef NO_DEBUG
} else {
- std::cerr << "*** WARNING #undef without identifier" << std::endl;
-#endif // NO_DEBUG
+ qCWarning(lexerLog) << "#undef without identifier";
}
}
@@ -2203,4 +2178,14 @@ void Preprocessor::maybeStartOutputLine()
buffer.append('\n');
}
+bool Preprocessor::checkConditionalNesting() const
+{
+ if (m_state.m_ifLevel >= MAX_LEVEL - 1) {
+ qCWarning(lexerLog) << "#if/#ifdef nesting exceeding maximum level" << MAX_LEVEL;
+ return false;
+ }
+ return true;
+}
+
+
} // namespace CPlusPlus
diff --git a/src/libs/cplusplus/pp-engine.h b/src/libs/cplusplus/pp-engine.h
index c888e8775d6..2163380dea2 100644
--- a/src/libs/cplusplus/pp-engine.h
+++ b/src/libs/cplusplus/pp-engine.h
@@ -237,6 +237,7 @@ private:
PPToken generateConcatenated(const PPToken &leftTk, const PPToken &rightTk);
void startSkippingBlocks(const PPToken &tk) const;
+ bool checkConditionalNesting() const;
private:
Client *m_client;
diff --git a/src/libs/extensionsystem/CMakeLists.txt b/src/libs/extensionsystem/CMakeLists.txt
index ea7cb60beb0..0e4e6607a55 100644
--- a/src/libs/extensionsystem/CMakeLists.txt
+++ b/src/libs/extensionsystem/CMakeLists.txt
@@ -19,7 +19,7 @@ add_qtc_library(ExtensionSystem
SKIP_AUTOMOC pluginmanager.cpp
)
-find_package(Qt5 COMPONENTS Test QUIET)
+find_package(Qt6 COMPONENTS Test QUIET)
extend_qtc_library(ExtensionSystem
CONDITION TARGET Qt::Test
diff --git a/src/libs/languageserverprotocol/jsonrpcmessages.h b/src/libs/languageserverprotocol/jsonrpcmessages.h
index 0b018d7f54a..680bff95782 100644
--- a/src/libs/languageserverprotocol/jsonrpcmessages.h
+++ b/src/libs/languageserverprotocol/jsonrpcmessages.h
@@ -141,6 +141,7 @@ public:
void setMethod(const QString &method)
{ m_jsonObject.insert(methodKey, method); }
+ using Parameters = Params;
std::optional<Params> params() const
{
const QJsonValue &params = m_jsonObject.value(paramsKey);
diff --git a/src/libs/languageserverprotocol/lsptypes.cpp b/src/libs/languageserverprotocol/lsptypes.cpp
index 4bf3f3746e2..bd7798dcef9 100644
--- a/src/libs/languageserverprotocol/lsptypes.cpp
+++ b/src/libs/languageserverprotocol/lsptypes.cpp
@@ -311,6 +311,16 @@ bool Range::overlaps(const Range &range) const
return !isLeftOf(range) && !range.isLeftOf(*this);
}
+QTextCursor Range::toSelection(QTextDocument *doc) const
+{
+ QTC_ASSERT(doc, return {});
+ if (!isValid())
+ return {};
+ QTextCursor cursor = start().toTextCursor(doc);
+ cursor.setPosition(end().toPositionInDocument(doc), QTextCursor::KeepAnchor);
+ return cursor;
+}
+
QString expressionForGlob(QString globPattern)
{
const QString anySubDir("qtc_anysubdir_id");
diff --git a/src/libs/languageserverprotocol/lsptypes.h b/src/libs/languageserverprotocol/lsptypes.h
index 239d40f6622..3a9adb1b0d8 100644
--- a/src/libs/languageserverprotocol/lsptypes.h
+++ b/src/libs/languageserverprotocol/lsptypes.h
@@ -117,6 +117,8 @@ public:
bool isLeftOf(const Range &other) const
{ return isEmpty() || other.isEmpty() ? end() < other.start() : end() <= other.start(); }
+ QTextCursor toSelection(QTextDocument *doc) const;
+
bool isValid() const override
{ return JsonObject::contains(startKey) && JsonObject::contains(endKey); }
};
@@ -556,7 +558,6 @@ enum class SymbolKind {
TypeParameter = 26,
LastSymbolKind = TypeParameter,
};
-using SymbolStringifier = std::function<QString(SymbolKind, const QString &, const QString &)>;
namespace CompletionItemKind {
enum Kind {
diff --git a/src/libs/languageserverprotocol/lsputils.h b/src/libs/languageserverprotocol/lsputils.h
index 5515b515767..0c815bafd8a 100644
--- a/src/libs/languageserverprotocol/lsputils.h
+++ b/src/libs/languageserverprotocol/lsputils.h
@@ -87,6 +87,13 @@ public:
return QJsonValue();
}
+ QList<T> toListOrEmpty() const
+ {
+ if (std::holds_alternative<QList<T>>(*this))
+ return std::get<QList<T>>(*this);
+ return {};
+ }
+
QList<T> toList() const
{
QTC_ASSERT(std::holds_alternative<QList<T>>(*this), return {});
diff --git a/src/libs/languageserverprotocol/workspace.h b/src/libs/languageserverprotocol/workspace.h
index efe77a68ec2..42cfcf451d2 100644
--- a/src/libs/languageserverprotocol/workspace.h
+++ b/src/libs/languageserverprotocol/workspace.h
@@ -175,7 +175,7 @@ class LANGUAGESERVERPROTOCOL_EXPORT WorkspaceSymbolRequest : public Request<
LanguageClientArray<SymbolInformation>, std::nullptr_t, WorkspaceSymbolParams>
{
public:
- WorkspaceSymbolRequest(const WorkspaceSymbolParams &params);
+ explicit WorkspaceSymbolRequest(const WorkspaceSymbolParams &params);
using Request::Request;
constexpr static const char methodName[] = "workspace/symbol";
};
diff --git a/src/libs/libs.qbs b/src/libs/libs.qbs
index 92f84fe4d19..141e2a65475 100644
--- a/src/libs/libs.qbs
+++ b/src/libs/libs.qbs
@@ -21,10 +21,12 @@ Project {
"qtcreatorcdbext/qtcreatorcdbext.qbs",
"sqlite/sqlite.qbs",
"tracing/tracing.qbs",
- "utils/process_stub.qbs",
"utils/process_ctrlc_stub.qbs",
"utils/utils.qbs",
+ "3rdparty/libptyqt/ptyqt.qbs",
+ "3rdparty/libvterm/vterm.qbs",
"3rdparty/syntax-highlighting/syntax-highlighting.qbs",
+ "3rdparty/winpty/winpty.qbs",
"3rdparty/yaml-cpp/yaml-cpp.qbs",
].concat(qlitehtml).concat(project.additionalLibs)
}
diff --git a/src/libs/qlitehtml b/src/libs/qlitehtml
-Subproject f05f78ef33225823d348ee18f2fa464e95024dd
+Subproject 8e541a22b513432ed566fca824af207395ee0c9
diff --git a/src/libs/qmljs/qmljsmodelmanagerinterface.cpp b/src/libs/qmljs/qmljsmodelmanagerinterface.cpp
index 3d2fc32120b..f6c84fb88f4 100644
--- a/src/libs/qmljs/qmljsmodelmanagerinterface.cpp
+++ b/src/libs/qmljs/qmljsmodelmanagerinterface.cpp
@@ -15,8 +15,8 @@
#include <cplusplus/cppmodelmanagerbase.h>
#include <utils/algorithm.h>
+#include <utils/asynctask.h>
#include <utils/hostosinfo.h>
-#include <utils/runextensions.h>
#include <utils/stringutils.h>
#ifdef WITH_TESTS
@@ -337,11 +337,9 @@ QFuture<void> ModelManagerInterface::refreshSourceFiles(const QList<Utils::FileP
if (sourceFiles.isEmpty())
return QFuture<void>();
- QFuture<void> result = Utils::runAsync(&m_threadPool,
- &ModelManagerInterface::parse,
- workingCopyInternal(), sourceFiles,
- this, Dialect(Dialect::Qml),
- emitDocumentOnDiskChanged);
+ QFuture<void> result = Utils::asyncRun(&m_threadPool, &ModelManagerInterface::parse,
+ workingCopyInternal(), sourceFiles, this,
+ Dialect(Dialect::Qml), emitDocumentOnDiskChanged);
addFuture(result);
if (sourceFiles.count() > 1)
@@ -365,13 +363,8 @@ QFuture<void> ModelManagerInterface::refreshSourceFiles(const QList<Utils::FileP
void ModelManagerInterface::fileChangedOnDisk(const Utils::FilePath &path)
{
- addFuture(Utils::runAsync(&m_threadPool,
- &ModelManagerInterface::parse,
- workingCopyInternal(),
- FilePaths({path}),
- this,
- Dialect(Dialect::AnyLanguage),
- true));
+ addFuture(Utils::asyncRun(&m_threadPool, &ModelManagerInterface::parse, workingCopyInternal(),
+ FilePaths({path}), this, Dialect(Dialect::AnyLanguage), true));
}
void ModelManagerInterface::removeFiles(const QList<Utils::FilePath> &files)
@@ -1044,24 +1037,24 @@ void ModelManagerInterface::parseLoop(QSet<Utils::FilePath> &scannedPaths,
class FutureReporter
{
public:
- FutureReporter(QFutureInterface<void> &future, int multiplier, int base)
- : future(future), multiplier(multiplier), base(base)
+ FutureReporter(QPromise<void> &promise, int multiplier, int base)
+ : m_promise(promise), m_multiplier(multiplier), m_base(base)
{}
bool operator()(qreal val)
{
- if (future.isCanceled())
+ if (m_promise.isCanceled())
return false;
- future.setProgressValue(int(base + multiplier * val));
+ m_promise.setProgressValue(int(m_base + m_multiplier * val));
return true;
}
private:
- QFutureInterface<void> &future;
- int multiplier;
- int base;
+ QPromise<void> &m_promise;
+ int m_multiplier;
+ int m_base;
};
-void ModelManagerInterface::parse(QFutureInterface<void> &future,
+void ModelManagerInterface::parse(QPromise<void> &promise,
const WorkingCopy &workingCopy,
QList<Utils::FilePath> files,
ModelManagerInterface *modelManager,
@@ -1069,8 +1062,8 @@ void ModelManagerInterface::parse(QFutureInterface<void> &future,
bool emitDocChangedOnDisk)
{
const int progressMax = 100;
- FutureReporter reporter(future, progressMax, 0);
- future.setProgressRange(0, progressMax);
+ FutureReporter reporter(promise, progressMax, 0);
+ promise.setProgressRange(0, progressMax);
// paths we have scanned for files and added to the files list
QSet<Utils::FilePath> scannedPaths;
@@ -1078,7 +1071,7 @@ void ModelManagerInterface::parse(QFutureInterface<void> &future,
QSet<Utils::FilePath> newLibraries;
parseLoop(scannedPaths, newLibraries, workingCopy, std::move(files), modelManager, mainLanguage,
emitDocChangedOnDisk, reporter);
- future.setProgressValue(progressMax);
+ promise.setProgressValue(progressMax);
}
struct ScanItem {
@@ -1087,11 +1080,20 @@ struct ScanItem {
Dialect language = Dialect::AnyLanguage;
};
-void ModelManagerInterface::importScan(QFutureInterface<void> &future,
- const ModelManagerInterface::WorkingCopy &workingCopy,
+void ModelManagerInterface::importScan(const WorkingCopy &workingCopy,
const PathsAndLanguages &paths,
ModelManagerInterface *modelManager,
- bool emitDocChangedOnDisk, bool libOnly, bool forceRescan)
+ bool emitDocChanged, bool libOnly, bool forceRescan)
+{
+ QPromise<void> promise;
+ promise.start();
+ importScanAsync(promise, workingCopy, paths, modelManager, emitDocChanged, libOnly, forceRescan);
+}
+
+void ModelManagerInterface::importScanAsync(QPromise<void> &promise, const WorkingCopy &workingCopy,
+ const PathsAndLanguages &paths,
+ ModelManagerInterface *modelManager,
+ bool emitDocChanged, bool libOnly, bool forceRescan)
{
// paths we have scanned for files and added to the files list
QSet<Utils::FilePath> scannedPaths;
@@ -1118,9 +1120,9 @@ void ModelManagerInterface::importScan(QFutureInterface<void> &future,
int progressRange = pathsToScan.size() * (1 << (2 + maxScanDepth));
int totalWork = progressRange;
int workDone = 0;
- future.setProgressRange(0, progressRange); // update max length while iterating?
+ promise.setProgressRange(0, progressRange); // update max length while iterating?
const Snapshot snapshot = modelManager->snapshot();
- bool isCanceled = future.isCanceled();
+ bool isCanceled = promise.isCanceled();
while (!pathsToScan.isEmpty() && !isCanceled) {
ScanItem toScan = pathsToScan.last();
pathsToScan.pop_back();
@@ -1135,16 +1137,16 @@ void ModelManagerInterface::importScan(QFutureInterface<void> &future,
toScan.language.companionLanguages());
}
workDone += 1;
- future.setProgressValue(progressRange * workDone / totalWork);
+ promise.setProgressValue(progressRange * workDone / totalWork);
if (!importedFiles.isEmpty()) {
- FutureReporter reporter(future, progressRange * pathBudget / (4 * totalWork),
+ FutureReporter reporter(promise, progressRange * pathBudget / (4 * totalWork),
progressRange * workDone / totalWork);
parseLoop(scannedPaths, newLibraries, workingCopy, importedFiles, modelManager,
- toScan.language, emitDocChangedOnDisk, reporter); // run in parallel??
+ toScan.language, emitDocChanged, reporter); // run in parallel??
importedFiles.clear();
}
workDone += pathBudget / 4 - 1;
- future.setProgressValue(progressRange * workDone / totalWork);
+ promise.setProgressValue(progressRange * workDone / totalWork);
} else {
workDone += pathBudget / 4;
}
@@ -1159,10 +1161,10 @@ void ModelManagerInterface::importScan(QFutureInterface<void> &future,
} else {
workDone += pathBudget * 3 / 4;
}
- future.setProgressValue(progressRange * workDone / totalWork);
- isCanceled = future.isCanceled();
+ promise.setProgressValue(progressRange * workDone / totalWork);
+ isCanceled = promise.isCanceled();
}
- future.setProgressValue(progressRange);
+ promise.setProgressValue(progressRange);
if (isCanceled) {
// assume no work has been done
QMutexLocker l(&modelManager->m_mutex);
@@ -1206,8 +1208,8 @@ void ModelManagerInterface::maybeScan(const PathsAndLanguages &importPaths)
}
if (pathToScan.length() >= 1) {
- QFuture<void> result = Utils::runAsync(&m_threadPool,
- &ModelManagerInterface::importScan,
+ QFuture<void> result = Utils::asyncRun(&m_threadPool,
+ &ModelManagerInterface::importScanAsync,
workingCopyInternal(), pathToScan,
this, true, true, false);
addFuture(result);
@@ -1373,8 +1375,8 @@ void ModelManagerInterface::startCppQmlTypeUpdate()
if (!cppModelManager)
return;
- m_cppQmlTypesUpdater = Utils::runAsync(&ModelManagerInterface::updateCppQmlTypes,
- this, cppModelManager->snapshot(), m_queuedCppDocuments);
+ m_cppQmlTypesUpdater = Utils::asyncRun(&ModelManagerInterface::updateCppQmlTypes, this,
+ cppModelManager->snapshot(), m_queuedCppDocuments);
m_queuedCppDocuments.clear();
}
@@ -1415,13 +1417,12 @@ bool rescanExports(const QString &fileName, FindExportedCppTypes &finder,
return hasNewInfo;
}
-void ModelManagerInterface::updateCppQmlTypes(
- QFutureInterface<void> &futureInterface, ModelManagerInterface *qmlModelManager,
- const CPlusPlus::Snapshot &snapshot,
+void ModelManagerInterface::updateCppQmlTypes(QPromise<void> &promise,
+ ModelManagerInterface *qmlModelManager, const CPlusPlus::Snapshot &snapshot,
const QHash<QString, QPair<CPlusPlus::Document::Ptr, bool>> &documents)
{
- futureInterface.setProgressRange(0, documents.size());
- futureInterface.setProgressValue(0);
+ promise.setProgressRange(0, documents.size());
+ promise.setProgressValue(0);
CppDataHash newData;
QHash<QString, QList<CPlusPlus::Document::Ptr>> newDeclarations;
@@ -1436,9 +1437,9 @@ void ModelManagerInterface::updateCppQmlTypes(
bool hasNewInfo = false;
using DocScanPair = QPair<CPlusPlus::Document::Ptr, bool>;
for (const DocScanPair &pair : documents) {
- if (futureInterface.isCanceled())
+ if (promise.isCanceled())
return;
- futureInterface.setProgressValue(futureInterface.progressValue() + 1);
+ promise.setProgressValue(promise.future().progressValue() + 1);
CPlusPlus::Document::Ptr doc = pair.first;
const bool scan = pair.second;
diff --git a/src/libs/qmljs/qmljsmodelmanagerinterface.h b/src/libs/qmljs/qmljsmodelmanagerinterface.h
index b7b88c24ff5..e702b91afbb 100644
--- a/src/libs/qmljs/qmljsmodelmanagerinterface.h
+++ b/src/libs/qmljs/qmljsmodelmanagerinterface.h
@@ -179,10 +179,12 @@ public:
void addFuture(const QFuture<void> &future);
QmlJS::Document::Ptr ensuredGetDocumentForPath(const Utils::FilePath &filePath);
- static void importScan(QFutureInterface<void> &future, const WorkingCopy& workingCopyInternal,
- const PathsAndLanguages& paths, ModelManagerInterface *modelManager,
- bool emitDocChangedOnDisk, bool libOnly = true,
- bool forceRescan = false);
+ static void importScan(const WorkingCopy &workingCopy, const PathsAndLanguages &paths,
+ ModelManagerInterface *modelManager, bool emitDocChanged,
+ bool libOnly = true, bool forceRescan = false);
+ static void importScanAsync(QPromise<void> &promise, const WorkingCopy& workingCopyInternal,
+ const PathsAndLanguages& paths, ModelManagerInterface *modelManager,
+ bool emitDocChanged, bool libOnly = true, bool forceRescan = false);
virtual void resetCodeModel();
void removeProjectInfo(ProjectExplorer::Project *project);
@@ -218,16 +220,15 @@ protected:
QmlJS::Dialect mainLanguage,
bool emitDocChangedOnDisk,
const std::function<bool(qreal)> &reportProgress);
- static void parse(QFutureInterface<void> &future,
+ static void parse(QPromise<void> &promise,
const WorkingCopy &workingCopyInternal,
QList<Utils::FilePath> files,
ModelManagerInterface *modelManager,
QmlJS::Dialect mainLanguage,
bool emitDocChangedOnDisk);
- static void updateCppQmlTypes(
- QFutureInterface<void> &futureInterface, ModelManagerInterface *qmlModelManager,
- const CPlusPlus::Snapshot &snapshot,
- const QHash<QString, QPair<CPlusPlus::Document::Ptr, bool>> &documents);
+ static void updateCppQmlTypes(QPromise<void> &promise, ModelManagerInterface *qmlModelManager,
+ const CPlusPlus::Snapshot &snapshot,
+ const QHash<QString, QPair<CPlusPlus::Document::Ptr, bool>> &documents);
void maybeScan(const PathsAndLanguages &importPaths);
void updateImportPaths();
diff --git a/src/libs/qmljs/qmljsplugindumper.cpp b/src/libs/qmljs/qmljsplugindumper.cpp
index a7bafdde948..20736803d27 100644
--- a/src/libs/qmljs/qmljsplugindumper.cpp
+++ b/src/libs/qmljs/qmljsplugindumper.cpp
@@ -7,9 +7,9 @@
#include "qmljsmodelmanagerinterface.h"
#include "qmljstr.h"
#include "qmljsutils.h"
-#include "qmljsviewercontext.h"
#include <utils/algorithm.h>
+#include <utils/asynctask.h>
#include <utils/filesystemwatcher.h>
#include <utils/fileutils.h>
#include <utils/hostosinfo.h>
@@ -273,14 +273,13 @@ void PluginDumper::qmlPluginTypeDumpDone(QtcProcess *process)
QStringList dependencies;
};
- auto future = Utils::runAsync(m_modelManager->threadPool(),
- [output, libraryPath](QFutureInterface<CppQmlTypesInfo>& future)
- {
+ auto future = Utils::asyncRun(m_modelManager->threadPool(),
+ [output, libraryPath](QPromise<CppQmlTypesInfo> &promise) {
CppQmlTypesInfo infos;
- CppQmlTypesLoader::parseQmlTypeDescriptions(output, &infos.objectsList, &infos.moduleApis, &infos.dependencies,
- &infos.error, &infos.warning,
- "<dump of " + libraryPath.toUserOutput() + '>');
- future.reportFinished(&infos);
+ CppQmlTypesLoader::parseQmlTypeDescriptions(output, &infos.objectsList,
+ &infos.moduleApis, &infos.dependencies, &infos.error, &infos.warning,
+ "<dump of " + libraryPath.toUserOutput() + '>');
+ promise.addResult(infos);
});
m_modelManager->addFuture(future);
@@ -327,8 +326,8 @@ void PluginDumper::pluginChanged(const QString &pluginLibrary)
QFuture<PluginDumper::QmlTypeDescription> PluginDumper::loadQmlTypeDescription(const FilePaths &paths) const
{
- auto future = Utils::runAsync(m_modelManager->threadPool(), [=](QFutureInterface<PluginDumper::QmlTypeDescription> &future)
- {
+ auto future = Utils::asyncRun(m_modelManager->threadPool(),
+ [=](QPromise<PluginDumper::QmlTypeDescription> &promise) {
PluginDumper::QmlTypeDescription result;
for (const FilePath &p: paths) {
@@ -355,8 +354,7 @@ QFuture<PluginDumper::QmlTypeDescription> PluginDumper::loadQmlTypeDescription(c
if (!warning.isEmpty())
result.warnings += warning;
}
-
- future.reportFinished(&result);
+ promise.addResult(result);
});
m_modelManager->addFuture(future);
diff --git a/src/libs/qmljs/qmljsutils.cpp b/src/libs/qmljs/qmljsutils.cpp
index 1609e49c68c..e33a6b3e809 100644
--- a/src/libs/qmljs/qmljsutils.cpp
+++ b/src/libs/qmljs/qmljsutils.cpp
@@ -115,8 +115,9 @@ SourceLocation QmlJS::fullLocationForQualifiedId(AST::UiQualifiedId *qualifiedId
}
/*!
- \returns the value of the 'id:' binding in \a object
- \param idBinding optional out parameter to get the UiScriptBinding for the id binding
+ Returns the value of the 'id:' binding in \a object.
+
+ \a idBinding is optional out parameter to get the UiScriptBinding for the id binding.
*/
QString QmlJS::idOfObject(Node *object, UiScriptBinding **idBinding)
{
diff --git a/src/libs/tracing/timelinetracemanager.cpp b/src/libs/tracing/timelinetracemanager.cpp
index 96eb147519e..b252a02cee9 100644
--- a/src/libs/tracing/timelinetracemanager.cpp
+++ b/src/libs/tracing/timelinetracemanager.cpp
@@ -6,12 +6,10 @@
#include "timelinetracemanager.h"
#include "tracingtr.h"
+#include <utils/asynctask.h>
#include <utils/qtcassert.h>
-#include <utils/temporaryfile.h>
-#include <utils/runextensions.h>
#include <QFile>
-#include <QDataStream>
#include <memory>
@@ -223,8 +221,11 @@ QFuture<void> TimelineTraceManager::save(const QString &filename)
connect(writer, &QObject::destroyed, this, &TimelineTraceManager::saveFinished);
connect(writer, &TimelineTraceFile::error, this, &TimelineTraceManager::error);
- return Utils::runAsync([filename, writer] (QFutureInterface<void> &future) {
- writer->setFuture(future);
+ QFutureInterface<void> fi;
+ fi.reportStarted();
+ writer->setFuture(fi);
+
+ Utils::asyncRun([filename, writer, fi] {
QFile file(filename);
if (file.open(QIODevice::WriteOnly))
@@ -232,10 +233,13 @@ QFuture<void> TimelineTraceManager::save(const QString &filename)
else
writer->fail(Tr::tr("Could not open %1 for writing.").arg(filename));
- if (future.isCanceled())
+ if (fi.isCanceled())
file.remove();
writer->deleteLater();
+ QFutureInterface fiCopy = fi;
+ fiCopy.reportFinished();
});
+ return fi.future();
}
QFuture<void> TimelineTraceManager::load(const QString &filename)
@@ -249,8 +253,10 @@ QFuture<void> TimelineTraceManager::load(const QString &filename)
connect(reader, &QObject::destroyed, this, &TimelineTraceManager::loadFinished);
connect(reader, &TimelineTraceFile::error, this, &TimelineTraceManager::error);
- QFuture<void> future = Utils::runAsync([filename, reader] (QFutureInterface<void> &future) {
- reader->setFuture(future);
+ QFutureInterface<void> fi;
+ fi.reportStarted();
+ reader->setFuture(fi);
+ Utils::asyncRun([filename, reader, fi] {
QFile file(filename);
if (file.open(QIODevice::ReadOnly))
@@ -259,11 +265,13 @@ QFuture<void> TimelineTraceManager::load(const QString &filename)
reader->fail(Tr::tr("Could not open %1 for reading.").arg(filename));
reader->deleteLater();
+ QFutureInterface fiCopy = fi;
+ fiCopy.reportFinished();
});
QFutureWatcher<void> *watcher = new QFutureWatcher<void>(reader);
connect(watcher, &QFutureWatcherBase::canceled, this, &TimelineTraceManager::clearAll);
- connect(watcher, &QFutureWatcherBase::finished, this, [this, reader]() {
+ connect(watcher, &QFutureWatcherBase::finished, this, [this, reader] {
if (!reader->isCanceled()) {
if (reader->traceStart() >= 0)
decreaseTraceStart(reader->traceStart());
@@ -272,9 +280,8 @@ QFuture<void> TimelineTraceManager::load(const QString &filename)
finalize();
}
});
- watcher->setFuture(future);
-
- return future;
+ watcher->setFuture(fi.future());
+ return fi.future();
}
qint64 TimelineTraceManager::traceStart() const
@@ -366,10 +373,9 @@ void TimelineTraceManager::restrictByFilter(TraceEventFilter filter)
QFutureInterface<void> future;
replayEvents(filter(std::bind(&TimelineTraceManagerPrivate::dispatch, d,
- std::placeholders::_1, std::placeholders::_2)),
- [this]() {
+ std::placeholders::_1, std::placeholders::_2)), [this] {
initialize();
- }, [this]() {
+ }, [this] {
if (d->notesModel)
d->notesModel->restore();
finalize();
diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt
index 76f4e8a314a..6b2e835d198 100644
--- a/src/libs/utils/CMakeLists.txt
+++ b/src/libs/utils/CMakeLists.txt
@@ -2,7 +2,7 @@ add_qtc_library(Utils
DEPENDS Qt::Qml Qt::Xml
PUBLIC_DEPENDS
Qt::Concurrent Qt::Core Qt::Network Qt::Gui Qt::Widgets
- Qt6Core5Compat
+ Qt::Core5Compat
SOURCES
../3rdparty/span/span.hpp
../3rdparty/tl_expected/include/tl/expected.hpp
@@ -55,6 +55,8 @@ add_qtc_library(Utils
filepath.cpp filepath.h
filepathinfo.h
filesearch.cpp filesearch.h
+ filestreamer.cpp filestreamer.h
+ filestreamermanager.cpp filestreamermanager.h
filesystemmodel.cpp filesystemmodel.h
filesystemwatcher.cpp filesystemwatcher.h
fileutils.cpp fileutils.h
@@ -170,7 +172,8 @@ add_qtc_library(Utils
temporarydirectory.cpp temporarydirectory.h
temporaryfile.cpp temporaryfile.h
terminalcommand.cpp terminalcommand.h
- terminalprocess.cpp terminalprocess_p.h
+ terminalhooks.cpp terminalhooks.h
+ terminalinterface.cpp terminalinterface.h
textfieldcheckbox.cpp textfieldcheckbox.h
textfieldcombobox.cpp textfieldcombobox.h
textfileformat.cpp textfileformat.h
@@ -190,6 +193,7 @@ add_qtc_library(Utils
utils_global.h
utilstr.h
utilsicons.cpp utilsicons.h
+ utiltypes.h
variablechooser.cpp variablechooser.h
winutils.cpp winutils.h
wizard.cpp wizard.h
@@ -256,7 +260,7 @@ extend_qtc_library(Utils CONDITION UNIX AND NOT APPLE
extend_qtc_library(Utils
CONDITION TARGET Qt::CorePrivate
- DEPENDS Qt::CorePrivate
+ DEPENDS Qt::CorePrivate ptyqt
DEFINES QTC_UTILS_WITH_FSENGINE
SOURCES fsengine/fsengine_impl.cpp
fsengine/fsengine_impl.h
@@ -270,19 +274,10 @@ extend_qtc_library(Utils
)
if (WIN32)
- add_qtc_executable(qtcreator_process_stub
- SOURCES process_stub_win.c
- DEPENDS shell32
- DEFINES _UNICODE UNICODE _CRT_SECURE_NO_WARNINGS
- )
-
add_qtc_executable(qtcreator_ctrlc_stub
DEPENDS user32 shell32
DEFINES _UNICODE UNICODE _CRT_SECURE_NO_WARNINGS
SOURCES
process_ctrlc_stub.cpp
)
-else()
- add_qtc_executable(qtcreator_process_stub SOURCES process_stub_unix.c)
endif()
-
diff --git a/src/libs/utils/aspects.cpp b/src/libs/utils/aspects.cpp
index 72e667d76a6..aed8c851d17 100644
--- a/src/libs/utils/aspects.cpp
+++ b/src/libs/utils/aspects.cpp
@@ -9,6 +9,7 @@
#include "layoutbuilder.h"
#include "pathchooser.h"
#include "qtcassert.h"
+#include "qtcolorbutton.h"
#include "qtcsettings.h"
#include "utilstr.h"
#include "variablechooser.h"
@@ -579,6 +580,12 @@ public:
QPointer<QGroupBox> m_groupBox; // For BoolAspects handling GroupBox check boxes
};
+class ColorAspectPrivate
+{
+public:
+ QPointer<QtColorButton> m_colorButton; // Owned by configuration widget
+};
+
class SelectionAspectPrivate
{
public:
@@ -625,7 +632,7 @@ public:
QString m_placeHolderText;
QString m_historyCompleterKey;
PathChooser::Kind m_expectedKind = PathChooser::File;
- EnvironmentChange m_environmentChange;
+ Environment m_environment;
QPointer<ElidingLabel> m_labelDisplay;
QPointer<FancyLineEdit> m_lineEditDisplay;
QPointer<PathChooser> m_pathChooserDisplay;
@@ -968,16 +975,11 @@ void StringAspect::setExpectedKind(const PathChooser::Kind expectedKind)
d->m_pathChooserDisplay->setExpectedKind(expectedKind);
}
-void StringAspect::setEnvironmentChange(const EnvironmentChange &change)
-{
- d->m_environmentChange = change;
- if (d->m_pathChooserDisplay)
- d->m_pathChooserDisplay->setEnvironmentChange(change);
-}
-
void StringAspect::setEnvironment(const Environment &env)
{
- setEnvironmentChange(EnvironmentChange::fromDictionary(env.toDictionary()));
+ d->m_environment = env;
+ if (d->m_pathChooserDisplay)
+ d->m_pathChooserDisplay->setEnvironment(env);
}
void StringAspect::setBaseFileName(const FilePath &baseFileName)
@@ -1075,7 +1077,7 @@ void StringAspect::addToLayout(Layouting::LayoutBuilder &builder)
d->m_pathChooserDisplay->setHistoryCompleter(d->m_historyCompleterKey);
if (d->m_validator)
d->m_pathChooserDisplay->setValidationFunction(d->m_validator);
- d->m_pathChooserDisplay->setEnvironmentChange(d->m_environmentChange);
+ d->m_pathChooserDisplay->setEnvironment(d->m_environment);
d->m_pathChooserDisplay->setBaseDirectory(d->m_baseFileName);
d->m_pathChooserDisplay->setOpenTerminalHandler(d->m_openTerminal);
if (defaultValue() == value())
@@ -1288,6 +1290,70 @@ void StringAspect::makeCheckable(CheckBoxPlacement checkBoxPlacement,
}
/*!
+ \class Utils::ColorAspect
+ \inmodule QtCreator
+
+ \brief A color aspect is a color property of some object, together with
+ a description of its behavior for common operations like visualizing or
+ persisting.
+
+ The color aspect is displayed using a QtColorButton.
+*/
+
+ColorAspect::ColorAspect(const QString &settingsKey)
+ : d(new Internal::ColorAspectPrivate)
+{
+ setDefaultValue(QColor::fromRgb(0, 0, 0));
+ setSettingsKey(settingsKey);
+ setSpan(1, 1);
+
+ addDataExtractor(this, &ColorAspect::value, &Data::value);
+}
+
+ColorAspect::~ColorAspect() = default;
+
+void ColorAspect::addToLayout(Layouting::LayoutBuilder &builder)
+{
+ QTC_CHECK(!d->m_colorButton);
+ d->m_colorButton = createSubWidget<QtColorButton>();
+ builder.addItem(d->m_colorButton.data());
+ d->m_colorButton->setColor(value());
+ if (isAutoApply()) {
+ connect(d->m_colorButton.data(),
+ &QtColorButton::colorChanged,
+ this,
+ [this](const QColor &color) { setValue(color); });
+ }
+}
+
+QColor ColorAspect::value() const
+{
+ return BaseAspect::value().value<QColor>();
+}
+
+void ColorAspect::setValue(const QColor &value)
+{
+ if (BaseAspect::setValueQuietly(value))
+ emit changed();
+}
+
+QVariant ColorAspect::volatileValue() const
+{
+ QTC_CHECK(!isAutoApply());
+ if (d->m_colorButton)
+ return d->m_colorButton->color();
+ QTC_CHECK(false);
+ return {};
+}
+
+void ColorAspect::setVolatileValue(const QVariant &val)
+{
+ QTC_CHECK(!isAutoApply());
+ if (d->m_colorButton)
+ d->m_colorButton->setColor(val.value<QColor>());
+}
+
+/*!
\class Utils::BoolAspect
\inmodule QtCreator
diff --git a/src/libs/utils/aspects.h b/src/libs/utils/aspects.h
index 413a17e6245..0b9a5606596 100644
--- a/src/libs/utils/aspects.h
+++ b/src/libs/utils/aspects.h
@@ -31,6 +31,7 @@ namespace Internal {
class AspectContainerPrivate;
class BaseAspectPrivate;
class BoolAspectPrivate;
+class ColorAspectPrivate;
class DoubleAspectPrivate;
class IntegerAspectPrivate;
class MultiSelectionAspectPrivate;
@@ -245,6 +246,31 @@ private:
std::unique_ptr<Internal::BoolAspectPrivate> d;
};
+class QTCREATOR_UTILS_EXPORT ColorAspect : public BaseAspect
+{
+ Q_OBJECT
+
+public:
+ explicit ColorAspect(const QString &settingsKey = QString());
+ ~ColorAspect() override;
+
+ struct Data : BaseAspect::Data
+ {
+ QColor value;
+ };
+
+ void addToLayout(Layouting::LayoutBuilder &builder) override;
+
+ QColor value() const;
+ void setValue(const QColor &val);
+
+ QVariant volatileValue() const override;
+ void setVolatileValue(const QVariant &val) override;
+
+private:
+ std::unique_ptr<Internal::ColorAspectPrivate> d;
+};
+
class QTCREATOR_UTILS_EXPORT SelectionAspect : public BaseAspect
{
Q_OBJECT
@@ -357,7 +383,6 @@ public:
void setPlaceHolderText(const QString &placeHolderText);
void setHistoryCompleter(const QString &historyCompleterKey);
void setExpectedKind(const PathChooser::Kind expectedKind);
- void setEnvironmentChange(const EnvironmentChange &change);
void setEnvironment(const Environment &env);
void setBaseFileName(const FilePath &baseFileName);
void setUndoRedoEnabled(bool readOnly);
diff --git a/src/libs/utils/asynctask.cpp b/src/libs/utils/asynctask.cpp
index 3576ad804c4..c2c42431d18 100644
--- a/src/libs/utils/asynctask.cpp
+++ b/src/libs/utils/asynctask.cpp
@@ -3,6 +3,55 @@
#include "asynctask.h"
+#include <QCoreApplication>
+
namespace Utils {
+static int s_maxThreadCount = INT_MAX;
+
+class AsyncThreadPool : public QThreadPool
+{
+public:
+ AsyncThreadPool(QThread::Priority priority) {
+ setThreadPriority(priority);
+ setMaxThreadCount(s_maxThreadCount);
+ moveToThread(qApp->thread());
+ }
+};
+
+#if QT_VERSION < QT_VERSION_CHECK(6, 3, 0)
+Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_idle, (QThread::IdlePriority));
+Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_lowest, (QThread::LowestPriority));
+Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_low, (QThread::LowPriority));
+Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_normal, (QThread::NormalPriority));
+Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_high, (QThread::HighPriority));
+Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_highest, (QThread::HighestPriority));
+Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_timeCritical, (QThread::TimeCriticalPriority));
+Q_GLOBAL_STATIC_WITH_ARGS(AsyncThreadPool, s_inherit, (QThread::InheritPriority));
+#else
+Q_GLOBAL_STATIC(AsyncThreadPool, s_idle, QThread::IdlePriority);
+Q_GLOBAL_STATIC(AsyncThreadPool, s_lowest, QThread::LowestPriority);
+Q_GLOBAL_STATIC(AsyncThreadPool, s_low, QThread::LowPriority);
+Q_GLOBAL_STATIC(AsyncThreadPool, s_normal, QThread::NormalPriority);
+Q_GLOBAL_STATIC(AsyncThreadPool, s_high, QThread::HighPriority);
+Q_GLOBAL_STATIC(AsyncThreadPool, s_highest, QThread::HighestPriority);
+Q_GLOBAL_STATIC(AsyncThreadPool, s_timeCritical, QThread::TimeCriticalPriority);
+Q_GLOBAL_STATIC(AsyncThreadPool, s_inherit, QThread::InheritPriority);
+#endif
+
+QThreadPool *asyncThreadPool(QThread::Priority priority)
+{
+ switch (priority) {
+ case QThread::IdlePriority : return s_idle;
+ case QThread::LowestPriority : return s_lowest;
+ case QThread::LowPriority : return s_low;
+ case QThread::NormalPriority : return s_normal;
+ case QThread::HighPriority : return s_high;
+ case QThread::HighestPriority : return s_highest;
+ case QThread::TimeCriticalPriority : return s_timeCritical;
+ case QThread::InheritPriority : return s_inherit;
+ }
+ return nullptr;
+}
+
} // namespace Utils
diff --git a/src/libs/utils/asynctask.h b/src/libs/utils/asynctask.h
index 1a6ae7d1b6a..a600a14b247 100644
--- a/src/libs/utils/asynctask.h
+++ b/src/libs/utils/asynctask.h
@@ -7,13 +7,44 @@
#include "futuresynchronizer.h"
#include "qtcassert.h"
-#include "runextensions.h"
#include "tasktree.h"
#include <QFutureWatcher>
+#include <QtConcurrent>
namespace Utils {
+QTCREATOR_UTILS_EXPORT QThreadPool *asyncThreadPool(QThread::Priority priority);
+
+template <typename Function, typename ...Args>
+auto asyncRun(QThreadPool *threadPool, QThread::Priority priority,
+ Function &&function, Args &&...args)
+{
+ QThreadPool *pool = threadPool ? threadPool : asyncThreadPool(priority);
+ return QtConcurrent::run(pool, std::forward<Function>(function), std::forward<Args>(args)...);
+}
+
+template <typename Function, typename ...Args>
+auto asyncRun(QThread::Priority priority, Function &&function, Args &&...args)
+{
+ return asyncRun<Function, Args...>(nullptr, priority,
+ std::forward<Function>(function), std::forward<Args>(args)...);
+}
+
+template <typename Function, typename ...Args>
+auto asyncRun(QThreadPool *threadPool, Function &&function, Args &&...args)
+{
+ return asyncRun<Function, Args...>(threadPool, QThread::InheritPriority,
+ std::forward<Function>(function), std::forward<Args>(args)...);
+}
+
+template <typename Function, typename ...Args>
+auto asyncRun(Function &&function, Args &&...args)
+{
+ return asyncRun<Function, Args...>(nullptr, QThread::InheritPriority,
+ std::forward<Function>(function), std::forward<Args>(args)...);
+}
+
class QTCREATOR_UTILS_EXPORT AsyncTaskBase : public QObject
{
Q_OBJECT
@@ -21,13 +52,18 @@ class QTCREATOR_UTILS_EXPORT AsyncTaskBase : public QObject
signals:
void started();
void done();
+ void resultReadyAt(int index);
};
template <typename ResultType>
class AsyncTask : public AsyncTaskBase
{
public:
- AsyncTask() { connect(&m_watcher, &QFutureWatcherBase::finished, this, &AsyncTaskBase::done); }
+ AsyncTask() {
+ connect(&m_watcher, &QFutureWatcherBase::finished, this, &AsyncTaskBase::done);
+ connect(&m_watcher, &QFutureWatcherBase::resultReadyAt,
+ this, &AsyncTaskBase::resultReadyAt);
+ }
~AsyncTask()
{
if (isDone())
@@ -38,15 +74,12 @@ public:
m_watcher.waitForFinished();
}
- using StartHandler = std::function<QFuture<ResultType>()>;
-
template <typename Function, typename ...Args>
- void setAsyncCallData(const Function &function, const Args &...args)
+ void setConcurrentCallData(Function &&function, Args &&...args)
{
- m_startHandler = [=] {
- return Utils::runAsync(m_threadPool, m_priority, function, args...);
- };
+ return wrapConcurrent(std::forward<Function>(function), std::forward<Args>(args)...);
}
+
void setFutureSynchronizer(FutureSynchronizer *synchorizer) { m_synchronizer = synchorizer; }
void setThreadPool(QThreadPool *pool) { m_threadPool = pool; }
void setPriority(QThread::Priority priority) { m_priority = priority; }
@@ -65,10 +98,29 @@ public:
QFuture<ResultType> future() const { return m_watcher.future(); }
ResultType result() const { return m_watcher.result(); }
+ ResultType resultAt(int index) const { return m_watcher.resultAt(index); }
QList<ResultType> results() const { return future().results(); }
bool isResultAvailable() const { return future().resultCount(); }
private:
+ template <typename Function, typename ...Args>
+ void wrapConcurrent(Function &&function, Args &&...args)
+ {
+ m_startHandler = [=] {
+ return asyncRun(m_threadPool, m_priority, function, args...);
+ };
+ }
+
+ template <typename Function, typename ...Args>
+ void wrapConcurrent(std::reference_wrapper<const Function> &&wrapper, Args &&...args)
+ {
+ m_startHandler = [=] {
+ return asyncRun(m_threadPool, m_priority, std::forward<const Function>(wrapper.get()),
+ args...);
+ };
+ }
+
+ using StartHandler = std::function<QFuture<ResultType>()>;
StartHandler m_startHandler;
FutureSynchronizer *m_synchronizer = nullptr;
QThreadPool *m_threadPool = nullptr;
diff --git a/src/libs/utils/clangutils.cpp b/src/libs/utils/clangutils.cpp
index 3f2fb69909d..ee4868f94b3 100644
--- a/src/libs/utils/clangutils.cpp
+++ b/src/libs/utils/clangutils.cpp
@@ -45,6 +45,11 @@ QVersionNumber clangdVersion(const FilePath &clangd)
bool checkClangdVersion(const FilePath &clangd, QString *error)
{
+ if (clangd.isEmpty()) {
+ *error = Tr::tr("No clangd executable specified.");
+ return false;
+ }
+
const QVersionNumber version = clangdVersion(clangd);
if (version >= minimumClangdVersion())
return true;
diff --git a/src/libs/utils/commandline.cpp b/src/libs/utils/commandline.cpp
index 6f8d4a4f5d9..9f6fdec35d1 100644
--- a/src/libs/utils/commandline.cpp
+++ b/src/libs/utils/commandline.cpp
@@ -362,12 +362,12 @@ static QStringList splitArgsUnix(const QString &args, bool abortOnMeta,
if (var == pwdName && pwd && !pwd->isEmpty()) {
cret += *pwd;
} else {
- Environment::const_iterator vit = env->constFind(var);
- if (vit == env->constEnd()) {
+ const Environment::FindResult res = env->find(var);
+ if (!res) {
if (abortOnMeta)
goto metaerr; // Assume this is a shell builtin
} else {
- cret += env->expandedValueForKey(env->key(vit));
+ cret += env->expandedValueForKey(res->key);
}
}
if (!braced)
@@ -412,12 +412,12 @@ static QStringList splitArgsUnix(const QString &args, bool abortOnMeta,
if (var == pwdName && pwd && !pwd->isEmpty()) {
val = *pwd;
} else {
- Environment::const_iterator vit = env->constFind(var);
- if (vit == env->constEnd()) {
+ const Environment::FindResult res = env->find(var);
+ if (!res) {
if (abortOnMeta)
goto metaerr; // Assume this is a shell builtin
} else {
- val = env->expandedValueForKey(env->key(vit));
+ val = env->expandedValueForKey(res->key);
}
}
for (int i = 0; i < val.length(); i++) {
@@ -1409,6 +1409,8 @@ CommandLine::CommandLine(const FilePath &executable)
: m_executable(executable)
{}
+CommandLine::~CommandLine() = default;
+
CommandLine::CommandLine(const FilePath &exe, const QStringList &args)
: m_executable(exe)
{
@@ -1429,16 +1431,20 @@ CommandLine::CommandLine(const FilePath &exe, const QString &args, RawType)
CommandLine CommandLine::fromUserInput(const QString &cmdline, MacroExpander *expander)
{
- CommandLine cmd;
- const int pos = cmdline.indexOf(' ');
- if (pos == -1) {
- cmd.m_executable = FilePath::fromString(cmdline);
- } else {
- cmd.m_executable = FilePath::fromString(cmdline.left(pos));
- cmd.m_arguments = cmdline.right(cmdline.length() - pos - 1);
- if (expander)
- cmd.m_arguments = expander->expand(cmd.m_arguments);
- }
+ if (cmdline.isEmpty())
+ return {};
+
+ QString input = cmdline.trimmed();
+
+ QStringList result = ProcessArgs::splitArgs(cmdline, HostOsInfo::hostOs());
+
+ if (result.isEmpty())
+ return {};
+
+ auto cmd = CommandLine(FilePath::fromUserInput(result.value(0)), result.mid(1));
+ if (expander)
+ cmd.m_arguments = expander->expand(cmd.m_arguments);
+
return cmd;
}
diff --git a/src/libs/utils/commandline.h b/src/libs/utils/commandline.h
index 23585adb8af..d7fc0a066be 100644
--- a/src/libs/utils/commandline.h
+++ b/src/libs/utils/commandline.h
@@ -55,7 +55,7 @@ public:
//! Append already quoted arguments to a shell command
static void addArgs(QString *args, const QString &inArgs);
//! Split a shell command into separate arguments.
- static QStringList splitArgs(const QString &cmd, OsType osType = HostOsInfo::hostOs(),
+ static QStringList splitArgs(const QString &cmd, OsType osType,
bool abortOnMeta = false, SplitError *err = nullptr,
const Environment *env = nullptr, const QString *pwd = nullptr);
//! Safely replace the expandos in a shell command
@@ -119,6 +119,8 @@ public:
enum RawType { Raw };
CommandLine();
+ ~CommandLine();
+
explicit CommandLine(const FilePath &executable);
CommandLine(const FilePath &exe, const QStringList &args);
CommandLine(const FilePath &exe, const QStringList &args, OsType osType);
diff --git a/src/libs/utils/devicefileaccess.cpp b/src/libs/utils/devicefileaccess.cpp
index 3d8354d7a0f..1e3c771ee7e 100644
--- a/src/libs/utils/devicefileaccess.cpp
+++ b/src/libs/utils/devicefileaccess.cpp
@@ -8,6 +8,7 @@
#include "environment.h"
#include "expected.h"
#include "hostosinfo.h"
+#include "osspecificaspects.h"
#include "qtcassert.h"
#include "utilstr.h"
@@ -268,12 +269,6 @@ bool DeviceFileAccess::renameFile(const FilePath &filePath, const FilePath &targ
return false;
}
-OsType DeviceFileAccess::osType(const FilePath &filePath) const
-{
- Q_UNUSED(filePath)
- return OsTypeOther;
-}
-
FilePath DeviceFileAccess::symLinkTarget(const FilePath &filePath) const
{
Q_UNUSED(filePath)
@@ -379,29 +374,6 @@ std::optional<FilePath> DeviceFileAccess::refersToExecutableFile(
return {};
}
-void DeviceFileAccess::asyncFileContents(const FilePath &filePath,
- const Continuation<expected_str<QByteArray>> &cont,
- qint64 limit,
- qint64 offset) const
-{
- cont(fileContents(filePath, limit, offset));
-}
-
-void DeviceFileAccess::asyncWriteFileContents(const FilePath &filePath,
- const Continuation<expected_str<qint64>> &cont,
- const QByteArray &data,
- qint64 offset) const
-{
- cont(writeFileContents(filePath, data, offset));
-}
-
-void DeviceFileAccess::asyncCopyFile(const FilePath &filePath,
- const Continuation<expected_str<void>> &cont,
- const FilePath &target) const
-{
- cont(copyFile(filePath, target));
-}
-
expected_str<FilePath> DeviceFileAccess::createTempFile(const FilePath &filePath)
{
Q_UNUSED(filePath)
@@ -587,10 +559,13 @@ bool DesktopDeviceFileAccess::removeRecursively(const FilePath &filePath, QStrin
expected_str<void> DesktopDeviceFileAccess::copyFile(const FilePath &filePath,
const FilePath &target) const
{
- if (QFile::copy(filePath.path(), target.path()))
+ QFile srcFile(filePath.path());
+
+ if (srcFile.copy(target.path()))
return {};
- return make_unexpected(Tr::tr("Failed to copy file \"%1\" to \"%2\".")
- .arg(filePath.toUserOutput(), target.toUserOutput()));
+ return make_unexpected(
+ Tr::tr("Failed to copy file \"%1\" to \"%2\": %3")
+ .arg(filePath.toUserOutput(), target.toUserOutput(), srcFile.errorString()));
}
bool DesktopDeviceFileAccess::renameFile(const FilePath &filePath, const FilePath &target) const
@@ -818,12 +793,6 @@ QByteArray DesktopDeviceFileAccess::fileId(const FilePath &filePath) const
return result;
}
-OsType DesktopDeviceFileAccess::osType(const FilePath &filePath) const
-{
- Q_UNUSED(filePath);
- return HostOsInfo::hostOs();
-}
-
// UnixDeviceAccess
UnixDeviceFileAccess::~UnixDeviceFileAccess() = default;
@@ -1054,12 +1023,6 @@ expected_str<FilePath> UnixDeviceFileAccess::createTempFile(const FilePath &file
return newPath;
}
-OsType UnixDeviceFileAccess::osType(const FilePath &filePath) const
-{
- Q_UNUSED(filePath)
- return OsTypeLinux;
-}
-
QDateTime UnixDeviceFileAccess::lastModified(const FilePath &filePath) const
{
const RunResult result = runInShell(
@@ -1069,10 +1032,19 @@ QDateTime UnixDeviceFileAccess::lastModified(const FilePath &filePath) const
return dt;
}
+QStringList UnixDeviceFileAccess::statArgs(const FilePath &filePath,
+ const QString &linuxFormat,
+ const QString &macFormat) const
+{
+ return (filePath.osType() == OsTypeMac ? QStringList{"-f", macFormat} : QStringList{"-c", linuxFormat})
+ << "-L" << filePath.path();
+}
+
QFile::Permissions UnixDeviceFileAccess::permissions(const FilePath &filePath) const
{
- const RunResult result = runInShell(
- {"stat", {"-L", "-c", "%a", filePath.path()}, OsType::OsTypeLinux});
+ QStringList args = statArgs(filePath, "%a", "%p");
+
+ const RunResult result = runInShell({"stat", args, OsType::OsTypeLinux});
const uint bits = result.stdOut.toUInt(nullptr, 8);
QFileDevice::Permissions perm = {};
#define BIT(n, p) \
@@ -1100,8 +1072,8 @@ bool UnixDeviceFileAccess::setPermissions(const FilePath &filePath, QFile::Permi
qint64 UnixDeviceFileAccess::fileSize(const FilePath &filePath) const
{
- const RunResult result = runInShell(
- {"stat", {"-L", "-c", "%s", filePath.path()}, OsType::OsTypeLinux});
+ const QStringList args = statArgs(filePath, "%s", "%z");
+ const RunResult result = runInShell({"stat", args, OsType::OsTypeLinux});
return result.stdOut.toLongLong();
}
@@ -1113,8 +1085,9 @@ qint64 UnixDeviceFileAccess::bytesAvailable(const FilePath &filePath) const
QByteArray UnixDeviceFileAccess::fileId(const FilePath &filePath) const
{
- const RunResult result = runInShell(
- {"stat", {"-L", "-c", "%D:%i", filePath.path()}, OsType::OsTypeLinux});
+ const QStringList args = statArgs(filePath, "%D:%i", "%d:%i");
+
+ const RunResult result = runInShell({"stat", args, OsType::OsTypeLinux});
if (result.exitCode != 0)
return {};
@@ -1136,9 +1109,12 @@ FilePathInfo UnixDeviceFileAccess::filePathInfo(const FilePath &filePath) const
return r;
}
- const RunResult stat = runInShell(
- {"stat", {"-L", "-c", "%f %Y %s", filePath.path()}, OsType::OsTypeLinux});
- return FileUtils::filePathInfoFromTriple(QString::fromLatin1(stat.stdOut));
+
+ const QStringList args = statArgs(filePath, "%f %Y %s", "%p %m %z");
+
+ const RunResult stat = runInShell({"stat", args, OsType::OsTypeLinux});
+ return FileUtils::filePathInfoFromTriple(QString::fromLatin1(stat.stdOut),
+ filePath.osType() == OsTypeMac ? 8 : 16);
}
// returns whether 'find' could be used.
@@ -1152,8 +1128,13 @@ bool UnixDeviceFileAccess::iterateWithFind(const FilePath &filePath,
// TODO: Using stat -L will always return the link target, not the link itself.
// We may wan't to add the information that it is a link at some point.
+
+ const QString statFormat = filePath.osType() == OsTypeMac
+ ? QLatin1String("-f \"%p %m %z\"") : QLatin1String("-c \"%f %Y %s\"");
+
if (callBack.index() == 1)
- cmdLine.addArgs(R"(-exec echo -n \"{}\"" " \; -exec stat -L -c "%f %Y %s" "{}" \;)",
+ cmdLine.addArgs(QString(R"(-exec echo -n \"{}\"" " \; -exec stat -L %1 "{}" \;)")
+ .arg(statFormat),
CommandLine::Raw);
const RunResult result = runInShell(cmdLine);
@@ -1175,23 +1156,26 @@ bool UnixDeviceFileAccess::iterateWithFind(const FilePath &filePath,
if (entries.isEmpty())
return true;
- const auto toFilePath = [&filePath, &callBack](const QString &entry) {
- if (callBack.index() == 0)
- return std::get<0>(callBack)(filePath.withNewPath(entry));
+ const int modeBase = filePath.osType() == OsTypeMac ? 8 : 16;
- const QString fileName = entry.mid(1, entry.lastIndexOf('\"') - 1);
- const QString infos = entry.mid(fileName.length() + 3);
+ const auto toFilePath =
+ [&filePath, &callBack, modeBase](const QString &entry) {
+ if (callBack.index() == 0)
+ return std::get<0>(callBack)(filePath.withNewPath(entry));
- const FilePathInfo fi = FileUtils::filePathInfoFromTriple(infos);
- if (!fi.fileFlags)
- return IterationPolicy::Continue;
+ const QString fileName = entry.mid(1, entry.lastIndexOf('\"') - 1);
+ const QString infos = entry.mid(fileName.length() + 3);
- const FilePath fp = filePath.withNewPath(fileName);
- // Do not return the entry for the directory we are searching in.
- if (fp.path() == filePath.path())
- return IterationPolicy::Continue;
- return std::get<1>(callBack)(fp, fi);
- };
+ const FilePathInfo fi = FileUtils::filePathInfoFromTriple(infos, modeBase);
+ if (!fi.fileFlags)
+ return IterationPolicy::Continue;
+
+ const FilePath fp = filePath.withNewPath(fileName);
+ // Do not return the entry for the directory we are searching in.
+ if (fp.path() == filePath.path())
+ return IterationPolicy::Continue;
+ return std::get<1>(callBack)(fp, fi);
+ };
// Remove the first line, this can be the directory we are searching in.
// as long as we do not specify "mindepth > 0"
diff --git a/src/libs/utils/devicefileaccess.h b/src/libs/utils/devicefileaccess.h
index 8fe8201e088..d57da462d2f 100644
--- a/src/libs/utils/devicefileaccess.h
+++ b/src/libs/utils/devicefileaccess.h
@@ -3,10 +3,13 @@
#pragma once
+#include "hostosinfo.h"
#include "utils_global.h"
#include "fileutils.h"
+class tst_unixdevicefileaccess; // For testing.
+
namespace Utils {
// Base class including dummy implementation usable as fallback.
@@ -19,6 +22,7 @@ public:
protected:
friend class FilePath;
+ friend class ::tst_unixdevicefileaccess; // For testing.
virtual QString mapToDevicePath(const QString &hostPath) const;
@@ -41,7 +45,6 @@ protected:
const FilePath &target) const;
virtual bool renameFile(const FilePath &filePath, const FilePath &target) const;
- virtual OsType osType(const FilePath &filePath) const;
virtual FilePath symLinkTarget(const FilePath &filePath) const;
virtual FilePathInfo filePathInfo(const FilePath &filePath) const;
virtual QDateTime lastModified(const FilePath &filePath) const;
@@ -68,20 +71,6 @@ protected:
const QByteArray &data,
qint64 offset) const;
- virtual void asyncFileContents(const FilePath &filePath,
- const Continuation<expected_str<QByteArray>> &cont,
- qint64 limit,
- qint64 offset) const;
-
- virtual void asyncWriteFileContents(const FilePath &filePath,
- const Continuation<expected_str<qint64>> &cont,
- const QByteArray &data,
- qint64 offset) const;
-
- virtual void asyncCopyFile(const FilePath &filePath,
- const Continuation<expected_str<void>> &cont,
- const FilePath &target) const;
-
virtual expected_str<FilePath> createTempFile(const FilePath &filePath);
};
@@ -110,7 +99,6 @@ protected:
expected_str<void> copyFile(const FilePath &filePath, const FilePath &target) const override;
bool renameFile(const FilePath &filePath, const FilePath &target) const override;
- OsType osType(const FilePath &filePath) const override;
FilePath symLinkTarget(const FilePath &filePath) const override;
FilePathInfo filePathInfo(const FilePath &filePath) const override;
QDateTime lastModified(const FilePath &filePath) const override;
@@ -169,7 +157,6 @@ protected:
bool renameFile(const FilePath &filePath, const FilePath &target) const override;
FilePathInfo filePathInfo(const FilePath &filePath) const override;
- OsType osType(const FilePath &filePath) const override;
FilePath symLinkTarget(const FilePath &filePath) const override;
QDateTime lastModified(const FilePath &filePath) const override;
QFile::Permissions permissions(const FilePath &filePath) const override;
@@ -204,6 +191,10 @@ private:
const FileFilter &filter,
QStringList *found) const;
+ QStringList statArgs(const FilePath &filePath,
+ const QString &linuxFormat,
+ const QString &macFormat) const;
+
mutable bool m_tryUseFind = true;
mutable std::optional<bool> m_hasMkTemp;
};
diff --git a/src/libs/utils/deviceshell.cpp b/src/libs/utils/deviceshell.cpp
index 18e816d38b2..35b2a9ad82d 100644
--- a/src/libs/utils/deviceshell.cpp
+++ b/src/libs/utils/deviceshell.cpp
@@ -97,6 +97,7 @@ RunResult DeviceShell::run(const CommandLine &cmd, const QByteArray &stdInData)
const RunResult errorResult{-1, {}, {}};
QTC_ASSERT(m_shellProcess, return errorResult);
+ QTC_ASSERT(m_shellProcess->isRunning(), return errorResult);
QTC_ASSERT(m_shellScriptState == State::Succeeded, return errorResult);
QMutexLocker lk(&m_commandMutex);
diff --git a/src/libs/utils/differ.cpp b/src/libs/utils/differ.cpp
index 538aa6657ab..9dc2ce8454e 100644
--- a/src/libs/utils/differ.cpp
+++ b/src/libs/utils/differ.cpp
@@ -14,11 +14,10 @@ publication by Neil Fraser: http://neil.fraser.name/writing/diff/
#include "utilstr.h"
#include <QList>
-#include <QRegularExpression>
-#include <QStringList>
#include <QMap>
#include <QPair>
-#include <QFutureInterfaceBase>
+#include <QRegularExpression>
+#include <QStringList>
namespace Utils {
@@ -937,10 +936,9 @@ QString Diff::toString() const
///////////////
-Differ::Differ(QFutureInterfaceBase *jobController)
- : m_jobController(jobController)
+Differ::Differ(const std::optional<QFuture<void>> &future)
+ : m_future(future)
{
-
}
QList<Diff> Differ::diff(const QString &text1, const QString &text2)
@@ -1075,7 +1073,7 @@ QList<Diff> Differ::diffMyers(const QString &text1, const QString &text2)
int kMinReverse = -D;
int kMaxReverse = D;
for (int d = 0; d <= D; d++) {
- if (m_jobController && m_jobController->isCanceled()) {
+ if (m_future && m_future->isCanceled()) {
delete [] forwardV;
delete [] reverseV;
return QList<Diff>();
@@ -1193,17 +1191,10 @@ QList<Diff> Differ::diffNonCharMode(const QString &text1, const QString &text2)
QString lastDelete;
QString lastInsert;
QList<Diff> newDiffList;
- if (m_jobController) {
- m_jobController->setProgressRange(0, diffList.count());
- m_jobController->setProgressValue(0);
- }
for (int i = 0; i <= diffList.count(); i++) {
- if (m_jobController) {
- if (m_jobController->isCanceled()) {
- m_currentDiffMode = diffMode;
- return QList<Diff>();
- }
- m_jobController->setProgressValue(i + 1);
+ if (m_future && m_future->isCanceled()) {
+ m_currentDiffMode = diffMode;
+ return {};
}
const Diff diffItem = i < diffList.count()
? diffList.at(i)
diff --git a/src/libs/utils/differ.h b/src/libs/utils/differ.h
index 62b8bc7ec46..dc6d74f30da 100644
--- a/src/libs/utils/differ.h
+++ b/src/libs/utils/differ.h
@@ -5,12 +5,14 @@
#include "utils_global.h"
+#include <QFuture>
#include <QString>
+#include <optional>
+
QT_BEGIN_NAMESPACE
template <class K, class T>
class QMap;
-class QFutureInterfaceBase;
QT_END_NAMESPACE
namespace Utils {
@@ -42,7 +44,7 @@ public:
WordMode,
LineMode
};
- Differ(QFutureInterfaceBase *jobController = nullptr);
+ Differ(const std::optional<QFuture<void>> &future = {});
QList<Diff> diff(const QString &text1, const QString &text2);
QList<Diff> unifiedDiff(const QString &text1, const QString &text2);
void setDiffMode(DiffMode mode);
@@ -90,7 +92,7 @@ private:
int subTextStart);
DiffMode m_diffMode = Differ::LineMode;
DiffMode m_currentDiffMode = Differ::LineMode;
- QFutureInterfaceBase *m_jobController = nullptr;
+ std::optional<QFuture<void>> m_future;
};
} // namespace Utils
diff --git a/src/libs/utils/dropsupport.cpp b/src/libs/utils/dropsupport.cpp
index 9d914e0f084..6def3fd1aff 100644
--- a/src/libs/utils/dropsupport.cpp
+++ b/src/libs/utils/dropsupport.cpp
@@ -40,10 +40,6 @@ static bool isFileDropMime(const QMimeData *d, QList<DropSupport::FileSpec> *fil
const QList<QUrl>::const_iterator cend = urls.constEnd();
for (QList<QUrl>::const_iterator it = urls.constBegin(); it != cend; ++it) {
QUrl url = *it;
-#ifdef Q_OS_OSX
- // for file drops from Finder, working around QTBUG-40449
- url = Internal::filePathUrl(url);
-#endif
const QString fileName = url.toLocalFile();
if (!fileName.isEmpty()) {
hasFiles = true;
diff --git a/src/libs/utils/environment.cpp b/src/libs/utils/environment.cpp
index c99293329c1..cacd60fcd6f 100644
--- a/src/libs/utils/environment.cpp
+++ b/src/libs/utils/environment.cpp
@@ -4,6 +4,7 @@
#include "environment.h"
#include "algorithm.h"
+#include "filepath.h"
#include "qtcassert.h"
#include <QDir>
@@ -18,70 +19,137 @@ Q_GLOBAL_STATIC_WITH_ARGS(Environment, staticSystemEnvironment,
(QProcessEnvironment::systemEnvironment().toStringList()))
Q_GLOBAL_STATIC(QVector<EnvironmentProvider>, environmentProviders)
+Environment::Environment()
+ : m_dict(HostOsInfo::hostOs())
+{}
+
+Environment::Environment(OsType osType)
+ : m_dict(osType)
+{}
+
+Environment::Environment(const QStringList &env, OsType osType)
+ : m_dict(osType)
+{
+ m_changeItems.append(NameValueDictionary(env, osType));
+}
+
+Environment::Environment(const NameValuePairs &nameValues)
+{
+ m_changeItems.append(NameValueDictionary(nameValues));
+}
+
+Environment::Environment(const NameValueDictionary &dict)
+{
+ m_changeItems.append(dict);
+}
+
NameValueItems Environment::diff(const Environment &other, bool checkAppendPrepend) const
{
- return m_dict.diff(other.m_dict, checkAppendPrepend);
+ const NameValueDictionary &dict = resolved();
+ const NameValueDictionary &otherDict = other.resolved();
+ return dict.diff(otherDict, checkAppendPrepend);
+}
+
+Environment::FindResult Environment::find(const QString &name) const
+{
+ const NameValueDictionary &dict = resolved();
+ const auto it = dict.constFind(name);
+ if (it == dict.constEnd())
+ return {};
+ return Entry{it.key().name, it.value().first, it.value().second};
+}
+
+void Environment::forEachEntry(const std::function<void(const QString &, const QString &, bool)> &callBack) const
+{
+ const NameValueDictionary &dict = resolved();
+ for (auto it = dict.m_values.constBegin(); it != dict.m_values.constEnd(); ++it)
+ callBack(it.key().name, it.value().first, it.value().second);
+}
+
+bool Environment::operator==(const Environment &other) const
+{
+ const NameValueDictionary &dict = resolved();
+ const NameValueDictionary &otherDict = other.resolved();
+ return dict == otherDict;
+}
+
+bool Environment::operator!=(const Environment &other) const
+{
+ const NameValueDictionary &dict = resolved();
+ const NameValueDictionary &otherDict = other.resolved();
+ return dict != otherDict;
+}
+
+QString Environment::value(const QString &key) const
+{
+ const NameValueDictionary &dict = resolved();
+ return dict.value(key);
+}
+
+QString Environment::value_or(const QString &key, const QString &defaultValue) const
+{
+ const NameValueDictionary &dict = resolved();
+ return dict.hasKey(key) ? dict.value(key) : defaultValue;
+}
+
+bool Environment::hasKey(const QString &key) const
+{
+ const NameValueDictionary &dict = resolved();
+ return dict.hasKey(key);
}
bool Environment::hasChanges() const
{
- return m_dict.size() != 0;
+ const NameValueDictionary &dict = resolved();
+ return dict.size() != 0;
+}
+
+OsType Environment::osType() const
+{
+ return m_dict.m_osType;
+}
+
+QStringList Environment::toStringList() const
+{
+ const NameValueDictionary &dict = resolved();
+ return dict.toStringList();
}
QProcessEnvironment Environment::toProcessEnvironment() const
{
+ const NameValueDictionary &dict = resolved();
QProcessEnvironment result;
- for (auto it = m_dict.m_values.constBegin(); it != m_dict.m_values.constEnd(); ++it) {
+ for (auto it = dict.m_values.constBegin(); it != dict.m_values.constEnd(); ++it) {
if (it.value().second)
- result.insert(it.key().name, expandedValueForKey(key(it)));
+ result.insert(it.key().name, expandedValueForKey(dict.key(it)));
}
return result;
}
void Environment::appendOrSetPath(const FilePath &value)
{
- QTC_CHECK(value.osType() == osType());
+ QTC_CHECK(value.osType() == m_dict.m_osType);
if (value.isEmpty())
return;
- appendOrSet("PATH", value.nativePath(),
- QString(OsSpecificAspects::pathListSeparator(osType())));
+ appendOrSet("PATH", value.nativePath(), OsSpecificAspects::pathListSeparator(osType()));
}
void Environment::prependOrSetPath(const FilePath &value)
{
- QTC_CHECK(value.osType() == osType());
+ QTC_CHECK(value.osType() == m_dict.m_osType);
if (value.isEmpty())
return;
- prependOrSet("PATH", value.nativePath(),
- QString(OsSpecificAspects::pathListSeparator(osType())));
+ prependOrSet("PATH", value.nativePath(), OsSpecificAspects::pathListSeparator(osType()));
}
void Environment::appendOrSet(const QString &key, const QString &value, const QString &sep)
{
- QTC_ASSERT(!key.contains('='), return );
- const auto it = m_dict.findKey(key);
- if (it == m_dict.m_values.end()) {
- m_dict.m_values.insert(DictKey(key, m_dict.nameCaseSensitivity()), {value, true});
- } else {
- // Append unless it is already there
- const QString toAppend = sep + value;
- if (!it.value().first.endsWith(toAppend))
- it.value().first.append(toAppend);
- }
+ addItem(Item{std::in_place_index_t<AppendOrSet>(), key, value, sep});
}
void Environment::prependOrSet(const QString &key, const QString &value, const QString &sep)
{
- QTC_ASSERT(!key.contains('='), return );
- const auto it = m_dict.findKey(key);
- if (it == m_dict.m_values.end()) {
- m_dict.m_values.insert(DictKey(key, m_dict.nameCaseSensitivity()), {value, true});
- } else {
- // Prepend unless it is already there
- const QString toPrepend = value + sep;
- if (!it.value().first.startsWith(toPrepend))
- it.value().first.prepend(toPrepend);
- }
+ addItem(Item{std::in_place_index_t<PrependOrSet>(), key, value, sep});
}
void Environment::prependOrSetLibrarySearchPath(const FilePath &value)
@@ -90,11 +158,11 @@ void Environment::prependOrSetLibrarySearchPath(const FilePath &value)
switch (osType()) {
case OsTypeWindows: {
const QChar sep = ';';
- prependOrSet("PATH", value.nativePath(), QString(sep));
+ prependOrSet("PATH", value.nativePath(), sep);
break;
}
case OsTypeMac: {
- const QString sep = ":";
+ const QChar sep = ':';
const QString nativeValue = value.nativePath();
prependOrSet("DYLD_LIBRARY_PATH", nativeValue, sep);
prependOrSet("DYLD_FRAMEWORK_PATH", nativeValue, sep);
@@ -103,7 +171,7 @@ void Environment::prependOrSetLibrarySearchPath(const FilePath &value)
case OsTypeLinux:
case OsTypeOtherUnix: {
const QChar sep = ':';
- prependOrSet("LD_LIBRARY_PATH", value.nativePath(), QString(sep));
+ prependOrSet("LD_LIBRARY_PATH", value.nativePath(), sep);
break;
}
default:
@@ -126,40 +194,47 @@ Environment Environment::systemEnvironment()
void Environment::setupEnglishOutput()
{
- m_dict.set("LC_MESSAGES", "en_US.utf8");
- m_dict.set("LANGUAGE", "en_US:en");
+ addItem(Item{std::in_place_index_t<SetupEnglishOutput>()});
}
-static FilePath searchInDirectory(const QStringList &execs,
- const FilePath &directory,
- QSet<FilePath> &alreadyChecked)
+using SearchResultCallback = std::function<IterationPolicy(const FilePath &)>;
+
+static IterationPolicy searchInDirectory(const SearchResultCallback &resultCallback,
+ const FilePaths &execs,
+ const FilePath &directory,
+ QSet<FilePath> &alreadyCheckedDirectories,
+ const FilePathPredicate &filter = {})
{
- const int checkedCount = alreadyChecked.count();
- alreadyChecked.insert(directory);
+ // Compare the initial size of the set with the size after insertion to check if the directory
+ // was already checked.
+ const int initialCount = alreadyCheckedDirectories.count();
+ alreadyCheckedDirectories.insert(directory);
+ const bool wasAlreadyChecked = alreadyCheckedDirectories.count() == initialCount;
- if (directory.isEmpty() || alreadyChecked.count() == checkedCount)
- return FilePath();
+ if (directory.isEmpty() || wasAlreadyChecked)
+ return IterationPolicy::Continue;
- for (const QString &exec : execs) {
- const FilePath filePath = directory.pathAppended(exec);
- if (filePath.isExecutableFile())
- return filePath;
+ for (const FilePath &exec : execs) {
+ const FilePath filePath = directory / exec.path();
+ if (filePath.isExecutableFile() && (!filter || filter(filePath))) {
+ if (resultCallback(filePath) == IterationPolicy::Stop)
+ return IterationPolicy::Stop;
+ }
}
- return FilePath();
+ return IterationPolicy::Continue;
}
-static QStringList appendExeExtensions(const Environment &env, const QString &executable)
+static FilePaths appendExeExtensions(const Environment &env, const FilePath &executable)
{
- QStringList execs(executable);
+ FilePaths execs{executable};
if (env.osType() == OsTypeWindows) {
- const QFileInfo fi(executable);
// Check all the executable extensions on windows:
// PATHEXT is only used if the executable has no extension
- if (fi.suffix().isEmpty()) {
+ if (executable.suffix().isEmpty()) {
const QStringList extensions = env.expandedValueForKey("PATHEXT").split(';');
for (const QString &ext : extensions)
- execs << executable + ext.toLower();
+ execs << executable.stringAppended(ext.toLower());
}
}
return execs;
@@ -167,102 +242,105 @@ static QStringList appendExeExtensions(const Environment &env, const QString &ex
QString Environment::expandedValueForKey(const QString &key) const
{
- return expandVariables(m_dict.value(key));
+ const NameValueDictionary &dict = resolved();
+ return expandVariables(dict.value(key));
}
-static FilePath searchInDirectoriesHelper(const Environment &env,
- const QString &executable,
- const FilePaths &dirs,
- const Environment::PathFilter &func,
- bool usePath)
+static void searchInDirectoriesHelper(const SearchResultCallback &resultCallback,
+ const Environment &env,
+ const QString &executable,
+ const FilePaths &dirs,
+ const FilePathPredicate &func,
+ bool usePath)
{
if (executable.isEmpty())
- return FilePath();
-
- const QString exec = QDir::cleanPath(env.expandVariables(executable));
- const QFileInfo fi(exec);
+ return;
- const QStringList execs = appendExeExtensions(env, exec);
+ const FilePath exec = FilePath::fromUserInput(QDir::cleanPath(env.expandVariables(executable)));
+ const FilePaths execs = appendExeExtensions(env, exec);
- if (fi.isAbsolute()) {
- for (const QString &path : execs) {
- QFileInfo pfi = QFileInfo(path);
- if (pfi.isFile() && pfi.isExecutable())
- return FilePath::fromString(path);
+ if (exec.isAbsolutePath()) {
+ for (const FilePath &path : execs) {
+ if (path.isExecutableFile() && (!func || func(path)))
+ if (resultCallback(path) == IterationPolicy::Stop)
+ return;
}
- return FilePath::fromString(exec);
+ return;
}
- QSet<FilePath> alreadyChecked;
+ QSet<FilePath> alreadyCheckedDirectories;
for (const FilePath &dir : dirs) {
- FilePath tmp = searchInDirectory(execs, dir, alreadyChecked);
- if (!tmp.isEmpty() && (!func || func(tmp)))
- return tmp;
+ if (searchInDirectory(resultCallback, execs, dir, alreadyCheckedDirectories, func)
+ == IterationPolicy::Stop)
+ return;
}
if (usePath) {
- if (executable.contains('/'))
- return FilePath();
+ QTC_ASSERT(!executable.contains('/'), return);
for (const FilePath &p : env.path()) {
- FilePath tmp = searchInDirectory(execs, p, alreadyChecked);
- if (!tmp.isEmpty() && (!func || func(tmp)))
- return tmp;
+ if (searchInDirectory(resultCallback, execs, p, alreadyCheckedDirectories, func)
+ == IterationPolicy::Stop)
+ return;
}
}
- return FilePath();
+ return;
}
FilePath Environment::searchInDirectories(const QString &executable,
const FilePaths &dirs,
- const PathFilter &func) const
-{
- return searchInDirectoriesHelper(*this, executable, dirs, func, false);
+ const FilePathPredicate &func) const
+{
+ FilePath result;
+ searchInDirectoriesHelper(
+ [&result](const FilePath &path) {
+ result = path;
+ return IterationPolicy::Stop;
+ },
+ *this,
+ executable,
+ dirs,
+ func,
+ false);
+
+ return result;
}
FilePath Environment::searchInPath(const QString &executable,
const FilePaths &additionalDirs,
- const PathFilter &func) const
-{
- return searchInDirectoriesHelper(*this, executable, additionalDirs, func, true);
+ const FilePathPredicate &func) const
+{
+ FilePath result;
+ searchInDirectoriesHelper(
+ [&result](const FilePath &path) {
+ result = path;
+ return IterationPolicy::Stop;
+ },
+ *this,
+ executable,
+ additionalDirs,
+ func,
+ true);
+
+ return result;
}
FilePaths Environment::findAllInPath(const QString &executable,
- const FilePaths &additionalDirs,
- const Environment::PathFilter &func) const
+ const FilePaths &additionalDirs,
+ const FilePathPredicate &func) const
{
- if (executable.isEmpty())
- return {};
-
- const QString exec = QDir::cleanPath(expandVariables(executable));
- const QFileInfo fi(exec);
-
- const QStringList execs = appendExeExtensions(*this, exec);
-
- if (fi.isAbsolute()) {
- for (const QString &path : execs) {
- QFileInfo pfi = QFileInfo(path);
- if (pfi.isFile() && pfi.isExecutable())
- return {FilePath::fromString(path)};
- }
- return {FilePath::fromString(exec)};
- }
-
QSet<FilePath> result;
- QSet<FilePath> alreadyChecked;
- for (const FilePath &dir : additionalDirs) {
- FilePath tmp = searchInDirectory(execs, dir, alreadyChecked);
- if (!tmp.isEmpty() && (!func || func(tmp)))
- result << tmp;
- }
+ searchInDirectoriesHelper(
+ [&result](const FilePath &path) {
+ result.insert(path);
+ return IterationPolicy::Continue;
+ },
+ *this,
+ executable,
+ additionalDirs,
+ func,
+ true);
- if (!executable.contains('/')) {
- for (const FilePath &p : path()) {
- FilePath tmp = searchInDirectory(execs, p, alreadyChecked);
- if (!tmp.isEmpty() && (!func || func(tmp)))
- result << tmp;
- }
- }
return result.values();
}
@@ -299,14 +377,16 @@ void Environment::setSystemEnvironment(const Environment &environment)
*/
QString Environment::expandVariables(const QString &input) const
{
+ const NameValueDictionary &dict = resolved();
+
QString result = input;
if (osType() == OsTypeWindows) {
for (int vStart = -1, i = 0; i < result.length(); ) {
if (result.at(i++) == '%') {
if (vStart > 0) {
- const auto it = m_dict.findKey(result.mid(vStart, i - vStart - 1));
- if (it != m_dict.m_values.constEnd()) {
+ const auto it = dict.findKey(result.mid(vStart, i - vStart - 1));
+ if (it != dict.m_values.constEnd()) {
result.replace(vStart - 1, i - vStart + 1, it->first);
i = vStart - 1 + it->first.length();
vStart = -1;
@@ -339,28 +419,30 @@ QString Environment::expandVariables(const QString &input) const
}
} else if (state == BRACEDVARIABLE) {
if (c == '}') {
- const_iterator it = constFind(result.mid(vStart, i - 1 - vStart));
- if (it != constEnd()) {
- result.replace(vStart - 2, i - vStart + 2, it->first);
- i = vStart - 2 + it->first.length();
+ const QString key = result.mid(vStart, i - 1 - vStart);
+ const Environment::FindResult res = find(key);
+ if (res) {
+ result.replace(vStart - 2, i - vStart + 2, res->value);
+ i = vStart - 2 + res->value.length();
}
state = BASE;
}
} else if (state == VARIABLE) {
if (!c.isLetterOrNumber() && c != '_') {
- const_iterator it = constFind(result.mid(vStart, i - vStart - 1));
- if (it != constEnd()) {
- result.replace(vStart - 1, i - vStart, it->first);
- i = vStart - 1 + it->first.length();
+ const QString key = result.mid(vStart, i - vStart - 1);
+ const Environment::FindResult res = find(key);
+ if (res) {
+ result.replace(vStart - 1, i - vStart, res->value);
+ i = vStart - 1 + res->value.length();
}
state = BASE;
}
}
}
if (state == VARIABLE) {
- const_iterator it = constFind(result.mid(vStart));
- if (it != constEnd())
- result.replace(vStart - 1, result.length() - vStart + 1, it->first);
+ const Environment::FindResult res = find(result.mid(vStart));
+ if (res)
+ result.replace(vStart - 1, result.length() - vStart + 1, res->value);
}
}
return result;
@@ -376,6 +458,12 @@ QStringList Environment::expandVariables(const QStringList &variables) const
return transform(variables, [this](const QString &i) { return expandVariables(i); });
}
+NameValueDictionary Environment::toDictionary() const
+{
+ const NameValueDictionary &dict = resolved();
+ return dict;
+}
+
void EnvironmentProvider::addProvider(EnvironmentProvider &&provider)
{
environmentProviders->append(std::move(provider));
@@ -394,63 +482,145 @@ std::optional<EnvironmentProvider> EnvironmentProvider::provider(const QByteArra
return std::nullopt;
}
-void EnvironmentChange::addSetValue(const QString &key, const QString &value)
+void Environment::addItem(const Item &item)
{
- m_changeItems.append(Item{std::in_place_index_t<SetValue>(), QPair<QString, QString>{key, value}});
+ m_dict.clear();
+ m_changeItems.append(item);
}
-void EnvironmentChange::addUnsetValue(const QString &key)
+void Environment::set(const QString &key, const QString &value, bool enabled)
{
- m_changeItems.append(Item{std::in_place_index_t<UnsetValue>(), key});
+ addItem(Item{std::in_place_index_t<SetValue>(),
+ std::tuple<QString, QString, bool>{key, value, enabled}});
}
-void EnvironmentChange::addPrependToPath(const FilePaths &values)
+void Environment::setFallback(const QString &key, const QString &value)
{
+ addItem(Item{std::in_place_index_t<SetFallbackValue>(),
+ std::tuple<QString, QString>{key, value}});
+}
+
+void Environment::unset(const QString &key)
+{
+ addItem(Item{std::in_place_index_t<UnsetValue>(), key});
+}
+
+void Environment::modify(const NameValueItems &items)
+{
+ addItem(Item{std::in_place_index_t<Modify>(), items});
+}
+
+void Environment::prependToPath(const FilePaths &values)
+{
+ m_dict.clear();
for (int i = values.size(); --i >= 0; ) {
const FilePath value = values.at(i);
- m_changeItems.append(Item{std::in_place_index_t<PrependToPath>(), value});
+ m_changeItems.append(Item{
+ std::in_place_index_t<PrependOrSet>(),
+ QString("PATH"),
+ value.nativePath(),
+ value.pathListSeparator()
+ });
}
}
-void EnvironmentChange::addAppendToPath(const FilePaths &values)
+void Environment::appendToPath(const FilePaths &values)
{
- for (const FilePath &value : values)
- m_changeItems.append(Item{std::in_place_index_t<AppendToPath>(), value});
+ m_dict.clear();
+ for (const FilePath &value : values) {
+ m_changeItems.append(Item{
+ std::in_place_index_t<AppendOrSet>(),
+ QString("PATH"),
+ value.nativePath(),
+ value.pathListSeparator()
+ });
+ }
}
-EnvironmentChange EnvironmentChange::fromDictionary(const NameValueDictionary &dict)
+const NameValueDictionary &Environment::resolved() const
{
- EnvironmentChange change;
- change.m_changeItems.append(Item{std::in_place_index_t<SetFixedDictionary>(), dict});
- return change;
-}
+ if (m_dict.size() != 0)
+ return m_dict;
-void EnvironmentChange::applyToEnvironment(Environment &env) const
-{
+ m_fullDict = false;
for (const Item &item : m_changeItems) {
switch (item.index()) {
case SetSystemEnvironment:
- env = Environment::systemEnvironment();
+ m_dict = Environment::systemEnvironment().toDictionary();
+ m_fullDict = true;
break;
case SetFixedDictionary:
- env = Environment(std::get<SetFixedDictionary>(item));
+ m_dict = std::get<SetFixedDictionary>(item);
+ m_fullDict = true;
break;
case SetValue: {
- const QPair<QString, QString> data = std::get<SetValue>(item);
- env.set(data.first, data.second);
+ auto [key, value, enabled] = std::get<SetValue>(item);
+ m_dict.set(key, value, enabled);
+ break;
+ }
+ case SetFallbackValue: {
+ auto [key, value] = std::get<SetFallbackValue>(item);
+ if (m_fullDict) {
+ if (m_dict.value(key).isEmpty())
+ m_dict.set(key, value, true);
+ } else {
+ QTC_ASSERT(false, qDebug() << "operating on partial dictionary");
+ m_dict.set(key, value, true);
+ }
break;
}
case UnsetValue:
- env.unset(std::get<UnsetValue>(item));
+ m_dict.unset(std::get<UnsetValue>(item));
+ break;
+ case PrependOrSet: {
+ auto [key, value, sep] = std::get<PrependOrSet>(item);
+ QTC_ASSERT(!key.contains('='), return m_dict);
+ const auto it = m_dict.findKey(key);
+ if (it == m_dict.m_values.end()) {
+ m_dict.m_values.insert(DictKey(key, m_dict.nameCaseSensitivity()), {value, true});
+ } else {
+ // Prepend unless it is already there
+ const QString toPrepend = value + sep;
+ if (!it.value().first.startsWith(toPrepend))
+ it.value().first.prepend(toPrepend);
+ }
+ break;
+ }
+ case AppendOrSet: {
+ auto [key, value, sep] = std::get<AppendOrSet>(item);
+ QTC_ASSERT(!key.contains('='), return m_dict);
+ const auto it = m_dict.findKey(key);
+ if (it == m_dict.m_values.end()) {
+ m_dict.m_values.insert(DictKey(key, m_dict.nameCaseSensitivity()), {value, true});
+ } else {
+ // Prepend unless it is already there
+ const QString toAppend = sep + value;
+ if (!it.value().first.endsWith(toAppend))
+ it.value().first.append(toAppend);
+ }
break;
- case PrependToPath:
- env.prependOrSetPath(std::get<PrependToPath>(item));
+ }
+ case Modify: {
+ NameValueItems items = std::get<Modify>(item);
+ m_dict.modify(items);
break;
- case AppendToPath:
- env.appendOrSetPath(std::get<AppendToPath>(item));
+ }
+ case SetupEnglishOutput:
+ m_dict.set("LC_MESSAGES", "en_US.utf8");
+ m_dict.set("LANGUAGE", "en_US:en");
break;
}
}
+
+ return m_dict;
+}
+
+Environment Environment::appliedToEnvironment(const Environment &base) const
+{
+ Environment res = base;
+ res.m_dict.clear();
+ res.m_changeItems.append(m_changeItems);
+ return res;
}
/*!
diff --git a/src/libs/utils/environment.h b/src/libs/utils/environment.h
index 2671e972cf3..7afa48ba0bb 100644
--- a/src/libs/utils/environment.h
+++ b/src/libs/utils/environment.h
@@ -8,6 +8,7 @@
#include "environmentfwd.h"
#include "filepath.h"
#include "namevaluedictionary.h"
+#include "utiltypes.h"
#include <functional>
#include <optional>
@@ -21,28 +22,25 @@ namespace Utils {
class QTCREATOR_UTILS_EXPORT Environment final
{
public:
- Environment() : m_dict(HostOsInfo::hostOs()) {}
- explicit Environment(OsType osType) : m_dict(osType) {}
- explicit Environment(const QStringList &env, OsType osType = HostOsInfo::hostOs())
- : m_dict(env, osType) {}
- explicit Environment(const NameValuePairs &nameValues) : m_dict(nameValues) {}
- explicit Environment(const NameValueDictionary &dict) : m_dict(dict) {}
-
- QString value(const QString &key) const { return m_dict.value(key); }
- QString value_or(const QString &key, const QString &defaultValue) const
- {
- return m_dict.hasKey(key) ? m_dict.value(key) : defaultValue;
- }
- bool hasKey(const QString &key) const { return m_dict.hasKey(key); }
-
- void set(const QString &key, const QString &value, bool enabled = true) { m_dict.set(key, value, enabled); }
- void unset(const QString &key) { m_dict.unset(key); }
- void modify(const NameValueItems &items) { m_dict.modify(items); }
+ Environment();
+ explicit Environment(OsType osType);
+ explicit Environment(const QStringList &env, OsType osType = HostOsInfo::hostOs());
+ explicit Environment(const NameValuePairs &nameValues);
+ explicit Environment(const NameValueDictionary &dict);
+
+ QString value(const QString &key) const;
+ QString value_or(const QString &key, const QString &defaultValue) const;
+ bool hasKey(const QString &key) const;
+
+ void set(const QString &key, const QString &value, bool enabled = true);
+ void setFallback(const QString &key, const QString &value);
+ void unset(const QString &key);
+ void modify(const NameValueItems &items);
bool hasChanges() const;
- void clear() { return m_dict.clear(); }
- QStringList toStringList() const { return m_dict.toStringList(); }
+ OsType osType() const;
+ QStringList toStringList() const;
QProcessEnvironment toProcessEnvironment() const;
void appendOrSet(const QString &key, const QString &value, const QString &sep = QString());
@@ -54,18 +52,21 @@ public:
void prependOrSetLibrarySearchPath(const FilePath &value);
void prependOrSetLibrarySearchPaths(const FilePaths &values);
+ void prependToPath(const FilePaths &values);
+ void appendToPath(const FilePaths &values);
+
void setupEnglishOutput();
+ void setupSudoAskPass(const FilePath &askPass);
- using PathFilter = std::function<bool(const FilePath &)>;
FilePath searchInPath(const QString &executable,
const FilePaths &additionalDirs = FilePaths(),
- const PathFilter &func = PathFilter()) const;
+ const FilePathPredicate &func = {}) const;
FilePath searchInDirectories(const QString &executable,
const FilePaths &dirs,
- const PathFilter &func = {}) const;
+ const FilePathPredicate &func = {}) const;
FilePaths findAllInPath(const QString &executable,
const FilePaths &additionalDirs = {},
- const PathFilter &func = {}) const;
+ const FilePathPredicate &func = {}) const;
FilePaths path() const;
FilePaths pathListValue(const QString &varName) const;
@@ -75,80 +76,62 @@ public:
FilePath expandVariables(const FilePath &input) const;
QStringList expandVariables(const QStringList &input) const;
- OsType osType() const { return m_dict.osType(); }
- QString userName() const;
-
- using const_iterator = NameValueMap::const_iterator; // FIXME: avoid
- NameValueDictionary toDictionary() const { return m_dict; } // FIXME: avoid
+ NameValueDictionary toDictionary() const; // FIXME: avoid
NameValueItems diff(const Environment &other, bool checkAppendPrepend = false) const; // FIXME: avoid
- QString key(const_iterator it) const { return m_dict.key(it); } // FIXME: avoid
- QString value(const_iterator it) const { return m_dict.value(it); } // FIXME: avoid
- bool isEnabled(const_iterator it) const { return m_dict.isEnabled(it); } // FIXME: avoid
+ struct Entry { QString key; QString value; bool enabled; };
+ using FindResult = std::optional<Entry>;
+ FindResult find(const QString &name) const; // Note res->key may differ in case from name.
- void setCombineWithDeviceEnvironment(bool combine) { m_combineWithDeviceEnvironment = combine; }
- bool combineWithDeviceEnvironment() const { return m_combineWithDeviceEnvironment; }
+ void forEachEntry(const std::function<void (const QString &, const QString &, bool)> &callBack) const;
- const_iterator constBegin() const { return m_dict.constBegin(); } // FIXME: avoid
- const_iterator constEnd() const { return m_dict.constEnd(); } // FIXME: avoid
- const_iterator constFind(const QString &name) const { return m_dict.constFind(name); } // FIXME: avoid
-
- friend bool operator!=(const Environment &first, const Environment &second)
- {
- return first.m_dict != second.m_dict;
- }
-
- friend bool operator==(const Environment &first, const Environment &second)
- {
- return first.m_dict == second.m_dict;
- }
+ bool operator!=(const Environment &other) const;
+ bool operator==(const Environment &other) const;
static Environment systemEnvironment();
static void modifySystemEnvironment(const EnvironmentItems &list); // use with care!!!
static void setSystemEnvironment(const Environment &environment); // don't use at all!!!
-private:
- NameValueDictionary m_dict;
- bool m_combineWithDeviceEnvironment = true;
-};
-
-class QTCREATOR_UTILS_EXPORT EnvironmentChange final
-{
-public:
- EnvironmentChange() = default;
-
enum Type {
SetSystemEnvironment,
SetFixedDictionary,
SetValue,
+ SetFallbackValue,
UnsetValue,
- PrependToPath,
- AppendToPath,
+ PrependOrSet,
+ AppendOrSet,
+ Modify,
+ SetupEnglishOutput
};
using Item = std::variant<
- std::monostate, // SetSystemEnvironment dummy
- NameValueDictionary, // SetFixedDictionary
- QPair<QString, QString>, // SetValue
- QString, // UnsetValue
- FilePath, // PrependToPath
- FilePath // AppendToPath
+ std::monostate, // SetSystemEnvironment dummy
+ NameValueDictionary, // SetFixedDictionary
+ std::tuple<QString, QString, bool>, // SetValue (key, value, enabled)
+ std::tuple<QString, QString>, // SetFallbackValue (key, value)
+ QString, // UnsetValue (key)
+ std::tuple<QString, QString, QString>, // PrependOrSet (key, value, separator)
+ std::tuple<QString, QString, QString>, // AppendOrSet (key, value, separator)
+ NameValueItems, // Modify
+ std::monostate, // SetupEnglishOutput
+ FilePath // SetupSudoAskPass (file path of qtc-askpass or ssh-askpass)
>;
- static EnvironmentChange fromDictionary(const NameValueDictionary &dict);
+ void addItem(const Item &item);
- void applyToEnvironment(Environment &) const;
+ Environment appliedToEnvironment(const Environment &base) const;
- void addSetValue(const QString &key, const QString &value);
- void addUnsetValue(const QString &key);
- void addPrependToPath(const FilePaths &values);
- void addAppendToPath(const FilePaths &values);
+ const NameValueDictionary &resolved() const;
private:
- QList<Item> m_changeItems;
+ mutable QList<Item> m_changeItems;
+ mutable NameValueDictionary m_dict; // Latest resolved.
+ mutable bool m_fullDict = false;
};
+using EnviromentChange = Environment;
+
class QTCREATOR_UTILS_EXPORT EnvironmentProvider
{
public:
diff --git a/src/libs/utils/filepath.cpp b/src/libs/utils/filepath.cpp
index 230dc251cbc..9b97fb6b028 100644
--- a/src/libs/utils/filepath.cpp
+++ b/src/libs/utils/filepath.cpp
@@ -6,6 +6,7 @@
#include "algorithm.h"
#include "devicefileaccess.h"
#include "environment.h"
+#include "filestreamermanager.h"
#include "fileutils.h"
#include "hostosinfo.h"
#include "qtcassert.h"
@@ -162,7 +163,7 @@ FilePath FilePath::fromFileInfo(const QFileInfo &info)
}
/*!
- \returns a QFileInfo
+ Returns a QFileInfo.
*/
QFileInfo FilePath::toFileInfo() const
{
@@ -214,7 +215,7 @@ QString decodeHost(QString host)
}
/*!
- \returns a QString for passing through QString based APIs
+ Returns a QString for passing through QString based APIs.
\note This is obsolete API and should be replaced by extended use
of proper \c FilePath, or, in case this is not possible by \c toFSPathString().
@@ -233,13 +234,16 @@ QString FilePath::toString() const
if (!needsDevice())
return path();
+ if (pathView().isEmpty())
+ return scheme() + "://" + encodedHost();
+
if (isRelativePath())
return scheme() + "://" + encodedHost() + "/./" + pathView();
return scheme() + "://" + encodedHost() + pathView();
}
/*!
- \returns a QString for passing on to QString based APIs
+ Returns a QString for passing on to QString based APIs.
This uses a /__qtc_devices__/host/path setup.
@@ -255,6 +259,9 @@ QString FilePath::toFSPathString() const
if (scheme().isEmpty())
return path();
+ if (pathView().isEmpty())
+ return specialRootPath() + '/' + scheme() + '/' + encodedHost();
+
if (isRelativePath())
return specialRootPath() + '/' + scheme() + '/' + encodedHost() + "/./" + pathView();
return specialRootPath() + '/' + scheme() + '/' + encodedHost() + pathView();
@@ -289,7 +296,7 @@ QString FilePath::toUserOutput() const
}
/*!
- \returns a QString to pass to target system native commands, without the device prefix.
+ Returns a QString to pass to target system native commands, without the device prefix.
Converts the separators to the native format of the system
this path belongs to.
@@ -343,7 +350,7 @@ QString FilePath::fileNameWithPathComponents(int pathComponents) const
}
/*!
- \returns the base name of the file without the path.
+ Returns the base name of the file without the path.
The base name consists of all characters in the file up to
(but not including) the first '.' character.
@@ -355,7 +362,7 @@ QString FilePath::baseName() const
}
/*!
- \returns the complete base name of the file without the path.
+ Returns the complete base name of the file without the path.
The complete base name consists of all characters in the file up to
(but not including) the last '.' character. In case of ".ui.qml"
@@ -370,7 +377,7 @@ QString FilePath::completeBaseName() const
}
/*!
- \returns the suffix (extension) of the file.
+ Returns the suffix (extension) of the file.
The suffix consists of all characters in the file after
(but not including) the last '.'. In case of ".ui.qml" it will
@@ -393,7 +400,7 @@ QString FilePath::suffix() const
}
/*!
- \returns the complete suffix (extension) of the file.
+ Returns the complete suffix (extension) of the file.
The complete suffix consists of all characters in the file after
(but not including) the first '.'.
@@ -441,7 +448,7 @@ void FilePath::setParts(const QStringView scheme, const QStringView host, QStrin
}
/*!
- \returns a bool indicating whether a file or directory with this FilePath exists.
+ Returns a bool indicating whether a file or directory with this FilePath exists.
*/
bool FilePath::exists() const
{
@@ -449,7 +456,7 @@ bool FilePath::exists() const
}
/*!
- \returns a bool indicating whether this is a writable directory.
+ Returns a bool indicating whether this is a writable directory.
*/
bool FilePath::isWritableDir() const
{
@@ -457,13 +464,20 @@ bool FilePath::isWritableDir() const
}
/*!
- \returns a bool indicating whether this is a writable file.
+ Returns a bool indicating whether this is a writable file.
*/
bool FilePath::isWritableFile() const
{
return fileAccess()->isWritableFile(*this);
}
+/*!
+ \brief Re-uses or creates a directory in this location.
+
+ Returns true if the directory is writable afterwards.
+
+ \sa createDir()
+*/
bool FilePath::ensureWritableDir() const
{
return fileAccess()->ensureWritableDirectory(*this);
@@ -480,7 +494,7 @@ bool FilePath::isExecutableFile() const
}
/*!
- \returns a bool indicating on whether a process with this FilePath's
+ Returns a bool indicating on whether a process with this FilePath's
.nativePath() is likely to start.
This is equivalent to \c isExecutableFile() in general.
@@ -550,6 +564,14 @@ bool FilePath::isSymLink() const
return fileAccess()->isSymLink(*this);
}
+/*!
+ \brief Creates a directory in this location.
+
+ Returns true if the directory could be created, false if not,
+ even if it existed before.
+
+ \sa ensureWriteableDir()
+*/
bool FilePath::createDir() const
{
return fileAccess()->createDirectory(*this);
@@ -620,13 +642,6 @@ bool FilePath::ensureReachable(const FilePath &other) const
return false;
}
-void FilePath::asyncFileContents(const Continuation<const expected_str<QByteArray> &> &cont,
- qint64 maxSize,
- qint64 offset) const
-{
- return fileAccess()->asyncFileContents(*this, cont, maxSize, offset);
-}
-
expected_str<qint64> FilePath::writeFileContents(const QByteArray &data, qint64 offset) const
{
return fileAccess()->writeFileContents(*this, data, offset);
@@ -637,11 +652,21 @@ FilePathInfo FilePath::filePathInfo() const
return fileAccess()->filePathInfo(*this);
}
-void FilePath::asyncWriteFileContents(const Continuation<const expected_str<qint64> &> &cont,
- const QByteArray &data,
- qint64 offset) const
+FileStreamHandle FilePath::asyncCopy(const FilePath &target, QObject *context,
+ const CopyContinuation &cont) const
+{
+ return FileStreamerManager::copy(*this, target, context, cont);
+}
+
+FileStreamHandle FilePath::asyncRead(QObject *context, const ReadContinuation &cont) const
{
- return fileAccess()->asyncWriteFileContents(*this, cont, data, offset);
+ return FileStreamerManager::read(*this, context, cont);
+}
+
+FileStreamHandle FilePath::asyncWrite(const QByteArray &data, QObject *context,
+ const WriteContinuation &cont) const
+{
+ return FileStreamerManager::write(*this, data, context, cont);
}
bool FilePath::needsDevice() const
@@ -716,7 +741,7 @@ bool FilePath::isSameExecutable(const FilePath &other) const
}
/*!
- \returns an empty FilePath if this is not a symbolic linl
+ Returns an empty FilePath if this is not a symbolic link.
*/
FilePath FilePath::symLinkTarget() const
{
@@ -774,13 +799,153 @@ int FilePath::schemeAndHostLength(const QStringView path)
return pos + 1; // scheme://host/ plus something
}
+static QString normalizePathSegmentHelper(const QString &name)
+{
+ const int len = name.length();
+
+ if (len == 0 || name.contains("%{"))
+ return name;
+
+ int i = len - 1;
+ QVarLengthArray<char16_t> outVector(len);
+ int used = len;
+ char16_t *out = outVector.data();
+ const ushort *p = reinterpret_cast<const ushort *>(name.data());
+ const ushort *prefix = p;
+ int up = 0;
+
+ const int prefixLength = name.at(0) == u'/' ? 1 : 0;
+
+ p += prefixLength;
+ i -= prefixLength;
+
+ // replicate trailing slash (i > 0 checks for emptiness of input string p)
+ // except for remote paths because there can be /../ or /./ ending
+ if (i > 0 && p[i] == '/') {
+ out[--used] = '/';
+ --i;
+ }
+
+ while (i >= 0) {
+ if (p[i] == '/') {
+ --i;
+ continue;
+ }
+
+ // remove current directory
+ if (p[i] == '.' && (i == 0 || p[i - 1] == '/')) {
+ --i;
+ continue;
+ }
+
+ // detect up dir
+ if (i >= 1 && p[i] == '.' && p[i - 1] == '.' && (i < 2 || p[i - 2] == '/')) {
+ ++up;
+ i -= i >= 2 ? 3 : 2;
+ continue;
+ }
+
+ // prepend a slash before copying when not empty
+ if (!up && used != len && out[used] != '/')
+ out[--used] = '/';
+
+ // skip or copy
+ while (i >= 0) {
+ if (p[i] == '/') {
+ --i;
+ break;
+ }
+
+ // actual copy
+ if (!up)
+ out[--used] = p[i];
+ --i;
+ }
+
+ // decrement up after copying/skipping
+ if (up)
+ --up;
+ }
+
+ // Indicate failure when ".." are left over for an absolute path.
+ // if (ok)
+ // *ok = prefixLength == 0 || up == 0;
+
+ // add remaining '..'
+ while (up) {
+ if (used != len && out[used] != '/') // is not empty and there isn't already a '/'
+ out[--used] = '/';
+ out[--used] = '.';
+ out[--used] = '.';
+ --up;
+ }
+
+ bool isEmpty = used == len;
+
+ if (prefixLength) {
+ if (!isEmpty && out[used] == '/') {
+ // Even though there is a prefix the out string is a slash. This happens, if the input
+ // string only consists of a prefix followed by one or more slashes. Just skip the slash.
+ ++used;
+ }
+ for (int i = prefixLength - 1; i >= 0; --i)
+ out[--used] = prefix[i];
+ } else {
+ if (isEmpty) {
+ // After resolving the input path, the resulting string is empty (e.g. "foo/.."). Return
+ // a dot in that case.
+ out[--used] = '.';
+ } else if (out[used] == '/') {
+ // After parsing the input string, out only contains a slash. That happens whenever all
+ // parts are resolved and there is a trailing slash ("./" or "foo/../" for example).
+ // Prepend a dot to have the correct return value.
+ out[--used] = '.';
+ }
+ }
+
+ // If path was not modified return the original value
+ if (used == 0)
+ return name;
+ return QString::fromUtf16(out + used, len - used);
+}
+
+QString doCleanPath(const QString &input_)
+{
+ QString input = input_;
+ if (input.contains('\\'))
+ input.replace('\\', '/');
+
+ if (input.startsWith("//?/")) {
+ input = input.mid(4);
+ if (input.startsWith("UNC/"))
+ input = '/' + input.mid(3); // trick it into reporting two slashs at start
+ }
+
+ int prefixLen = 0;
+ const int shLen = FilePath::schemeAndHostLength(input);
+ if (shLen > 0) {
+ prefixLen = shLen + FilePath::rootLength(input.mid(shLen));
+ } else {
+ prefixLen = FilePath::rootLength(input);
+ if (prefixLen > 0 && input.at(prefixLen - 1) == '/')
+ --prefixLen;
+ }
+
+ QString path = normalizePathSegmentHelper(input.mid(prefixLen));
+
+ // Strip away last slash except for root directories
+ if (path.size() > 1 && path.endsWith(u'/'))
+ path.chop(1);
+
+ return input.left(prefixLen) + path;
+}
/*! Find the parent directory of a given directory.
Returns an empty FilePath if the current directory is already
a root level directory.
- \returns \a FilePath with the last segment removed.
+ Returns \a FilePath with the last segment removed.
*/
FilePath FilePath::parentDir() const
{
@@ -1038,10 +1203,10 @@ FilePath FilePath::fromStringWithExtension(const QString &filepath, const QStrin
*/
FilePath FilePath::fromUserInput(const QString &filePath)
{
- QString clean = doCleanPath(filePath);
- if (clean.startsWith(QLatin1String("~/")))
- return FileUtils::homePath().pathAppended(clean.mid(2));
- return FilePath::fromString(clean);
+ const QString expandedPath = filePath.startsWith("~/")
+ ? (QDir::homePath() + "/" + filePath.mid(2))
+ : filePath;
+ return FilePath::fromString(doCleanPath(expandedPath));
}
/*!
@@ -1075,7 +1240,7 @@ QVariant FilePath::toVariant() const
}
/*!
- \returns whether FilePath is a child of \a s
+ Returns whether FilePath is a child of \a s.
*/
bool FilePath::isChildOf(const FilePath &s) const
{
@@ -1097,7 +1262,7 @@ bool FilePath::isChildOf(const FilePath &s) const
}
/*!
- \returns whether \c path() starts with \a s.
+ Returns whether \c path() starts with \a s.
*/
bool FilePath::startsWith(const QString &s) const
{
@@ -1105,7 +1270,7 @@ bool FilePath::startsWith(const QString &s) const
}
/*!
- \returns whether \c path() ends with \a s.
+ Returns whether \c path() ends with \a s.
*/
bool FilePath::endsWith(const QString &s) const
{
@@ -1113,7 +1278,7 @@ bool FilePath::endsWith(const QString &s) const
}
/*!
- \returns whether \c path() contains \a s.
+ Returns whether \c path() contains \a s.
*/
bool FilePath::contains(const QString &s) const
{
@@ -1124,7 +1289,8 @@ bool FilePath::contains(const QString &s) const
\brief Checks whether the FilePath starts with a drive letter.
Defaults to \c false if it is a non-Windows host or represents a path on device
- \returns whether FilePath starts with a drive letter
+
+ Returns whether FilePath starts with a drive letter
*/
bool FilePath::startsWithDriveLetter() const
{
@@ -1138,7 +1304,8 @@ bool FilePath::startsWithDriveLetter() const
Returns a empty FilePath if this is not a child of \p parent.
That is, this never returns a path starting with "../"
\param parent The Parent to calculate the relative path to.
- \returns The relative path of this to \p parent if this is a child of \p parent.
+
+ Returns The relative path of this to \p parent if this is a child of \p parent.
*/
FilePath FilePath::relativeChildPath(const FilePath &parent) const
{
@@ -1153,7 +1320,7 @@ FilePath FilePath::relativeChildPath(const FilePath &parent) const
}
/*!
- \returns the relativePath of FilePath from a given \a anchor.
+ Returns the relativePath of FilePath from a given \a anchor.
Both, FilePath and anchor may be files or directories.
Example usage:
@@ -1198,8 +1365,10 @@ FilePath FilePath::relativePathFrom(const FilePath &anchor) const
}
/*!
- \returns the relativePath of \a absolutePath to given \a absoluteAnchorPath.
- Both paths must be an absolute path to a directory. Example usage:
+ Returns the relativePath of \a absolutePath to given \a absoluteAnchorPath.
+ Both paths must be an absolute path to a directory.
+
+ Example usage:
\code
qDebug() << FilePath::calcRelativePath("/foo/b/ar", "/foo/c");
@@ -1247,8 +1416,7 @@ QString FilePath::calcRelativePath(const QString &absolutePath, const QString &a
}
/*!
- \brief Returns a path corresponding to the current object on the
-
+ Returns a path corresponding to the current object on the
same device as \a deviceTemplate. The FilePath needs to be local.
Example usage:
@@ -1260,8 +1428,6 @@ QString FilePath::calcRelativePath(const QString &absolutePath, const QString &a
\endcode
\param deviceTemplate A path from which the host and scheme is taken.
-
- \returns A path on the same device as \a deviceTemplate.
*/
FilePath FilePath::onDevice(const FilePath &deviceTemplate) const
{
@@ -1305,7 +1471,7 @@ FilePath FilePath::withNewPath(const QString &newPath) const
assert(fullPath == FilePath::fromUrl("docker://123/usr/bin/make"))
\endcode
*/
-FilePath FilePath::searchInDirectories(const FilePaths &dirs, const PathFilter &filter) const
+FilePath FilePath::searchInDirectories(const FilePaths &dirs, const FilePathPredicate &filter) const
{
if (isAbsolutePath())
return *this;
@@ -1314,7 +1480,7 @@ FilePath FilePath::searchInDirectories(const FilePaths &dirs, const PathFilter &
FilePath FilePath::searchInPath(const FilePaths &additionalDirs,
PathAmending amending,
- const PathFilter &filter) const
+ const FilePathPredicate &filter) const
{
if (isAbsolutePath())
return *this;
@@ -1416,7 +1582,11 @@ bool FilePath::setPermissions(QFile::Permissions permissions) const
OsType FilePath::osType() const
{
- return fileAccess()->osType(*this);
+ if (!needsDevice())
+ return HostOsInfo::hostOs();
+
+ QTC_ASSERT(s_deviceHooks.osType, return HostOsInfo::hostOs());
+ return s_deviceHooks.osType(*this);
}
bool FilePath::removeFile() const
@@ -1429,7 +1599,7 @@ bool FilePath::removeFile() const
\note The \a error parameter is optional.
- \returns A bool indicating whether the operation succeeded.
+ Returns a Bool indicating whether the operation succeeded.
*/
bool FilePath::removeRecursively(QString *error) const
{
@@ -1468,26 +1638,6 @@ expected_str<void> FilePath::copyFile(const FilePath &target) const
return fileAccess()->copyFile(*this, target);
}
-void FilePath::asyncCopyFile(const Continuation<const expected_str<void> &> &cont,
- const FilePath &target) const
-{
- if (host() != target.host()) {
- asyncFileContents([cont, target](const expected_str<QByteArray> &contents) {
- if (contents)
- target.asyncWriteFileContents(
- [cont](const expected_str<qint64> &result) {
- if (result)
- cont({});
- else
- cont(make_unexpected(result.error()));
- },
- *contents);
- });
- return;
- }
- return fileAccess()->asyncCopyFile(*this, cont, target);
-}
-
bool FilePath::renameFile(const FilePath &target) const
{
return fileAccess()->renameFile(*this, target);
@@ -1507,7 +1657,7 @@ qint64 FilePath::bytesAvailable() const
\brief Checks if this is newer than \p timeStamp
\param timeStamp The time stamp to compare with
- \returns true if this is newer than \p timeStamp.
+ Returns true if this is newer than \p timeStamp.
If this is a directory, the function will recursively check all files and return
true if one of them is newer than \a timeStamp. If this is a single file, true will
be returned if the file is newer than \a timeStamp.
@@ -1532,7 +1682,6 @@ bool FilePath::isNewerThan(const QDateTime &timeStamp) const
/*!
\brief Returns the caseSensitivity of the path.
- \returns The caseSensitivity of the path.
This is currently only based on the Host OS.
For device paths, \c Qt::CaseSensitive is always returned.
*/
@@ -1551,7 +1700,7 @@ Qt::CaseSensitivity FilePath::caseSensitivity() const
/*!
\brief Returns the separator of path components for this path.
- \returns The path separator of the path.
+ Returns the path separator of the path.
*/
QChar FilePath::pathComponentSeparator() const
{
@@ -1561,7 +1710,7 @@ QChar FilePath::pathComponentSeparator() const
/*!
\brief Returns the path list separator for the device this path belongs to.
- \returns The path list separator of the device for this path
+ Returns the path list separator of the device for this path.
*/
QChar FilePath::pathListSeparator() const
{
@@ -1577,7 +1726,7 @@ QChar FilePath::pathListSeparator() const
\note Maximum recursion depth == 16.
- \returns the symlink target file path.
+ Returns the symlink target file path.
*/
FilePath FilePath::resolveSymlinks() const
{
@@ -1598,7 +1747,7 @@ FilePath FilePath::resolveSymlinks() const
* Unlike QFileInfo::canonicalFilePath(), this function will not return an empty
* string if path doesn't exist.
*
-* \returns the canonical path.
+* Returns the canonical path.
*/
FilePath FilePath::canonicalPath() const
{
@@ -1655,7 +1804,7 @@ void FilePath::clear()
/*!
\brief Checks if the path() is empty.
- \returns true if the path() is empty.
+ Returns true if the path() is empty.
The Host and Scheme of the part are ignored.
*/
bool FilePath::isEmpty() const
@@ -1669,7 +1818,7 @@ bool FilePath::isEmpty() const
Like QDir::toNativeSeparators(), but use prefix '~' instead of $HOME on unix systems when an
absolute path is given.
- \returns the possibly shortened path with native separators.
+ Returns the possibly shortened path with native separators.
*/
QString FilePath::shortNativePath() const
{
@@ -1686,7 +1835,7 @@ QString FilePath::shortNativePath() const
/*!
\brief Checks whether the path is relative
- \returns true if the path is relative.
+ Returns true if the path is relative.
*/
bool FilePath::isRelativePath() const
{
@@ -1704,7 +1853,8 @@ bool FilePath::isRelativePath() const
\brief Appends the tail to this, if the tail is a relative path.
\param tail The tail to append.
- \returns Returns tail if tail is absolute, otherwise this + tail.
+
+ Returns tail if tail is absolute, otherwise this + tail.
*/
FilePath FilePath::resolvePath(const FilePath &tail) const
{
@@ -1719,7 +1869,8 @@ FilePath FilePath::resolvePath(const FilePath &tail) const
\brief Appends the tail to this, if the tail is a relative path.
\param tail The tail to append.
- \returns Returns tail if tail is absolute, otherwise this + tail.
+
+ Returns tail if tail is absolute, otherwise this + tail.
*/
FilePath FilePath::resolvePath(const QString &tail) const
{
@@ -1782,150 +1933,7 @@ QTextStream &operator<<(QTextStream &s, const FilePath &fn)
return s << fn.toString();
}
-static QString normalizePathSegmentHelper(const QString &name)
-{
- const int len = name.length();
-
- if (len == 0 || name.contains("%{"))
- return name;
-
- int i = len - 1;
- QVarLengthArray<char16_t> outVector(len);
- int used = len;
- char16_t *out = outVector.data();
- const ushort *p = reinterpret_cast<const ushort *>(name.data());
- const ushort *prefix = p;
- int up = 0;
-
- const int prefixLength = name.at(0) == u'/' ? 1 : 0;
-
- p += prefixLength;
- i -= prefixLength;
-
- // replicate trailing slash (i > 0 checks for emptiness of input string p)
- // except for remote paths because there can be /../ or /./ ending
- if (i > 0 && p[i] == '/') {
- out[--used] = '/';
- --i;
- }
-
- while (i >= 0) {
- if (p[i] == '/') {
- --i;
- continue;
- }
-
- // remove current directory
- if (p[i] == '.' && (i == 0 || p[i-1] == '/')) {
- --i;
- continue;
- }
-
- // detect up dir
- if (i >= 1 && p[i] == '.' && p[i-1] == '.' && (i < 2 || p[i - 2] == '/')) {
- ++up;
- i -= i >= 2 ? 3 : 2;
- continue;
- }
-
- // prepend a slash before copying when not empty
- if (!up && used != len && out[used] != '/')
- out[--used] = '/';
-
- // skip or copy
- while (i >= 0) {
- if (p[i] == '/') {
- --i;
- break;
- }
-
- // actual copy
- if (!up)
- out[--used] = p[i];
- --i;
- }
-
- // decrement up after copying/skipping
- if (up)
- --up;
- }
-
- // Indicate failure when ".." are left over for an absolute path.
-// if (ok)
-// *ok = prefixLength == 0 || up == 0;
-
- // add remaining '..'
- while (up) {
- if (used != len && out[used] != '/') // is not empty and there isn't already a '/'
- out[--used] = '/';
- out[--used] = '.';
- out[--used] = '.';
- --up;
- }
-
- bool isEmpty = used == len;
-
- if (prefixLength) {
- if (!isEmpty && out[used] == '/') {
- // Even though there is a prefix the out string is a slash. This happens, if the input
- // string only consists of a prefix followed by one or more slashes. Just skip the slash.
- ++used;
- }
- for (int i = prefixLength - 1; i >= 0; --i)
- out[--used] = prefix[i];
- } else {
- if (isEmpty) {
- // After resolving the input path, the resulting string is empty (e.g. "foo/.."). Return
- // a dot in that case.
- out[--used] = '.';
- } else if (out[used] == '/') {
- // After parsing the input string, out only contains a slash. That happens whenever all
- // parts are resolved and there is a trailing slash ("./" or "foo/../" for example).
- // Prepend a dot to have the correct return value.
- out[--used] = '.';
- }
- }
-
- // If path was not modified return the original value
- if (used == 0)
- return name;
- return QString::fromUtf16(out + used, len - used);
-}
-
-QString doCleanPath(const QString &input_)
-{
- QString input = input_;
- if (input.contains('\\'))
- input.replace('\\', '/');
-
- if (input.startsWith("//?/")) {
- input = input.mid(4);
- if (input.startsWith("UNC/"))
- input = '/' + input.mid(3); // trick it into reporting two slashs at start
- }
-
- int prefixLen = 0;
- const int shLen = FilePath::schemeAndHostLength(input);
- if (shLen > 0) {
- prefixLen = shLen + FilePath::rootLength(input.mid(shLen));
- } else {
- prefixLen = FilePath::rootLength(input);
- if (prefixLen > 0 && input.at(prefixLen - 1) == '/')
- --prefixLen;
- }
-
- QString path = normalizePathSegmentHelper(input.mid(prefixLen));
-
- // Strip away last slash except for root directories
- if (path.size() > 1 && path.endsWith(u'/'))
- path.chop(1);
-
- return input.left(prefixLen) + path;
-}
-
-
// FileFilter
-
FileFilter::FileFilter(const QStringList &nameFilters,
const QDir::Filters fileFilters,
const QDirIterator::IteratorFlags flags)
diff --git a/src/libs/utils/filepath.h b/src/libs/utils/filepath.h
index 4c61786eba4..220c6de5daa 100644
--- a/src/libs/utils/filepath.h
+++ b/src/libs/utils/filepath.h
@@ -8,6 +8,7 @@
#include "expected.h"
#include "filepathinfo.h"
#include "osspecificaspects.h"
+#include "utiltypes.h"
#include <QDir>
#include <QDirIterator>
@@ -31,9 +32,12 @@ namespace Utils {
class DeviceFileAccess;
class Environment;
-class EnvironmentChange;
+enum class FileStreamHandle;
template <class ...Args> using Continuation = std::function<void(Args...)>;
+using CopyContinuation = Continuation<const expected_str<void> &>;
+using ReadContinuation = Continuation<const expected_str<QByteArray> &>;
+using WriteContinuation = Continuation<const expected_str<qint64> &>;
class QTCREATOR_UTILS_EXPORT FileFilter
{
@@ -51,8 +55,6 @@ public:
using FilePaths = QList<class FilePath>;
-enum class IterationPolicy { Stop, Continue };
-
class QTCREATOR_UTILS_EXPORT FilePath
{
public:
@@ -159,7 +161,7 @@ public:
[[nodiscard]] FilePath relativeChildPath(const FilePath &parent) const;
[[nodiscard]] FilePath relativePathFrom(const FilePath &anchor) const;
[[nodiscard]] FilePath searchInDirectories(const FilePaths &dirs,
- const PathFilter &filter = {}) const;
+ const FilePathPredicate &filter = {}) const;
[[nodiscard]] Environment deviceEnvironment() const;
[[nodiscard]] FilePath onDevice(const FilePath &deviceTemplate) const;
[[nodiscard]] FilePath withNewPath(const QString &newPath) const;
@@ -182,7 +184,7 @@ public:
enum PathAmending { AppendToPath, PrependToPath };
[[nodiscard]] FilePath searchInPath(const FilePaths &additionalDirs = {},
PathAmending = AppendToPath,
- const PathFilter &filter = {}) const;
+ const FilePathPredicate &filter = {}) const;
enum MatchScope { ExactMatchOnly, WithExeSuffix, WithBatSuffix,
WithExeOrBatSuffix, WithAnySuffix };
@@ -207,14 +209,11 @@ public:
static void sort(FilePaths &files);
// Asynchronous interface
- void asyncCopyFile(const Continuation<const expected_str<void> &> &cont,
- const FilePath &target) const;
- void asyncFileContents(const Continuation<const expected_str<QByteArray> &> &cont,
- qint64 maxSize = -1,
- qint64 offset = 0) const;
- void asyncWriteFileContents(const Continuation<const expected_str<qint64> &> &cont,
- const QByteArray &data,
- qint64 offset = 0) const;
+ FileStreamHandle asyncCopy(const FilePath &target, QObject *context,
+ const CopyContinuation &cont = {}) const;
+ FileStreamHandle asyncRead(QObject *context, const ReadContinuation &cont = {}) const;
+ FileStreamHandle asyncWrite(const QByteArray &data, QObject *context,
+ const WriteContinuation &cont = {}) const;
// Prefer not to use
// Using needsDevice() in "user" code is likely to result in code that
@@ -291,8 +290,12 @@ public:
std::function<bool(const FilePath &left, const FilePath &right)> isSameDevice;
std::function<expected_str<FilePath>(const FilePath &)> localSource;
std::function<void(const FilePath &, const Environment &)> openTerminal;
+ std::function<OsType(const FilePath &)> osType;
};
+// For testing
+QTCREATOR_UTILS_EXPORT QString doCleanPath(const QString &input);
+
} // Utils
Q_DECLARE_METATYPE(Utils::FilePath)
diff --git a/src/libs/utils/filesearch.cpp b/src/libs/utils/filesearch.cpp
index ccbcfbfb9f9..02f6409de08 100644
--- a/src/libs/utils/filesearch.cpp
+++ b/src/libs/utils/filesearch.cpp
@@ -9,6 +9,7 @@
#include "qtcassert.h"
#include "stringutils.h"
#include "utilstr.h"
+#include "utiltypes.h"
#include <QLoggingCategory>
#include <QMutex>
@@ -496,8 +497,7 @@ static bool isFileIncluded(const QList<QRegularExpression> &filterRegs,
return isIncluded && (exclusionRegs.isEmpty() || !matches(exclusionRegs, filePath));
}
-std::function<bool(const FilePath &)> filterFileFunction(const QStringList &filters,
- const QStringList &exclusionFilters)
+FilePathPredicate filterFileFunction(const QStringList &filters, const QStringList &exclusionFilters)
{
const QList<QRegularExpression> filterRegs = filtersToRegExps(filters);
const QList<QRegularExpression> exclusionRegs = filtersToRegExps(exclusionFilters);
@@ -598,19 +598,20 @@ FileIterator::const_iterator FileIterator::end() const
// #pragma mark -- FileListIterator
-QTextCodec *encodingAt(const QList<QTextCodec *> &encodings, int index)
+QList<FileIterator::Item> constructItems(const FilePaths &fileList,
+ const QList<QTextCodec *> &encodings)
{
- if (index >= 0 && index < encodings.size())
- return encodings.at(index);
- return QTextCodec::codecForLocale();
+ QList<FileIterator::Item> items;
+ items.reserve(fileList.size());
+ QTextCodec *defaultEncoding = QTextCodec::codecForLocale();
+ for (int i = 0; i < fileList.size(); ++i)
+ items.append(FileIterator::Item(fileList.at(i), encodings.value(i, defaultEncoding)));
+ return items;
}
-FileListIterator::FileListIterator(const FilePaths &fileList, const QList<QTextCodec *> encodings)
- : m_maxIndex(-1)
+FileListIterator::FileListIterator(const FilePaths &fileList, const QList<QTextCodec *> &encodings)
+ : m_items(constructItems(fileList, encodings))
{
- m_items.reserve(fileList.size());
- for (int i = 0; i < fileList.size(); ++i)
- m_items.append(Item(fileList.at(i), encodingAt(encodings, i)));
}
void FileListIterator::update(int requestedIndex)
diff --git a/src/libs/utils/filesearch.h b/src/libs/utils/filesearch.h
index 9d427f5946e..ecb6c574af0 100644
--- a/src/libs/utils/filesearch.h
+++ b/src/libs/utils/filesearch.h
@@ -107,7 +107,8 @@ protected:
class QTCREATOR_UTILS_EXPORT FileListIterator : public FileIterator
{
public:
- explicit FileListIterator(const FilePaths &fileList, const QList<QTextCodec *> encodings);
+ explicit FileListIterator(const FilePaths &fileList = {},
+ const QList<QTextCodec *> &encodings = {});
int maxProgress() const override;
int currentProgress() const override;
@@ -118,8 +119,8 @@ protected:
const Item &itemAt(int index) const override;
private:
- QVector<Item> m_items;
- int m_maxIndex;
+ const QList<Item> m_items;
+ int m_maxIndex = -1;
};
class QTCREATOR_UTILS_EXPORT SubDirFileIterator : public FileIterator
diff --git a/src/libs/utils/filestreamer.cpp b/src/libs/utils/filestreamer.cpp
new file mode 100644
index 00000000000..47cbffd3c27
--- /dev/null
+++ b/src/libs/utils/filestreamer.cpp
@@ -0,0 +1,510 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "filestreamer.h"
+
+#include "asynctask.h"
+#include "qtcprocess.h"
+
+#include <QFile>
+#include <QMutex>
+#include <QMutexLocker>
+#include <QWaitCondition>
+
+namespace Utils {
+
+using namespace Tasking;
+
+// TODO: Adjust according to time spent on single buffer read so that it's not more than ~50 ms
+// in case of local read / write. Should it be adjusted dynamically / automatically?
+static const qint64 s_bufferSize = 0x1 << 20; // 1048576
+
+class FileStreamBase : public QObject
+{
+ Q_OBJECT
+
+public:
+ void setFilePath(const FilePath &filePath) { m_filePath = filePath; }
+ void start() {
+ QTC_ASSERT(!m_taskTree, return);
+
+ const TaskItem task = m_filePath.needsDevice() ? remoteTask() : localTask();
+ m_taskTree.reset(new TaskTree({task}));
+ const auto finalize = [this](bool success) {
+ m_taskTree.release()->deleteLater();
+ emit done(success);
+ };
+ connect(m_taskTree.get(), &TaskTree::done, this, [=] { finalize(true); });
+ connect(m_taskTree.get(), &TaskTree::errorOccurred, this, [=] { finalize(false); });
+ m_taskTree->start();
+ }
+
+signals:
+ void done(bool success);
+
+protected:
+ FilePath m_filePath;
+ std::unique_ptr<TaskTree> m_taskTree;
+
+private:
+ virtual TaskItem remoteTask() = 0;
+ virtual TaskItem localTask() = 0;
+};
+
+static void localRead(QPromise<QByteArray> &promise, const FilePath &filePath)
+{
+ if (promise.isCanceled())
+ return;
+
+ QFile file(filePath.path());
+ if (!file.exists()) {
+ promise.future().cancel();
+ return;
+ }
+
+ if (!file.open(QFile::ReadOnly)) {
+ promise.future().cancel();
+ return;
+ }
+
+ while (int chunkSize = qMin(s_bufferSize, file.bytesAvailable())) {
+ if (promise.isCanceled())
+ return;
+ promise.addResult(file.read(chunkSize));
+ }
+}
+
+class FileStreamReader : public FileStreamBase
+{
+ Q_OBJECT
+
+signals:
+ void readyRead(const QByteArray &newData);
+
+private:
+ TaskItem remoteTask() final {
+ const auto setup = [this](QtcProcess &process) {
+ const QStringList args = {"if=" + m_filePath.path()};
+ const FilePath dd = m_filePath.withNewPath("dd");
+ process.setCommand({dd, args, OsType::OsTypeLinux});
+ QtcProcess *processPtr = &process;
+ connect(processPtr, &QtcProcess::readyReadStandardOutput, this, [this, processPtr] {
+ emit readyRead(processPtr->readAllRawStandardOutput());
+ });
+ };
+ return Process(setup);
+ }
+ TaskItem localTask() final {
+ const auto setup = [this](AsyncTask<QByteArray> &async) {
+ async.setConcurrentCallData(localRead, m_filePath);
+ AsyncTask<QByteArray> *asyncPtr = &async;
+ connect(asyncPtr, &AsyncTaskBase::resultReadyAt, this, [=](int index) {
+ emit readyRead(asyncPtr->resultAt(index));
+ });
+ };
+ return Async<QByteArray>(setup);
+ }
+};
+
+class WriteBuffer : public QObject
+{
+ Q_OBJECT
+
+public:
+ WriteBuffer(bool isConcurrent, QObject *parent)
+ : QObject(parent)
+ , m_isConcurrent(isConcurrent) {}
+ struct Data {
+ QByteArray m_writeData;
+ bool m_closeWriteChannel = false;
+ bool m_canceled = false;
+ bool hasNewData() const { return m_closeWriteChannel || !m_writeData.isEmpty(); }
+ };
+
+ void write(const QByteArray &newData) {
+ if (m_isConcurrent) {
+ QMutexLocker locker(&m_mutex);
+ QTC_ASSERT(!m_data.m_closeWriteChannel, return);
+ QTC_ASSERT(!m_data.m_canceled, return);
+ m_data.m_writeData += newData;
+ m_waitCondition.wakeOne();
+ return;
+ }
+ emit writeRequested(newData);
+ }
+ void closeWriteChannel() {
+ if (m_isConcurrent) {
+ QMutexLocker locker(&m_mutex);
+ QTC_ASSERT(!m_data.m_canceled, return);
+ m_data.m_closeWriteChannel = true;
+ m_waitCondition.wakeOne();
+ return;
+ }
+ emit closeWriteChannelRequested();
+ }
+ void cancel() {
+ if (m_isConcurrent) {
+ QMutexLocker locker(&m_mutex);
+ m_data.m_canceled = true;
+ m_waitCondition.wakeOne();
+ return;
+ }
+ emit closeWriteChannelRequested();
+ }
+ Data waitForData() {
+ QTC_ASSERT(m_isConcurrent, return {});
+ QMutexLocker locker(&m_mutex);
+ if (!m_data.hasNewData())
+ m_waitCondition.wait(&m_mutex);
+ return std::exchange(m_data, {});
+ }
+
+signals:
+ void writeRequested(const QByteArray &newData);
+ void closeWriteChannelRequested();
+
+private:
+ QMutex m_mutex;
+ QWaitCondition m_waitCondition;
+ Data m_data;
+ bool m_isConcurrent = false; // Depends on whether FileStreamWriter::m_writeData is empty or not
+};
+
+static void localWrite(QPromise<void> &promise, const FilePath &filePath,
+ const QByteArray &initialData, WriteBuffer *buffer)
+{
+ if (promise.isCanceled())
+ return;
+
+ QFile file(filePath.path());
+
+ if (!file.open(QFile::WriteOnly | QFile::Truncate)) {
+ promise.future().cancel();
+ return;
+ }
+
+ if (!initialData.isEmpty()) {
+ const qint64 res = file.write(initialData);
+ if (res != initialData.size())
+ promise.future().cancel();
+ return;
+ }
+
+ while (true) {
+ if (promise.isCanceled()) {
+ promise.future().cancel();
+ return;
+ }
+ const WriteBuffer::Data data = buffer->waitForData();
+ if (data.m_canceled || promise.isCanceled()) {
+ promise.future().cancel();
+ return;
+ }
+ if (!data.m_writeData.isEmpty()) {
+ // TODO: Write in chunks of s_bufferSize and check for promise.isCanceled()
+ const qint64 res = file.write(data.m_writeData);
+ if (res != data.m_writeData.size()) {
+ promise.future().cancel();
+ return;
+ }
+ }
+ if (data.m_closeWriteChannel)
+ return;
+ }
+}
+
+class FileStreamWriter : public FileStreamBase
+{
+ Q_OBJECT
+
+public:
+ ~FileStreamWriter() { // TODO: should d'tor remove unfinished file write leftovers?
+ if (m_writeBuffer && isBuffered())
+ m_writeBuffer->cancel();
+ }
+
+ void setWriteData(const QByteArray &writeData) {
+ QTC_ASSERT(!m_taskTree, return);
+ m_writeData = writeData;
+ }
+ void write(const QByteArray &newData) {
+ QTC_ASSERT(m_taskTree, return);
+ QTC_ASSERT(m_writeData.isEmpty(), return);
+ QTC_ASSERT(m_writeBuffer, return);
+ m_writeBuffer->write(newData);
+ }
+ void closeWriteChannel() {
+ QTC_ASSERT(m_taskTree, return);
+ QTC_ASSERT(m_writeData.isEmpty(), return);
+ QTC_ASSERT(m_writeBuffer, return);
+ m_writeBuffer->closeWriteChannel();
+ }
+
+signals:
+ void started();
+
+private:
+ TaskItem remoteTask() final {
+ const auto setup = [this](QtcProcess &process) {
+ m_writeBuffer = new WriteBuffer(false, &process);
+ connect(m_writeBuffer, &WriteBuffer::writeRequested, &process, &QtcProcess::writeRaw);
+ connect(m_writeBuffer, &WriteBuffer::closeWriteChannelRequested,
+ &process, &QtcProcess::closeWriteChannel);
+ const QStringList args = {"of=" + m_filePath.path()};
+ const FilePath dd = m_filePath.withNewPath("dd");
+ process.setCommand({dd, args, OsType::OsTypeLinux});
+ if (isBuffered())
+ process.setProcessMode(ProcessMode::Writer);
+ else
+ process.setWriteData(m_writeData);
+ connect(&process, &QtcProcess::started, this, [this] { emit started(); });
+ };
+ const auto finalize = [this](const QtcProcess &) {
+ delete m_writeBuffer;
+ m_writeBuffer = nullptr;
+ };
+ return Process(setup, finalize, finalize);
+ }
+ TaskItem localTask() final {
+ const auto setup = [this](AsyncTask<void> &async) {
+ m_writeBuffer = new WriteBuffer(isBuffered(), &async);
+ async.setConcurrentCallData(localWrite, m_filePath, m_writeData, m_writeBuffer);
+ emit started();
+ };
+ const auto finalize = [this](const AsyncTask<void> &) {
+ delete m_writeBuffer;
+ m_writeBuffer = nullptr;
+ };
+ return Async<void>(setup, finalize, finalize);
+ }
+
+ bool isBuffered() const { return m_writeData.isEmpty(); }
+ QByteArray m_writeData;
+ WriteBuffer *m_writeBuffer = nullptr;
+};
+
+class FileStreamReaderAdapter : public Utils::Tasking::TaskAdapter<FileStreamReader>
+{
+public:
+ FileStreamReaderAdapter() { connect(task(), &FileStreamBase::done, this, &TaskInterface::done); }
+ void start() override { task()->start(); }
+};
+
+class FileStreamWriterAdapter : public Utils::Tasking::TaskAdapter<FileStreamWriter>
+{
+public:
+ FileStreamWriterAdapter() { connect(task(), &FileStreamBase::done, this, &TaskInterface::done); }
+ void start() override { task()->start(); }
+};
+
+} // namespace Utils
+
+QTC_DECLARE_CUSTOM_TASK(Reader, Utils::FileStreamReaderAdapter);
+QTC_DECLARE_CUSTOM_TASK(Writer, Utils::FileStreamWriterAdapter);
+
+namespace Utils {
+
+static Group sameRemoteDeviceTransferTask(const FilePath &source, const FilePath &destination)
+{
+ QTC_CHECK(source.needsDevice());
+ QTC_CHECK(destination.needsDevice());
+ QTC_CHECK(source.isSameDevice(destination));
+
+ const auto setup = [source, destination](QtcProcess &process) {
+ const QStringList args = {source.path(), destination.path()};
+ const FilePath cp = source.withNewPath("cp");
+ process.setCommand({cp, args, OsType::OsTypeLinux});
+ };
+ return {Process(setup)};
+}
+
+static Group interDeviceTransferTask(const FilePath &source, const FilePath &destination)
+{
+ struct TransferStorage { QPointer<FileStreamWriter> writer; };
+ Condition condition;
+ TreeStorage<TransferStorage> storage;
+
+ const auto setupReader = [=](FileStreamReader &reader) {
+ reader.setFilePath(source);
+ QTC_CHECK(storage->writer != nullptr);
+ QObject::connect(&reader, &FileStreamReader::readyRead,
+ storage->writer, &FileStreamWriter::write);
+ };
+ const auto finalizeReader = [=](const FileStreamReader &) {
+ QTC_CHECK(storage->writer != nullptr);
+ storage->writer->closeWriteChannel();
+ };
+ const auto setupWriter = [=](FileStreamWriter &writer) {
+ writer.setFilePath(destination);
+ ConditionActivator *activator = condition.activator();
+ QObject::connect(&writer, &FileStreamWriter::started,
+ &writer, [activator] { activator->activate(); });
+ QTC_CHECK(storage->writer == nullptr);
+ storage->writer = &writer;
+ };
+
+ const Group root {
+ parallel,
+ Storage(storage),
+ Writer(setupWriter),
+ Group {
+ WaitFor(condition),
+ Reader(setupReader, finalizeReader, finalizeReader)
+ }
+ };
+
+ return root;
+}
+
+static Group transferTask(const FilePath &source, const FilePath &destination)
+{
+ if (source.needsDevice() && destination.needsDevice() && source.isSameDevice(destination))
+ return sameRemoteDeviceTransferTask(source, destination);
+ return interDeviceTransferTask(source, destination);
+}
+
+static void transfer(QPromise<void> &promise, const FilePath &source, const FilePath &destination)
+{
+ if (promise.isCanceled())
+ return;
+
+ std::unique_ptr<TaskTree> taskTree(new TaskTree(transferTask(source, destination)));
+
+ QEventLoop eventLoop;
+ bool finalized = false;
+ const auto finalize = [loop = &eventLoop, &taskTree, &finalized](int exitCode) {
+ if (finalized) // finalize only once
+ return;
+ finalized = true;
+ // Give the tree a chance to delete later all tasks that have finished and caused
+ // emission of tree's done or errorOccurred signal.
+ // TODO: maybe these signals should be sent queued already?
+ QMetaObject::invokeMethod(loop, [loop, &taskTree, exitCode] {
+ taskTree.reset();
+ loop->exit(exitCode);
+ }, Qt::QueuedConnection);
+ };
+ QTimer timer;
+ timer.setInterval(50);
+ QObject::connect(&timer, &QTimer::timeout, [&promise, finalize] {
+ if (promise.isCanceled())
+ finalize(2);
+ });
+ QObject::connect(taskTree.get(), &TaskTree::done, &eventLoop, [=] { finalize(0); });
+ QObject::connect(taskTree.get(), &TaskTree::errorOccurred, &eventLoop, [=] { finalize(1); });
+ taskTree->start();
+ timer.start();
+ if (eventLoop.exec())
+ promise.future().cancel();
+}
+
+class FileStreamerPrivate : public QObject
+{
+public:
+ StreamMode m_streamerMode = StreamMode::Transfer;
+ FilePath m_source;
+ FilePath m_destination;
+ QByteArray m_readBuffer;
+ QByteArray m_writeBuffer;
+ StreamResult m_streamResult = StreamResult::FinishedWithError;
+ std::unique_ptr<TaskTree> m_taskTree;
+
+ TaskItem task() {
+ if (m_streamerMode == StreamMode::Reader)
+ return readerTask();
+ if (m_streamerMode == StreamMode::Writer)
+ return writerTask();
+ return transferTask();
+ }
+
+private:
+ TaskItem readerTask() {
+ const auto setup = [this](FileStreamReader &reader) {
+ m_readBuffer.clear();
+ reader.setFilePath(m_source);
+ connect(&reader, &FileStreamReader::readyRead, this, [this](const QByteArray &data) {
+ m_readBuffer += data;
+ });
+ };
+ return Reader(setup);
+ }
+ TaskItem writerTask() {
+ const auto setup = [this](FileStreamWriter &writer) {
+ writer.setFilePath(m_destination);
+ writer.setWriteData(m_writeBuffer);
+ };
+ return Writer(setup);
+ }
+ TaskItem transferTask() {
+ const auto setup = [this](AsyncTask<void> &async) {
+ async.setConcurrentCallData(transfer, m_source, m_destination);
+ };
+ return Async<void>(setup);
+ }
+};
+
+FileStreamer::FileStreamer(QObject *parent)
+ : QObject(parent)
+ , d(new FileStreamerPrivate)
+{
+}
+
+FileStreamer::~FileStreamer()
+{
+ delete d;
+}
+
+void FileStreamer::setSource(const FilePath &source)
+{
+ d->m_source = source;
+}
+
+void FileStreamer::setDestination(const FilePath &destination)
+{
+ d->m_destination = destination;
+}
+
+void FileStreamer::setStreamMode(StreamMode mode)
+{
+ d->m_streamerMode = mode;
+}
+
+QByteArray FileStreamer::readData() const
+{
+ return d->m_readBuffer;
+}
+
+void FileStreamer::setWriteData(const QByteArray &writeData)
+{
+ d->m_writeBuffer = writeData;
+}
+
+StreamResult FileStreamer::result() const
+{
+ return d->m_streamResult;
+}
+
+void FileStreamer::start()
+{
+ // TODO: Preliminary check if local source exists?
+ QTC_ASSERT(!d->m_taskTree, return);
+ d->m_taskTree.reset(new TaskTree({d->task()}));
+ const auto finalize = [this](bool success) {
+ d->m_streamResult = success ? StreamResult::FinishedWithSuccess
+ : StreamResult::FinishedWithError;
+ d->m_taskTree.release()->deleteLater();
+ emit done();
+ };
+ connect(d->m_taskTree.get(), &TaskTree::done, this, [=] { finalize(true); });
+ connect(d->m_taskTree.get(), &TaskTree::errorOccurred, this, [=] { finalize(false); });
+ d->m_taskTree->start();
+}
+
+void FileStreamer::stop()
+{
+ d->m_taskTree.reset();
+}
+
+} // namespace Utils
+
+#include "filestreamer.moc"
diff --git a/src/libs/utils/filestreamer.h b/src/libs/utils/filestreamer.h
new file mode 100644
index 00000000000..b572e910a20
--- /dev/null
+++ b/src/libs/utils/filestreamer.h
@@ -0,0 +1,62 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "utils_global.h"
+
+#include "filepath.h"
+#include "tasktree.h"
+
+#include <QObject>
+
+QT_BEGIN_NAMESPACE
+class QByteArray;
+QT_END_NAMESPACE
+
+namespace Utils {
+
+enum class StreamMode { Reader, Writer, Transfer };
+
+enum class StreamResult { FinishedWithSuccess, FinishedWithError };
+
+class QTCREATOR_UTILS_EXPORT FileStreamer final : public QObject
+{
+ Q_OBJECT
+
+public:
+ FileStreamer(QObject *parent = nullptr);
+ ~FileStreamer();
+
+ void setSource(const FilePath &source);
+ void setDestination(const FilePath &destination);
+ void setStreamMode(StreamMode mode); // Transfer by default
+
+ // Only for Reader mode
+ QByteArray readData() const;
+ // Only for Writer mode
+ void setWriteData(const QByteArray &writeData);
+
+ StreamResult result() const;
+
+ void start();
+ void stop();
+
+signals:
+ void done();
+
+private:
+ class FileStreamerPrivate *d = nullptr;
+};
+
+class FileStreamerAdapter : public Utils::Tasking::TaskAdapter<FileStreamer>
+{
+public:
+ FileStreamerAdapter() { connect(task(), &FileStreamer::done, this,
+ [this] { emit done(task()->result() == StreamResult::FinishedWithSuccess); }); }
+ void start() override { task()->start(); }
+};
+
+} // namespace Utils
+
+QTC_DECLARE_CUSTOM_TASK(Streamer, Utils::FileStreamerAdapter);
diff --git a/src/libs/utils/filestreamermanager.cpp b/src/libs/utils/filestreamermanager.cpp
new file mode 100644
index 00000000000..11d05faee57
--- /dev/null
+++ b/src/libs/utils/filestreamermanager.cpp
@@ -0,0 +1,201 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "filestreamermanager.h"
+
+#include "filestreamer.h"
+#include "threadutils.h"
+#include "utilstr.h"
+
+#include <QMutex>
+#include <QMutexLocker>
+#include <QThread>
+#include <QWaitCondition>
+
+#include <unordered_map>
+
+namespace Utils {
+
+// TODO: destruct the instance before destructing ProjectExplorer::DeviceManager (?)
+
+static FileStreamHandle generateUniqueHandle()
+{
+ static std::atomic_int handleCounter = 1;
+ return FileStreamHandle(handleCounter.fetch_add(1));
+}
+
+static QMutex s_mutex = {};
+static QWaitCondition s_waitCondition = {};
+static std::unordered_map<FileStreamHandle, FileStreamer *> s_fileStreamers = {};
+
+static void addStreamer(FileStreamHandle handle, FileStreamer *streamer)
+{
+ QMutexLocker locker(&s_mutex);
+ const bool added = s_fileStreamers.try_emplace(handle, streamer).second;
+ QTC_CHECK(added);
+}
+
+static void removeStreamer(FileStreamHandle handle)
+{
+ QMutexLocker locker(&s_mutex);
+ auto it = s_fileStreamers.find(handle);
+ QTC_ASSERT(it != s_fileStreamers.end(), return);
+ QTC_ASSERT(QThread::currentThread() == it->second->thread(), return);
+ s_fileStreamers.erase(it);
+ s_waitCondition.wakeAll();
+}
+
+static void deleteStreamer(FileStreamHandle handle)
+{
+ QMutexLocker locker(&s_mutex);
+ auto it = s_fileStreamers.find(handle);
+ if (it == s_fileStreamers.end())
+ return;
+ if (QThread::currentThread() == it->second->thread()) {
+ delete it->second;
+ s_fileStreamers.erase(it);
+ s_waitCondition.wakeAll();
+ } else {
+ QMetaObject::invokeMethod(it->second, [handle] {
+ deleteStreamer(handle);
+ });
+ s_waitCondition.wait(&s_mutex);
+ QTC_CHECK(s_fileStreamers.find(handle) == s_fileStreamers.end());
+ }
+}
+
+static void deleteAllStreamers()
+{
+ QMutexLocker locker(&s_mutex);
+ QTC_ASSERT(Utils::isMainThread(), return);
+ while (s_fileStreamers.size()) {
+ auto it = s_fileStreamers.begin();
+ if (QThread::currentThread() == it->second->thread()) {
+ delete it->second;
+ s_fileStreamers.erase(it);
+ s_waitCondition.wakeAll();
+ } else {
+ const FileStreamHandle handle = it->first;
+ QMetaObject::invokeMethod(it->second, [handle] {
+ deleteStreamer(handle);
+ });
+ s_waitCondition.wait(&s_mutex);
+ QTC_CHECK(s_fileStreamers.find(handle) == s_fileStreamers.end());
+ }
+ }
+}
+
+static FileStreamHandle checkHandle(FileStreamHandle handle)
+{
+ QMutexLocker locker(&s_mutex);
+ return s_fileStreamers.find(handle) != s_fileStreamers.end() ? handle : FileStreamHandle(0);
+}
+
+FileStreamHandle execute(const std::function<void(FileStreamer *)> &onSetup,
+ const std::function<void(FileStreamer *)> &onDone,
+ QObject *context)
+{
+ FileStreamer *streamer = new FileStreamer;
+ onSetup(streamer);
+ const FileStreamHandle handle = generateUniqueHandle();
+ QTC_CHECK(context == nullptr || context->thread() == QThread::currentThread());
+ if (onDone) {
+ QObject *finalContext = context ? context : streamer;
+ QObject::connect(streamer, &FileStreamer::done, finalContext, [=] { onDone(streamer); });
+ }
+ QObject::connect(streamer, &FileStreamer::done, streamer, [=] {
+ removeStreamer(handle);
+ streamer->deleteLater();
+ });
+ addStreamer(handle, streamer);
+ streamer->start();
+ return checkHandle(handle); // The handle could have been already removed
+}
+
+FileStreamHandle FileStreamerManager::copy(const FilePath &source, const FilePath &destination,
+ const CopyContinuation &cont)
+{
+ return copy(source, destination, nullptr, cont);
+}
+
+FileStreamHandle FileStreamerManager::copy(const FilePath &source, const FilePath &destination,
+ QObject *context, const CopyContinuation &cont)
+{
+ const auto onSetup = [=](FileStreamer *streamer) {
+ streamer->setSource(source);
+ streamer->setDestination(destination);
+ };
+ if (!cont)
+ return execute(onSetup, {}, context);
+
+ const auto onDone = [=](FileStreamer *streamer) {
+ if (streamer->result() == StreamResult::FinishedWithSuccess)
+ cont({});
+ else
+ cont(make_unexpected(Tr::tr("Failed copying file")));
+ };
+ return execute(onSetup, onDone, context);
+}
+
+FileStreamHandle FileStreamerManager::read(const FilePath &source, const ReadContinuation &cont)
+{
+ return read(source, nullptr, cont);
+}
+
+FileStreamHandle FileStreamerManager::read(const FilePath &source, QObject *context,
+ const ReadContinuation &cont)
+{
+ const auto onSetup = [=](FileStreamer *streamer) {
+ streamer->setStreamMode(StreamMode::Reader);
+ streamer->setSource(source);
+ };
+ if (!cont)
+ return execute(onSetup, {}, context);
+
+ const auto onDone = [=](FileStreamer *streamer) {
+ if (streamer->result() == StreamResult::FinishedWithSuccess)
+ cont(streamer->readData());
+ else
+ cont(make_unexpected(Tr::tr("Failed reading file")));
+ };
+ return execute(onSetup, onDone, context);
+}
+
+FileStreamHandle FileStreamerManager::write(const FilePath &destination, const QByteArray &data,
+ const WriteContinuation &cont)
+{
+ return write(destination, data, nullptr, cont);
+}
+
+FileStreamHandle FileStreamerManager::write(const FilePath &destination, const QByteArray &data,
+ QObject *context, const WriteContinuation &cont)
+{
+ const auto onSetup = [=](FileStreamer *streamer) {
+ streamer->setStreamMode(StreamMode::Writer);
+ streamer->setDestination(destination);
+ streamer->setWriteData(data);
+ };
+ if (!cont)
+ return execute(onSetup, {}, context);
+
+ const auto onDone = [=](FileStreamer *streamer) {
+ if (streamer->result() == StreamResult::FinishedWithSuccess)
+ cont(0); // TODO: return write count?
+ else
+ cont(make_unexpected(Tr::tr("Failed writing file")));
+ };
+ return execute(onSetup, onDone, context);
+}
+
+void FileStreamerManager::stop(FileStreamHandle handle)
+{
+ deleteStreamer(handle);
+}
+
+void FileStreamerManager::stopAll()
+{
+ deleteAllStreamers();
+}
+
+} // namespace Utils
+
diff --git a/src/libs/utils/filestreamermanager.h b/src/libs/utils/filestreamermanager.h
new file mode 100644
index 00000000000..f86a3db480f
--- /dev/null
+++ b/src/libs/utils/filestreamermanager.h
@@ -0,0 +1,42 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "utils_global.h"
+
+#include "filepath.h"
+
+#include <QObject>
+
+QT_BEGIN_NAMESPACE
+class QByteArray;
+QT_END_NAMESPACE
+
+namespace Utils {
+
+enum class FileStreamHandle : int {};
+
+class QTCREATOR_UTILS_EXPORT FileStreamerManager
+{
+public:
+ static FileStreamHandle copy(const FilePath &source, const FilePath &destination,
+ const CopyContinuation &cont);
+ static FileStreamHandle copy(const FilePath &source, const FilePath &destination,
+ QObject *context, const CopyContinuation &cont);
+
+ static FileStreamHandle read(const FilePath &source, const ReadContinuation &cont = {});
+ static FileStreamHandle read(const FilePath &source, QObject *context,
+ const ReadContinuation &cont = {});
+
+ static FileStreamHandle write(const FilePath &destination, const QByteArray &data,
+ const WriteContinuation &cont = {});
+ static FileStreamHandle write(const FilePath &destination, const QByteArray &data,
+ QObject *context, const WriteContinuation &cont = {});
+
+ // If called from the same thread that started the task, no continuation is going to be called.
+ static void stop(FileStreamHandle handle);
+ static void stopAll();
+};
+
+} // namespace Utils
diff --git a/src/libs/utils/fileutils.cpp b/src/libs/utils/fileutils.cpp
index 69c31729149..4bd0692e133 100644
--- a/src/libs/utils/fileutils.cpp
+++ b/src/libs/utils/fileutils.cpp
@@ -5,7 +5,6 @@
#include "savefile.h"
#include "algorithm.h"
-#include "hostosinfo.h"
#include "qtcassert.h"
#include "utilstr.h"
@@ -586,8 +585,7 @@ FilePaths FileUtils::getOpenFilePaths(QWidget *parent,
#endif // QT_WIDGETS_LIB
-// Converts a hex string of the st_mode field of a stat structure to FileFlags.
-FilePathInfo::FileFlags fileInfoFlagsfromStatRawModeHex(const QString &hexString)
+FilePathInfo::FileFlags fileInfoFlagsfromStatMode(const QString &hexString, int modeBase)
{
// Copied from stat.h
enum st_mode {
@@ -617,7 +615,7 @@ FilePathInfo::FileFlags fileInfoFlagsfromStatRawModeHex(const QString &hexString
};
bool ok = false;
- uint mode = hexString.toUInt(&ok, 16);
+ uint mode = hexString.toUInt(&ok, modeBase);
QTC_ASSERT(ok, return {});
@@ -657,13 +655,13 @@ FilePathInfo::FileFlags fileInfoFlagsfromStatRawModeHex(const QString &hexString
return result;
}
-FilePathInfo FileUtils::filePathInfoFromTriple(const QString &infos)
+FilePathInfo FileUtils::filePathInfoFromTriple(const QString &infos, int modeBase)
{
const QStringList parts = infos.split(' ', Qt::SkipEmptyParts);
if (parts.size() != 3)
return {};
- FilePathInfo::FileFlags flags = fileInfoFlagsfromStatRawModeHex(parts[0]);
+ FilePathInfo::FileFlags flags = fileInfoFlagsfromStatMode(parts[0], modeBase);
const QDateTime dt = QDateTime::fromSecsSinceEpoch(parts[1].toLongLong(), Qt::UTC);
qint64 size = parts[2].toLongLong();
@@ -802,7 +800,7 @@ FilePath FileUtils::commonPath(const FilePath &oldCommonPath, const FilePath &fi
FilePath FileUtils::homePath()
{
- return FilePath::fromString(doCleanPath(QDir::homePath()));
+ return FilePath::fromUserInput(QDir::homePath());
}
FilePaths FileUtils::toFilePathList(const QStringList &paths) {
diff --git a/src/libs/utils/fileutils.h b/src/libs/utils/fileutils.h
index f34206d8b77..a1a7ffef977 100644
--- a/src/libs/utils/fileutils.h
+++ b/src/libs/utils/fileutils.h
@@ -83,7 +83,7 @@ public:
static qint64 bytesAvailableFromDFOutput(const QByteArray &dfOutput);
- static FilePathInfo filePathInfoFromTriple(const QString &infos);
+ static FilePathInfo filePathInfoFromTriple(const QString &infos, int modeBase);
#ifdef QT_WIDGETS_LIB
static void setDialogParentGetter(const std::function<QWidget *()> &getter);
@@ -121,7 +121,6 @@ public:
QString *selectedFilter = nullptr,
QFileDialog::Options options = {});
#endif
-
};
// for actually finding out if e.g. directories are writable on Windows
@@ -232,9 +231,5 @@ QTCREATOR_UTILS_EXPORT QTextStream &operator<<(QTextStream &s, const FilePath &f
bool isRelativePathHelper(const QString &path, OsType osType);
-// For testing
-QTCREATOR_UTILS_EXPORT QString doCleanPath(const QString &input);
-QTCREATOR_UTILS_EXPORT QString cleanPathHelper(const QString &path);
-
} // namespace Utils
diff --git a/src/libs/utils/fileutils_mac.h b/src/libs/utils/fileutils_mac.h
index 4c9e7557e10..c7ddd0d2cdc 100644
--- a/src/libs/utils/fileutils_mac.h
+++ b/src/libs/utils/fileutils_mac.h
@@ -8,7 +8,6 @@
namespace Utils {
namespace Internal {
-QUrl filePathUrl(const QUrl &url);
QString normalizePathName(const QString &filePath);
} // Internal
diff --git a/src/libs/utils/fileutils_mac.mm b/src/libs/utils/fileutils_mac.mm
index e7ff062f66b..c3d494991d4 100644
--- a/src/libs/utils/fileutils_mac.mm
+++ b/src/libs/utils/fileutils_mac.mm
@@ -14,17 +14,6 @@
namespace Utils {
namespace Internal {
-QUrl filePathUrl(const QUrl &url)
-{
- QUrl ret = url;
- @autoreleasepool {
- NSURL *nsurl = url.toNSURL();
- if ([nsurl isFileReferenceURL])
- ret = QUrl::fromNSURL([nsurl filePathURL]);
- }
- return ret;
-}
-
QString normalizePathName(const QString &filePath)
{
QString result;
diff --git a/src/libs/utils/fsengine/diriterator.h b/src/libs/utils/fsengine/diriterator.h
index 9daebbebaaa..54e9d5d2dd3 100644
--- a/src/libs/utils/fsengine/diriterator.h
+++ b/src/libs/utils/fsengine/diriterator.h
@@ -39,6 +39,9 @@ public:
QString currentFileName() const override
{
const QString result = it->fileName();
+ if (result.isEmpty() && !it->host().isEmpty()) {
+ return it->host().toString();
+ }
return chopIfEndsWith(result, '/');
}
diff --git a/src/libs/utils/fsengine/fileiconprovider.cpp b/src/libs/utils/fsengine/fileiconprovider.cpp
index e5ed4a1281f..07ea4b3f5a8 100644
--- a/src/libs/utils/fsengine/fileiconprovider.cpp
+++ b/src/libs/utils/fsengine/fileiconprovider.cpp
@@ -216,46 +216,7 @@ QIcon FileIconProviderImplementation::icon(const FilePath &filePath) const
{
qCDebug(fileIconProvider) << "FileIconProvider::icon" << filePath.absoluteFilePath();
- if (filePath.isEmpty())
- return unknownFileIcon();
-
- // Check if its one of the virtual devices directories
- if (filePath.path().startsWith(FilePath::specialRootPath())) {
- // If the filepath does not need a device, it is a virtual device directory
- if (!filePath.needsDevice())
- return dirIcon();
- }
-
- bool isDir = filePath.isDir();
-
- // Check for cached overlay icons by file suffix.
- const QString filename = !isDir ? filePath.fileName() : QString();
- if (!filename.isEmpty()) {
- const std::optional<QIcon> icon = getIcon(m_filenameCache, filename);
- if (icon)
- return *icon;
- }
-
- const QString suffix = !isDir ? filePath.suffix() : QString();
- if (!suffix.isEmpty()) {
- const std::optional<QIcon> icon = getIcon(m_suffixCache, suffix);
- if (icon)
- return *icon;
- }
-
- if (filePath.needsDevice())
- return isDir ? dirIcon() : unknownFileIcon();
-
- // Get icon from OS (and cache it based on suffix!)
- QIcon icon;
- if (HostOsInfo::isWindowsHost() || HostOsInfo::isMacHost())
- icon = QFileIconProvider::icon(filePath.toFileInfo());
- else // File icons are unknown on linux systems.
- icon = isDir ? QFileIconProvider::icon(filePath.toFileInfo()) : unknownFileIcon();
-
- if (!isDir && !suffix.isEmpty())
- m_suffixCache.insert(suffix, icon);
- return icon;
+ return icon(QFileInfo(filePath.toFSPathString()));
}
/*!
diff --git a/src/libs/utils/fsengine/fileiteratordevicesappender.h b/src/libs/utils/fsengine/fileiteratordevicesappender.h
index c08c6cb3f70..9aec917d6b7 100644
--- a/src/libs/utils/fsengine/fileiteratordevicesappender.h
+++ b/src/libs/utils/fsengine/fileiteratordevicesappender.h
@@ -93,7 +93,10 @@ private:
void setPath() const
{
if (!m_hasSetPath) {
- const QString p = path();
+ // path() can be "/somedir/.." so we need to clean it first.
+ // We only need QDir::cleanPath here, as the path is always
+ // a fs engine path and will not contain scheme:// etc.
+ const QString p = QDir::cleanPath(path());
if (p.compare(QDir::rootPath(), Qt::CaseInsensitive) == 0)
m_status = State::IteratingRoot;
diff --git a/src/libs/utils/fsengine/fixedlistfsengine.h b/src/libs/utils/fsengine/fixedlistfsengine.h
index 991bc08b1bf..3518b5d0b5a 100644
--- a/src/libs/utils/fsengine/fixedlistfsengine.h
+++ b/src/libs/utils/fsengine/fixedlistfsengine.h
@@ -43,6 +43,8 @@ public:
return chopIfEndsWith(m_filePath.toString(), '/');
break;
case QAbstractFileEngine::BaseName:
+ if (m_filePath.fileName().isEmpty())
+ return m_filePath.host().toString();
return m_filePath.fileName();
break;
case QAbstractFileEngine::PathName:
diff --git a/src/libs/utils/fsengine/fsengine_impl.cpp b/src/libs/utils/fsengine/fsengine_impl.cpp
index 6654e741ace..ade8236d5e1 100644
--- a/src/libs/utils/fsengine/fsengine_impl.cpp
+++ b/src/libs/utils/fsengine/fsengine_impl.cpp
@@ -238,6 +238,8 @@ QString FSEngineImpl::fileName(FileName file) const
return m_filePath.toFSPathString();
break;
case QAbstractFileEngine::BaseName:
+ if (m_filePath.fileName().isEmpty())
+ return m_filePath.host().toString();
return m_filePath.fileName();
break;
case QAbstractFileEngine::PathName:
diff --git a/src/libs/utils/fsengine/fsenginehandler.cpp b/src/libs/utils/fsengine/fsenginehandler.cpp
index a713315ae39..c5fa71e7861 100644
--- a/src/libs/utils/fsengine/fsenginehandler.cpp
+++ b/src/libs/utils/fsengine/fsenginehandler.cpp
@@ -13,6 +13,26 @@
namespace Utils::Internal {
+static FilePath removeDoubleSlash(const QString &fileName)
+{
+ // Reduce every two or more slashes to a single slash.
+ QString result;
+ const QChar slash = QChar('/');
+ bool lastWasSlash = false;
+ for (const QChar &ch : fileName) {
+ if (ch == slash) {
+ if (!lastWasSlash)
+ result.append(ch);
+ lastWasSlash = true;
+ } else {
+ result.append(ch);
+ lastWasSlash = false;
+ }
+ }
+ // We use fromString() here to not normalize / clean the path anymore.
+ return FilePath::fromString(result);
+}
+
QAbstractFileEngine *FSEngineHandler::create(const QString &fileName) const
{
if (fileName.startsWith(':'))
@@ -29,7 +49,7 @@ QAbstractFileEngine *FSEngineHandler::create(const QString &fileName) const
return rootFilePath.pathAppended(scheme);
});
- return new FixedListFSEngine(rootFilePath, paths);
+ return new FixedListFSEngine(removeDoubleSlash(fileName), paths);
}
if (fixedFileName.startsWith(rootPath)) {
@@ -41,17 +61,18 @@ QAbstractFileEngine *FSEngineHandler::create(const QString &fileName) const
return root.scheme() == scheme;
});
- return new FixedListFSEngine(rootFilePath.pathAppended(scheme), filteredRoots);
+ return new FixedListFSEngine(removeDoubleSlash(fileName), filteredRoots);
}
}
- FilePath filePath = FilePath::fromString(fixedFileName);
- if (filePath.needsDevice())
- return new FSEngineImpl(filePath);
+ FilePath fixedPath = FilePath::fromString(fixedFileName);
+
+ if (fixedPath.needsDevice())
+ return new FSEngineImpl(removeDoubleSlash(fileName));
}
if (fixedFileName.compare(QDir::rootPath(), Qt::CaseInsensitive) == 0)
- return new RootInjectFSEngine(fixedFileName);
+ return new RootInjectFSEngine(fileName);
return nullptr;
}
diff --git a/src/libs/utils/images/iconoverlay_close_small.png b/src/libs/utils/images/iconoverlay_close_small.png
new file mode 100644
index 00000000000..e39b67cfbb3
--- /dev/null
+++ b/src/libs/utils/images/iconoverlay_close_small.png
Binary files differ
diff --git a/src/libs/utils/images/[email protected] b/src/libs/utils/images/[email protected]
new file mode 100644
index 00000000000..e8a02dff03a
--- /dev/null
+++ b/src/libs/utils/images/[email protected]
Binary files differ
diff --git a/src/libs/utils/images/pinned_small.png b/src/libs/utils/images/pinned_small.png
new file mode 100644
index 00000000000..10f96f30954
--- /dev/null
+++ b/src/libs/utils/images/pinned_small.png
Binary files differ
diff --git a/src/libs/utils/images/[email protected] b/src/libs/utils/images/[email protected]
new file mode 100644
index 00000000000..672af736fa5
--- /dev/null
+++ b/src/libs/utils/images/[email protected]
Binary files differ
diff --git a/src/libs/utils/launcherpackets.cpp b/src/libs/utils/launcherpackets.cpp
index 13c3a1560d4..37261224ef5 100644
--- a/src/libs/utils/launcherpackets.cpp
+++ b/src/libs/utils/launcherpackets.cpp
@@ -48,7 +48,8 @@ void StartProcessPacket::doSerialize(QDataStream &stream) const
<< lowPriority
<< unixTerminalDisabled
<< useCtrlCStub
- << reaperTimeout;
+ << reaperTimeout
+ << createConsoleOnWindows;
}
void StartProcessPacket::doDeserialize(QDataStream &stream)
@@ -68,7 +69,8 @@ void StartProcessPacket::doDeserialize(QDataStream &stream)
>> lowPriority
>> unixTerminalDisabled
>> useCtrlCStub
- >> reaperTimeout;
+ >> reaperTimeout
+ >> createConsoleOnWindows;
processMode = Utils::ProcessMode(processModeInt);
processChannelMode = QProcess::ProcessChannelMode(processChannelModeInt);
}
diff --git a/src/libs/utils/launcherpackets.h b/src/libs/utils/launcherpackets.h
index 2f0bae2915e..27e98a74e5b 100644
--- a/src/libs/utils/launcherpackets.h
+++ b/src/libs/utils/launcherpackets.h
@@ -98,6 +98,7 @@ public:
bool unixTerminalDisabled = false;
bool useCtrlCStub = false;
int reaperTimeout = 500;
+ bool createConsoleOnWindows = false;
private:
void doSerialize(QDataStream &stream) const override;
diff --git a/src/libs/utils/launchersocket.cpp b/src/libs/utils/launchersocket.cpp
index 07ebc84df9a..ccf2ee7b2f4 100644
--- a/src/libs/utils/launchersocket.cpp
+++ b/src/libs/utils/launchersocket.cpp
@@ -246,6 +246,8 @@ void CallerHandle::start(const QString &program, const QStringList &arguments)
p.command = m_command;
p.arguments = m_arguments;
p.env = m_setup->m_environment.toStringList();
+ if (p.env.isEmpty())
+ p.env = Environment::systemEnvironment().toStringList();
p.workingDir = m_setup->m_workingDirectory.path();
p.processMode = m_setup->m_processMode;
p.writeData = m_setup->m_writeData;
@@ -257,6 +259,7 @@ void CallerHandle::start(const QString &program, const QStringList &arguments)
p.unixTerminalDisabled = m_setup->m_unixTerminalDisabled;
p.useCtrlCStub = m_setup->m_useCtrlCStub;
p.reaperTimeout = m_setup->m_reaperTimeout;
+ p.createConsoleOnWindows = m_setup->m_createConsoleOnWindows;
sendPacket(p);
}
diff --git a/src/libs/utils/link.h b/src/libs/utils/link.h
index 6f01b484348..00194654c97 100644
--- a/src/libs/utils/link.h
+++ b/src/libs/utils/link.h
@@ -17,7 +17,8 @@ namespace Utils {
class QTCREATOR_UTILS_EXPORT Link
{
public:
- Link(const FilePath &filePath = FilePath(), int line = 0, int column = 0)
+ Link() = default;
+ Link(const FilePath &filePath, int line = 0, int column = 0)
: targetFilePath(filePath)
, targetLine(line)
, targetColumn(column)
@@ -26,7 +27,11 @@ public:
static Link fromString(const QString &filePathWithNumbers, bool canContainLineNumber = false);
bool hasValidTarget() const
- { return !targetFilePath.isEmpty(); }
+ {
+ if (!targetFilePath.isEmpty())
+ return true;
+ return !targetFilePath.scheme().isEmpty() || !targetFilePath.host().isEmpty();
+ }
bool hasValidLinkText() const
{ return linkTextStart != linkTextEnd; }
@@ -48,8 +53,8 @@ public:
int linkTextEnd = -1;
FilePath targetFilePath;
- int targetLine;
- int targetColumn;
+ int targetLine = 0;
+ int targetColumn = 0;
};
using LinkHandler = std::function<void(const Link &)>;
@@ -58,3 +63,12 @@ using Links = QList<Link>;
} // namespace Utils
Q_DECLARE_METATYPE(Utils::Link)
+
+namespace std {
+
+template<> struct hash<Utils::Link>
+{
+ size_t operator()(const Utils::Link &fn) const { return qHash(fn); }
+};
+
+} // std
diff --git a/src/libs/utils/multitextcursor.cpp b/src/libs/utils/multitextcursor.cpp
index 2f598066eeb..934ca17ed37 100644
--- a/src/libs/utils/multitextcursor.cpp
+++ b/src/libs/utils/multitextcursor.cpp
@@ -16,33 +16,130 @@ namespace Utils {
MultiTextCursor::MultiTextCursor() {}
MultiTextCursor::MultiTextCursor(const QList<QTextCursor> &cursors)
- : m_cursors(cursors)
{
- mergeCursors();
+ setCursors(cursors);
+}
+
+void MultiTextCursor::fillMapWithList()
+{
+ m_cursorMap.clear();
+ for (auto it = m_cursorList.begin(); it != m_cursorList.end(); ++it)
+ m_cursorMap[it->selectionStart()] = it;
+}
+
+MultiTextCursor& MultiTextCursor::operator=(const MultiTextCursor &multiCursor)
+{
+ m_cursorList = multiCursor.m_cursorList;
+ fillMapWithList();
+ return *this;
+}
+
+MultiTextCursor::MultiTextCursor(const MultiTextCursor &multiCursor)
+{
+ *this = multiCursor;
+}
+
+MultiTextCursor& MultiTextCursor::operator=(const MultiTextCursor &&multiCursor)
+{
+ m_cursorList = std::move(multiCursor.m_cursorList);
+ fillMapWithList();
+ return *this;
+}
+
+MultiTextCursor::MultiTextCursor(const MultiTextCursor &&multiCursor)
+{
+ *this = std::move(multiCursor);
+}
+
+MultiTextCursor::~MultiTextCursor() = default;
+
+static bool cursorsOverlap(const QTextCursor &c1, const QTextCursor &c2)
+{
+ if (c1.hasSelection()) {
+ if (c2.hasSelection()) {
+ return c2.selectionEnd() > c1.selectionStart()
+ && c2.selectionStart() < c1.selectionEnd();
+ }
+ const int c2Pos = c2.position();
+ return c2Pos > c1.selectionStart() && c2Pos < c1.selectionEnd();
+ }
+ if (c2.hasSelection()) {
+ const int c1Pos = c1.position();
+ return c1Pos > c2.selectionStart() && c1Pos < c2.selectionEnd();
+ }
+ return c1 == c2;
+};
+
+static void mergeCursors(QTextCursor &c1, const QTextCursor &c2)
+{
+ if (c1.position() == c2.position() && c1.anchor() == c2.anchor())
+ return;
+ if (c1.hasSelection()) {
+ if (!c2.hasSelection())
+ return;
+ int pos = c1.position();
+ int anchor = c1.anchor();
+ if (c1.selectionStart() > c2.selectionStart()) {
+ if (pos < anchor)
+ pos = c2.selectionStart();
+ else
+ anchor = c2.selectionStart();
+ }
+ if (c1.selectionEnd() < c2.selectionEnd()) {
+ if (pos < anchor)
+ anchor = c2.selectionEnd();
+ else
+ pos = c2.selectionEnd();
+ }
+ c1.setPosition(anchor);
+ c1.setPosition(pos, QTextCursor::KeepAnchor);
+ } else {
+ c1 = c2;
+ }
}
void MultiTextCursor::addCursor(const QTextCursor &cursor)
{
QTC_ASSERT(!cursor.isNull(), return);
- m_cursors.append(cursor);
- mergeCursors();
+
+ QTextCursor c1 = cursor;
+ const int pos = c1.selectionStart();
+
+ auto found = m_cursorMap.lower_bound(pos);
+ if (found != m_cursorMap.begin())
+ --found;
+
+ for (; !m_cursorMap.empty() && found != m_cursorMap.end()
+ && found->second->selectionStart() <= cursor.selectionEnd();) {
+ const QTextCursor &c2 = *found->second;
+ if (cursorsOverlap(c1, c2)) {
+ Utils::mergeCursors(c1, c2);
+ m_cursorList.erase(found->second);
+ found = m_cursorMap.erase(found);
+ continue;
+ }
+ ++found;
+ }
+
+ m_cursorMap[pos] = m_cursorList.insert(m_cursorList.end(), c1);
}
void MultiTextCursor::addCursors(const QList<QTextCursor> &cursors)
{
- m_cursors.append(cursors);
- mergeCursors();
+ for (const QTextCursor &c : cursors)
+ addCursor(c);
}
void MultiTextCursor::setCursors(const QList<QTextCursor> &cursors)
{
- m_cursors = cursors;
- mergeCursors();
+ m_cursorList.clear();
+ m_cursorMap.clear();
+ addCursors(cursors);
}
const QList<QTextCursor> MultiTextCursor::cursors() const
{
- return m_cursors;
+ return QList<QTextCursor>(m_cursorList.begin(), m_cursorList.end());
}
void MultiTextCursor::replaceMainCursor(const QTextCursor &cursor)
@@ -54,64 +151,72 @@ void MultiTextCursor::replaceMainCursor(const QTextCursor &cursor)
QTextCursor MultiTextCursor::mainCursor() const
{
- if (m_cursors.isEmpty())
+ if (m_cursorList.empty())
return {};
- return m_cursors.last();
+ return m_cursorList.back();
}
QTextCursor MultiTextCursor::takeMainCursor()
{
- if (m_cursors.isEmpty())
+ if (m_cursorList.empty())
return {};
- return m_cursors.takeLast();
+
+ QTextCursor cursor = m_cursorList.back();
+ auto it = m_cursorList.end();
+ --it;
+ m_cursorMap.erase(it->selectionStart());
+ m_cursorList.erase(it);
+
+ return cursor;
}
void MultiTextCursor::beginEditBlock()
{
- QTC_ASSERT(!m_cursors.empty(), return);
- m_cursors.last().beginEditBlock();
+ QTC_ASSERT(!m_cursorList.empty(), return);
+ m_cursorList.back().beginEditBlock();
}
void MultiTextCursor::endEditBlock()
{
- QTC_ASSERT(!m_cursors.empty(), return);
- m_cursors.last().endEditBlock();
+ QTC_ASSERT(!m_cursorList.empty(), return);
+ m_cursorList.back().endEditBlock();
}
bool MultiTextCursor::isNull() const
{
- return m_cursors.isEmpty();
+ return m_cursorList.empty();
}
bool MultiTextCursor::hasMultipleCursors() const
{
- return m_cursors.size() > 1;
+ return m_cursorList.size() > 1;
}
int MultiTextCursor::cursorCount() const
{
- return m_cursors.size();
+ return m_cursorList.size();
}
void MultiTextCursor::movePosition(QTextCursor::MoveOperation operation,
QTextCursor::MoveMode mode,
int n)
{
- for (QTextCursor &cursor : m_cursors)
+ for (auto &cursor : m_cursorList)
cursor.movePosition(operation, mode, n);
+
mergeCursors();
}
bool MultiTextCursor::hasSelection() const
{
- return Utils::anyOf(m_cursors, &QTextCursor::hasSelection);
+ return Utils::anyOf(m_cursorList, &QTextCursor::hasSelection);
}
QString MultiTextCursor::selectedText() const
{
QString text;
- const QList<QTextCursor> cursors = Utils::sorted(m_cursors);
- for (const QTextCursor &cursor : cursors) {
+ for (const auto &element : std::as_const(m_cursorMap)) {
+ const QTextCursor &cursor = *element.second;
const QString &cursorText = cursor.selectedText();
if (cursorText.isEmpty())
continue;
@@ -128,8 +233,8 @@ QString MultiTextCursor::selectedText() const
void MultiTextCursor::removeSelectedText()
{
beginEditBlock();
- for (QTextCursor &c : m_cursors)
- c.removeSelectedText();
+ for (auto cursor = m_cursorList.begin(); cursor != m_cursorList.end(); ++cursor)
+ cursor->removeSelectedText();
endEditBlock();
mergeCursors();
}
@@ -149,25 +254,27 @@ static void insertAndSelect(QTextCursor &cursor, const QString &text, bool selec
void MultiTextCursor::insertText(const QString &text, bool selectNewText)
{
- if (m_cursors.isEmpty())
+ if (m_cursorList.empty())
return;
- m_cursors.last().beginEditBlock();
+
+ m_cursorList.back().beginEditBlock();
if (hasMultipleCursors()) {
QStringList lines = text.split('\n');
if (!lines.isEmpty() && lines.last().isEmpty())
lines.pop_back();
int index = 0;
- if (lines.count() == m_cursors.count()) {
- QList<QTextCursor> cursors = Utils::sorted(m_cursors);
- for (QTextCursor &cursor : cursors)
+ if (static_cast<long unsigned int>(lines.count()) == m_cursorList.size()) {
+ for (const auto &element : std::as_const(m_cursorMap)) {
+ QTextCursor &cursor = *element.second;
insertAndSelect(cursor, lines.at(index++), selectNewText);
- m_cursors.last().endEditBlock();
+ }
+ m_cursorList.back().endEditBlock();
return;
}
}
- for (QTextCursor &cursor : m_cursors)
- insertAndSelect(cursor, text, selectNewText);
- m_cursors.last().endEditBlock();
+ for (auto cursor = m_cursorList.begin(); cursor != m_cursorList.end(); ++cursor)
+ insertAndSelect(*cursor, text, selectNewText);
+ m_cursorList.back().endEditBlock();
}
bool equalCursors(const QTextCursor &lhs, const QTextCursor &rhs)
@@ -177,17 +284,21 @@ bool equalCursors(const QTextCursor &lhs, const QTextCursor &rhs)
bool MultiTextCursor::operator==(const MultiTextCursor &other) const
{
- if (m_cursors.size() != other.m_cursors.size())
+ if (m_cursorList.size() != other.m_cursorList.size())
return false;
- if (m_cursors.isEmpty())
+ if (m_cursorList.empty())
return true;
- QList<QTextCursor> thisCursors = m_cursors;
- QList<QTextCursor> otherCursors = other.m_cursors;
- if (!equalCursors(thisCursors.takeLast(), otherCursors.takeLast()))
+
+ if (!equalCursors(m_cursorList.back(), other.m_cursorList.back()))
return false;
- for (const QTextCursor &oc : otherCursors) {
- auto compare = [oc](const QTextCursor &c) { return equalCursors(oc, c); };
- if (!Utils::contains(thisCursors, compare))
+
+ auto it = m_cursorMap.begin();
+ auto otherIt = other.m_cursorMap.begin();
+ for (;it != m_cursorMap.end() && otherIt != other.m_cursorMap.end(); ++it, ++otherIt) {
+ const QTextCursor &cursor = *it->second;
+ const QTextCursor &otherCursor = *otherIt->second;
+ if (it->first != otherIt->first || cursor != otherCursor
+ || cursor.anchor() != otherCursor.anchor())
return false;
}
return true;
@@ -198,70 +309,10 @@ bool MultiTextCursor::operator!=(const MultiTextCursor &other) const
return !operator==(other);
}
-static bool cursorsOverlap(const QTextCursor &c1, const QTextCursor &c2)
-{
- if (c1.hasSelection()) {
- if (c2.hasSelection()) {
- return c2.selectionEnd() > c1.selectionStart()
- && c2.selectionStart() < c1.selectionEnd();
- }
- const int c2Pos = c2.position();
- return c2Pos > c1.selectionStart() && c2Pos < c1.selectionEnd();
- }
- if (c2.hasSelection()) {
- const int c1Pos = c1.position();
- return c1Pos > c2.selectionStart() && c1Pos < c2.selectionEnd();
- }
- return c1 == c2;
-};
-
-static void mergeCursors(QTextCursor &c1, const QTextCursor &c2)
-{
- if (c1.position() == c2.position() && c1.anchor() == c2.anchor())
- return;
- if (c1.hasSelection()) {
- if (!c2.hasSelection())
- return;
- int pos = c1.position();
- int anchor = c1.anchor();
- if (c1.selectionStart() > c2.selectionStart()) {
- if (pos < anchor)
- pos = c2.selectionStart();
- else
- anchor = c2.selectionStart();
- }
- if (c1.selectionEnd() < c2.selectionEnd()) {
- if (pos < anchor)
- anchor = c2.selectionEnd();
- else
- pos = c2.selectionEnd();
- }
- c1.setPosition(anchor);
- c1.setPosition(pos, QTextCursor::KeepAnchor);
- } else {
- c1 = c2;
- }
-}
-
void MultiTextCursor::mergeCursors()
{
- std::list<QTextCursor> cursors(m_cursors.begin(), m_cursors.end());
- cursors = Utils::filtered(cursors, [](const QTextCursor &c){
- return !c.isNull();
- });
- for (auto it = cursors.begin(); it != cursors.end(); ++it) {
- QTextCursor &c1 = *it;
- for (auto other = std::next(it); other != cursors.end();) {
- const QTextCursor &c2 = *other;
- if (cursorsOverlap(c1, c2)) {
- Utils::mergeCursors(c1, c2);
- other = cursors.erase(other);
- continue;
- }
- ++other;
- }
- }
- m_cursors = QList<QTextCursor>(cursors.begin(), cursors.end());
+ QList<QTextCursor> cursors(m_cursorList.begin(), m_cursorList.end());
+ setCursors(cursors);
}
// could go into QTextCursor...
@@ -321,7 +372,7 @@ bool MultiTextCursor::handleMoveKeyEvent(QKeyEvent *e,
return false;
}
- const QList<QTextCursor> cursors = m_cursors;
+ const std::list<QTextCursor> cursors = m_cursorList;
for (QTextCursor cursor : cursors) {
if (camelCaseNavigationEnabled && op == QTextCursor::WordRight)
CamelCaseCursor::right(&cursor, edit, QTextCursor::MoveAnchor);
@@ -329,14 +380,14 @@ bool MultiTextCursor::handleMoveKeyEvent(QKeyEvent *e,
CamelCaseCursor::left(&cursor, edit, QTextCursor::MoveAnchor);
else
cursor.movePosition(op, QTextCursor::MoveAnchor);
- m_cursors << cursor;
- }
- mergeCursors();
+ addCursor(cursor);
+ }
return true;
}
- for (QTextCursor &cursor : m_cursors) {
+ for (auto it = m_cursorList.begin(); it != m_cursorList.end(); ++it) {
+ QTextCursor &cursor = *it;
QTextCursor::MoveMode mode = QTextCursor::MoveAnchor;
QTextCursor::MoveOperation op = QTextCursor::NoMove;
diff --git a/src/libs/utils/multitextcursor.h b/src/libs/utils/multitextcursor.h
index 390082e63ad..b2565f931ff 100644
--- a/src/libs/utils/multitextcursor.h
+++ b/src/libs/utils/multitextcursor.h
@@ -21,15 +21,22 @@ public:
MultiTextCursor();
explicit MultiTextCursor(const QList<QTextCursor> &cursors);
- /// replace all cursors with \param cursors and the last one will be the new main cursors
+ MultiTextCursor(const MultiTextCursor &multiCursor);
+ MultiTextCursor &operator=(const MultiTextCursor &multiCursor);
+ MultiTextCursor(const MultiTextCursor &&multiCursor);
+ MultiTextCursor &operator=(const MultiTextCursor &&multiCursor);
+
+ ~MultiTextCursor();
+
+ /// Replaces all cursors with \param cursors and the last one will be the new main cursors.
void setCursors(const QList<QTextCursor> &cursors);
const QList<QTextCursor> cursors() const;
- /// \returns whether this multi cursor contains any cursor
+ /// Returns whether this multi cursor contains any cursor.
bool isNull() const;
- /// \returns whether this multi cursor contains more than one cursor
+ /// Returns whether this multi cursor contains more than one cursor.
bool hasMultipleCursors() const;
- /// \returns the number of cursors handled by this cursor
+ /// Returns the number of cursors handled by this cursor.
int cursorCount() const;
/// the \param cursor that is appended by added by \brief addCursor
@@ -39,9 +46,9 @@ public:
/// convenience function that removes the old main cursor and appends
/// \param cursor as the new main cursor
void replaceMainCursor(const QTextCursor &cursor);
- /// \returns the main cursor
+ /// Returns the main cursor.
QTextCursor mainCursor() const;
- /// \returns the main cursor and removes it from this multi cursor
+ /// Returns the main cursor and removes it from this multi cursor.
QTextCursor takeMainCursor();
void beginEditBlock();
@@ -55,10 +62,10 @@ public:
/// with the move \param mode
void movePosition(QTextCursor::MoveOperation operation, QTextCursor::MoveMode mode, int n = 1);
- /// \returns whether any cursor has a selection
+ /// Returns whether any cursor has a selection.
bool hasSelection() const;
- /// \returns the selected text of all cursors that have a selection separated by
- /// a newline character
+ /// Returns the selected text of all cursors that have a selection separated by
+ /// a newline character.
QString selectedText() const;
/// removes the selected text of all cursors that have a selection from the document
void removeSelectedText();
@@ -69,20 +76,23 @@ public:
bool operator==(const MultiTextCursor &other) const;
bool operator!=(const MultiTextCursor &other) const;
- using iterator = QList<QTextCursor>::iterator;
- using const_iterator = QList<QTextCursor>::const_iterator;
+ using iterator = std::list<QTextCursor>::iterator;
+ using const_iterator = std::list<QTextCursor>::const_iterator;
- iterator begin() { return m_cursors.begin(); }
- iterator end() { return m_cursors.end(); }
- const_iterator begin() const { return m_cursors.begin(); }
- const_iterator end() const { return m_cursors.end(); }
- const_iterator constBegin() const { return m_cursors.constBegin(); }
- const_iterator constEnd() const { return m_cursors.constEnd(); }
+ iterator begin() { return m_cursorList.begin(); }
+ iterator end() { return m_cursorList.end(); }
+ const_iterator begin() const { return m_cursorList.begin(); }
+ const_iterator end() const { return m_cursorList.end(); }
+ const_iterator constBegin() const { return m_cursorList.cbegin(); }
+ const_iterator constEnd() const { return m_cursorList.cend(); }
static bool multiCursorAddEvent(QKeyEvent *e, QKeySequence::StandardKey matchKey);
private:
- QList<QTextCursor> m_cursors;
+ std::list<QTextCursor> m_cursorList;
+ std::map<int, std::list<QTextCursor>::iterator> m_cursorMap;
+
+ void fillMapWithList();
};
} // namespace Utils
diff --git a/src/libs/utils/osspecificaspects.h b/src/libs/utils/osspecificaspects.h
index 0cc22efe5fe..c735f313abc 100644
--- a/src/libs/utils/osspecificaspects.h
+++ b/src/libs/utils/osspecificaspects.h
@@ -14,6 +14,36 @@ namespace Utils {
// Add more as needed.
enum OsType { OsTypeWindows, OsTypeLinux, OsTypeMac, OsTypeOtherUnix, OsTypeOther };
+inline QString osTypeToString(OsType osType)
+{
+ switch (osType) {
+ case OsTypeWindows:
+ return "Windows";
+ case OsTypeLinux:
+ return "Linux";
+ case OsTypeMac:
+ return "Mac";
+ case OsTypeOtherUnix:
+ return "Other Unix";
+ case OsTypeOther:
+ default:
+ return "Other";
+ }
+}
+
+inline OsType osTypeFromString(const QString &string)
+{
+ if (string == "Windows")
+ return OsTypeWindows;
+ if (string == "Linux")
+ return OsTypeLinux;
+ if (string == "Mac")
+ return OsTypeMac;
+ if (string == "Other Unix")
+ return OsTypeOtherUnix;
+ return OsTypeOther;
+}
+
namespace OsSpecificAspects {
inline QString withExecutableSuffix(OsType osType, const QString &executable)
diff --git a/src/libs/utils/pathchooser.cpp b/src/libs/utils/pathchooser.cpp
index 53825931b97..0f0737f54c5 100644
--- a/src/libs/utils/pathchooser.cpp
+++ b/src/libs/utils/pathchooser.cpp
@@ -171,7 +171,7 @@ public:
FilePath m_initialBrowsePathOverride;
QString m_defaultValue;
FilePath m_baseDirectory;
- EnvironmentChange m_environmentChange;
+ Environment m_environment;
BinaryVersionToolTipEventFilter *m_binaryVersionToolTipEventFilter = nullptr;
QList<QAbstractButton *> m_buttons;
const MacroExpander *m_macroExpander = globalMacroExpander();
@@ -196,8 +196,7 @@ FilePath PathChooserPrivate::expandedPath(const FilePath &input) const
FilePath path = input;
- Environment env = path.deviceEnvironment();
- m_environmentChange.applyToEnvironment(env);
+ Environment env = m_environment.appliedToEnvironment(path.deviceEnvironment());
path = env.expandVariables(path);
if (m_macroExpander)
@@ -324,20 +323,15 @@ void PathChooser::setBaseDirectory(const FilePath &base)
triggerChanged();
}
-void PathChooser::setEnvironment(const Environment &env)
-{
- setEnvironmentChange(EnvironmentChange::fromDictionary(env.toDictionary()));
-}
-
FilePath PathChooser::baseDirectory() const
{
return d->m_baseDirectory;
}
-void PathChooser::setEnvironmentChange(const EnvironmentChange &env)
+void PathChooser::setEnvironment(const Environment &env)
{
QString oldExpand = filePath().toString();
- d->m_environmentChange = env;
+ d->m_environment = env;
if (filePath().toString() != oldExpand) {
triggerChanged();
emit rawPathChanged();
diff --git a/src/libs/utils/pathchooser.h b/src/libs/utils/pathchooser.h
index 92b5973b2de..62a370185da 100644
--- a/src/libs/utils/pathchooser.h
+++ b/src/libs/utils/pathchooser.h
@@ -20,7 +20,6 @@ namespace Utils {
class CommandLine;
class MacroExpander;
class Environment;
-class EnvironmentChange;
class PathChooserPrivate;
class QTCREATOR_UTILS_EXPORT PathChooser : public QWidget
@@ -77,7 +76,6 @@ public:
void setBaseDirectory(const FilePath &base);
void setEnvironment(const Environment &env);
- void setEnvironmentChange(const EnvironmentChange &change);
/** Returns the suggested label title when used in a form layout. */
static QString label();
diff --git a/src/libs/utils/process_ctrlc_stub.cpp b/src/libs/utils/process_ctrlc_stub.cpp
index bbfdf7ff14d..b924e74e49d 100644
--- a/src/libs/utils/process_ctrlc_stub.cpp
+++ b/src/libs/utils/process_ctrlc_stub.cpp
@@ -44,7 +44,7 @@ public:
fwprintf(stderr, L"qtcreator_ctrlc_stub: CreateJobObject failed: 0x%x.\n", GetLastError());
return;
}
- JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = {0};
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli = {};
jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
if (!SetInformationJobObject(m_job, JobObjectExtendedLimitInformation, &jeli, sizeof(jeli))) {
fwprintf(stderr, L"qtcreator_ctrlc_stub: SetInformationJobObject failed: 0x%x.\n", GetLastError());
@@ -85,7 +85,7 @@ int main(int argc, char **)
uiShutDownWindowMessage = RegisterWindowMessage(L"qtcctrlcstub_shutdown");
uiInterruptMessage = RegisterWindowMessage(L"qtcctrlcstub_interrupt");
- WNDCLASSEX wcex = {0};
+ WNDCLASSEX wcex = {};
wcex.cbSize = sizeof(wcex);
wcex.lpfnWndProc = WndProc;
wcex.hInstance = GetModuleHandle(nullptr);
@@ -188,14 +188,14 @@ DWORD WINAPI processWatcherThread(LPVOID lpParameter)
bool startProcess(wchar_t *pCommandLine, bool lowerPriority, const JobKillOnClose& job)
{
- SECURITY_ATTRIBUTES sa = {0};
+ SECURITY_ATTRIBUTES sa = {};
sa.nLength = sizeof(sa);
sa.bInheritHandle = TRUE;
- STARTUPINFO si = {0};
+ STARTUPINFO si = {};
si.cb = sizeof(si);
- PROCESS_INFORMATION pi;
+ PROCESS_INFORMATION pi = {};
DWORD dwCreationFlags = lowerPriority ? BELOW_NORMAL_PRIORITY_CLASS : 0;
BOOL bSuccess = CreateProcess(NULL, pCommandLine, &sa, &sa, TRUE, dwCreationFlags, NULL, NULL, &si, &pi);
if (!bSuccess) {
diff --git a/src/libs/utils/process_stub.qbs b/src/libs/utils/process_stub.qbs
deleted file mode 100644
index 341fb57791b..00000000000
--- a/src/libs/utils/process_stub.qbs
+++ /dev/null
@@ -1,21 +0,0 @@
-import qbs 1.0
-
-QtcTool {
- name: "qtcreator_process_stub"
- consoleApplication: true
-
-
- files: {
- if (qbs.targetOS.contains("windows")) {
- return [ "process_stub_win.c" ]
- } else {
- return [ "process_stub_unix.c" ]
- }
- }
-
- cpp.dynamicLibraries: {
- if (qbs.targetOS.contains("windows")) {
- return [ "shell32" ]
- }
- }
-}
diff --git a/src/libs/utils/process_stub_unix.c b/src/libs/utils/process_stub_unix.c
deleted file mode 100644
index 716e88d1012..00000000000
--- a/src/libs/utils/process_stub_unix.c
+++ /dev/null
@@ -1,340 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-#include <sys/wait.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <signal.h>
-#include <string.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <errno.h>
-#include <assert.h>
-
-#ifdef __linux__
-#include <sys/prctl.h>
-
-// Enable compilation with older header that doesn't contain this constant
-// for running on newer libraries that do support it
-#ifndef PR_SET_PTRACER
-#define PR_SET_PTRACER 0x59616d61
-#endif
-#endif
-
-/* For OpenBSD */
-#ifndef EPROTO
-# define EPROTO EINVAL
-#endif
-
-extern char **environ;
-
-static int qtcFd;
-static char *sleepMsg;
-static int chldPipe[2];
-static int blockingPipe[2];
-static int isDebug;
-static volatile int isDetached;
-static volatile int chldPid;
-
-static void __attribute__((noreturn)) doExit(int code)
-{
- tcsetpgrp(0, getpid());
- puts(sleepMsg);
- const char *rv = fgets(sleepMsg, 2, stdin); /* Minimal size to make it wait */
- (void)rv; // Q_UNUSED
- exit(code);
-}
-
-static void sendMsg(const char *msg, int num)
-{
- int pidStrLen;
- int ioRet;
- char pidStr[64];
-
- pidStrLen = sprintf(pidStr, msg, num);
- if (!isDetached && (ioRet = write(qtcFd, pidStr, pidStrLen)) != pidStrLen) {
- fprintf(stderr, "Cannot write to creator comm socket: %s\n",
- (ioRet < 0) ? strerror(errno) : "short write");
- isDetached = 2;
- }
-}
-
-enum {
- ArgCmd = 0,
- ArgAction,
- ArgSocket,
- ArgMsg,
- ArgDir,
- ArgEnv,
- ArgPid,
- ArgExe
-};
-
-/* Handle sigchld */
-static void sigchldHandler(int sig)
-{
- int chldStatus;
- /* Currently we have only one child, so we exit in case of error. */
- int waitRes;
- (void)sig;
- for (;;) {
- waitRes = waitpid(-1, &chldStatus, WNOHANG);
- if (!waitRes)
- break;
- if (waitRes < 0) {
- perror("Cannot obtain exit status of child process");
- doExit(3);
- }
- if (WIFSTOPPED(chldStatus)) {
- /* The child stopped. This can be the result of the initial SIGSTOP handling.
- * We won't need the notification pipe any more, as we know that
- * the exec() succeeded. */
- close(chldPipe[0]);
- close(chldPipe[1]);
- chldPipe[0] = -1;
- if (isDetached == 2 && isDebug) {
- /* qtcreator was not informed and died while debugging, killing the child */
- kill(chldPid, SIGKILL);
- }
- } else if (WIFEXITED(chldStatus)) {
- sendMsg("exit %d\n", WEXITSTATUS(chldStatus));
- doExit(0);
- } else {
- sendMsg("crash %d\n", WTERMSIG(chldStatus));
- doExit(0);
- }
- }
-}
-
-
-/* syntax: $0 {"run"|"debug"} <pid-socket> <continuation-msg> <workdir> <env-file> <exe> <args...> */
-/* exit codes: 0 = ok, 1 = invocation error, 3 = internal error */
-int main(int argc, char *argv[])
-{
- int errNo, hadInvalidCommand = 0;
- char **env = 0;
- struct sockaddr_un sau;
- struct sigaction act;
-
- memset(&act, 0, sizeof(act));
-
- if (argc < ArgEnv) {
- fprintf(stderr, "This is an internal helper of Qt Creator. Do not run it manually.\n");
- return 1;
- }
- sleepMsg = argv[ArgMsg];
-
- /* Connect to the master, i.e. Creator. */
- if ((qtcFd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) {
- perror("Cannot create creator comm socket");
- doExit(3);
- }
- memset(&sau, 0, sizeof(sau));
- sau.sun_family = AF_UNIX;
- strncpy(sau.sun_path, argv[ArgSocket], sizeof(sau.sun_path) - 1);
- if (connect(qtcFd, (struct sockaddr *)&sau, sizeof(sau))) {
- fprintf(stderr, "Cannot connect creator comm socket %s: %s\n", sau.sun_path, strerror(errno));
- doExit(1);
- }
-
- isDebug = !strcmp(argv[ArgAction], "debug");
- isDetached = 0;
-
- if (*argv[ArgDir] && chdir(argv[ArgDir])) {
- /* Only expected error: no such file or direcotry */
- sendMsg("err:chdir %d\n", errno);
- return 1;
- }
-
- if (*argv[ArgEnv]) {
- FILE *envFd;
- char *envdata, *edp, *termEnv;
- long size;
- int count;
- if (!(envFd = fopen(argv[ArgEnv], "r"))) {
- fprintf(stderr, "Cannot read creator env file %s: %s\n",
- argv[ArgEnv], strerror(errno));
- doExit(1);
- }
- fseek(envFd, 0, SEEK_END);
- size = ftell(envFd);
- if (size < 0) {
- perror("Failed to get size of env file");
- doExit(1);
- }
- rewind(envFd);
- envdata = malloc(size + 1);
- if (fread(envdata, 1, size, envFd) != (size_t)size) {
- perror("Failed to read env file");
- doExit(1);
- }
- envdata[size] = '\0';
- fclose(envFd);
- assert(!size || !envdata[size - 1]);
- for (count = 0, edp = envdata; edp < envdata + size; ++count)
- edp += strlen(edp) + 1;
- env = malloc((count + 2) * sizeof(char *));
- for (count = 0, edp = envdata; edp < envdata + size; ++count) {
- env[count] = edp;
- edp += strlen(edp) + 1;
- }
- if ((termEnv = getenv("TERM")))
- env[count++] = termEnv - 5;
- env[count] = 0;
- }
-
- /* send our pid after we read the environment file (creator will get rid of it) */
- sendMsg("spid %ld\n", (long)getpid());
-
- /*
- * set up the signal handlers
- */
- {
- /* Ignore SIGTTOU. Without this, calling tcsetpgrp() from a background
- * process group (in which we will be, once as child and once as parent)
- * generates the mentioned signal and stops the concerned process. */
- act.sa_handler = SIG_IGN;
- if (sigaction(SIGTTOU, &act, 0)) {
- perror("sigaction SIGTTOU");
- doExit(3);
- }
-
- /* Handle SIGCHLD to keep track of what the child does without blocking */
- act.sa_handler = sigchldHandler;
- if (sigaction(SIGCHLD, &act, 0)) {
- perror("sigaction SIGCHLD");
- doExit(3);
- }
- }
-
- /* Create execution result notification pipe. */
- if (pipe(chldPipe)) {
- perror("Cannot create status pipe");
- doExit(3);
- }
-
- /* The debugged program is not supposed to inherit these handles. But we cannot
- * close the writing end before calling exec(). Just handle both ends the same way ... */
- fcntl(chldPipe[0], F_SETFD, FD_CLOEXEC);
- fcntl(chldPipe[1], F_SETFD, FD_CLOEXEC);
-
- if (isDebug) {
- /* Create execution start notification pipe. The child waits on this until
- the parent writes to it, triggered by an 'c' message from Creator */
- if (pipe(blockingPipe)) {
- perror("Cannot create blocking pipe");
- doExit(3);
- }
- }
-
- switch ((chldPid = fork())) {
- case -1:
- perror("Cannot fork child process");
- doExit(3);
- case 0:
- close(qtcFd);
-
- /* Remove the SIGCHLD handler from the child */
- act.sa_handler = SIG_DFL;
- sigaction(SIGCHLD, &act, 0);
-
- /* Put the process into an own process group and make it the foregroud
- * group on this terminal, so it will receive ctrl-c events, etc.
- * This is the main reason for *all* this stub magic in the first place. */
- /* If one of these calls fails, the world is about to end anyway, so
- * don't bother checking the return values. */
- setpgid(0, 0);
- tcsetpgrp(0, getpid());
-
-#ifdef __linux__
- prctl(PR_SET_PTRACER, atoi(argv[ArgPid]));
-#endif
- /* Block to allow the debugger to attach */
- if (isDebug) {
- char buf;
- int res = read(blockingPipe[0], &buf, 1);
- if (res < 0)
- perror("Could not read from blocking pipe");
- close(blockingPipe[0]);
- close(blockingPipe[1]);
- }
-
- if (env)
- environ = env;
-
- execvp(argv[ArgExe], argv + ArgExe);
- /* Only expected error: no such file or direcotry, i.e. executable not found */
- errNo = errno;
- /* Only realistic error case is SIGPIPE */
- if (write(chldPipe[1], &errNo, sizeof(errNo)) != sizeof(errNo))
- perror("Error passing errno to child");
- _exit(0);
- default:
- sendMsg("pid %d\n", chldPid);
- for (;;) {
- char buffer[100];
- int nbytes;
-
- nbytes = read(qtcFd, buffer, 100);
- if (nbytes <= 0) {
- if (nbytes < 0 && errno == EINTR)
- continue;
- if (!isDetached) {
- isDetached = 2;
- if (nbytes == 0)
- fprintf(stderr, "Lost connection to QtCreator, detaching from it.\n");
- else
- perror("Lost connection to QtCreator, detaching from it");
- }
- break;
- } else {
- int i;
- char c = 'i';
- for (i = 0; i < nbytes; ++i) {
- switch (buffer[i]) {
- case 'k':
- if (chldPid > 0) {
- kill(chldPid, SIGTERM);
- sleep(1);
- kill(chldPid, SIGKILL);
- }
- break;
- case 'i':
- if (chldPid > 0) {
- int res = kill(chldPid, SIGINT);
- if (res)
- perror("Stub could not interrupt inferior");
- }
- break;
- case 'c': {
- int res = write(blockingPipe[1], &c, 1);
- if (res < 0)
- perror("Could not write to blocking pipe");
- break;
- }
- case 'd':
- isDetached = 1;
- break;
- case 's':
- exit(0);
- default:
- if (!hadInvalidCommand) {
- fprintf(stderr, "Ignoring invalid commands from QtCreator.\n");
- hadInvalidCommand = 1;
- }
- }
- }
- }
- }
- if (isDetached) {
- for (;;)
- pause(); /* will exit in the signal handler... */
- }
- }
- assert(0);
- return 0;
-}
diff --git a/src/libs/utils/process_stub_win.c b/src/libs/utils/process_stub_win.c
deleted file mode 100644
index 09f220a6c65..00000000000
--- a/src/libs/utils/process_stub_win.c
+++ /dev/null
@@ -1,204 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#undef _WIN32_WINNT
-#define _WIN32_WINNT 0x0501 /* WinXP, needed for DebugActiveProcessStop() */
-
-#include <windows.h>
-#include <shellapi.h>
-#include <string.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <errno.h>
-#include <direct.h>
-
-static FILE *qtcFd;
-static wchar_t *sleepMsg;
-
-enum RunMode { Run, Debug, Suspend };
-
-/* Print some "press enter" message, wait for that, exit. */
-static void doExit(int code)
-{
- char buf[2];
- _putws(sleepMsg);
- fgets(buf, 2, stdin); /* Minimal size to make it wait */
- exit(code);
-}
-
-/* Print an error message for unexpected Windows system errors, wait, exit. */
-static void systemError(const char *str)
-{
- fprintf(stderr, str, GetLastError());
- doExit(3);
-}
-
-/* Send a message to the master. */
-static void sendMsg(const char *msg, int num)
-{
- int pidStrLen;
- char pidStr[64];
-
- pidStrLen = sprintf(pidStr, msg, num);
- if (fwrite(pidStr, pidStrLen, 1, qtcFd) != 1 || fflush(qtcFd)) {
- fprintf(stderr, "Cannot write to creator comm socket: %s\n",
- strerror(errno));
- doExit(3);
- }
-}
-
-/* Ignore the first ctrl-c/break within a second. */
-static BOOL WINAPI ctrlHandler(DWORD dwCtrlType)
-{
- static ULARGE_INTEGER lastTime;
- ULARGE_INTEGER thisTime;
- SYSTEMTIME sysTime;
- FILETIME fileTime;
-
- if (dwCtrlType == CTRL_C_EVENT || dwCtrlType == CTRL_BREAK_EVENT) {
- GetSystemTime(&sysTime);
- SystemTimeToFileTime(&sysTime, &fileTime);
- thisTime.LowPart = fileTime.dwLowDateTime;
- thisTime.HighPart = fileTime.dwHighDateTime;
- if (lastTime.QuadPart + 10000000 < thisTime.QuadPart) {
- lastTime.QuadPart = thisTime.QuadPart;
- return TRUE;
- }
- }
- return FALSE;
-}
-
-enum {
- ArgCmd = 0,
- ArgAction,
- ArgSocket,
- ArgDir,
- ArgEnv,
- ArgCmdLine,
- ArgMsg,
- ArgCount
-};
-
-/* syntax: $0 {"run"|"debug"} <pid-socket> <workdir> <env-file> <cmdline> <continuation-msg> */
-/* exit codes: 0 = ok, 1 = invocation error, 3 = internal error */
-int main()
-{
- int argc;
- int creationFlags;
- wchar_t **argv;
- wchar_t *env = 0;
- STARTUPINFOW si;
- PROCESS_INFORMATION pi;
- DEBUG_EVENT dbev;
- enum RunMode mode = Run;
- HANDLE image = NULL;
-
- argv = CommandLineToArgvW(GetCommandLine(), &argc);
-
- if (argc != ArgCount) {
- fprintf(stderr, "This is an internal helper of Qt Creator. Do not run it manually.\n");
- return 1;
- }
- sleepMsg = argv[ArgMsg];
-
- /* Connect to the master, i.e. Creator. */
- if (!(qtcFd = _wfopen(argv[ArgSocket], L"w"))) {
- fprintf(stderr, "Cannot connect creator comm pipe %S: %s\n",
- argv[ArgSocket], strerror(errno));
- doExit(1);
- }
-
- if (*argv[ArgDir] && !SetCurrentDirectoryW(argv[ArgDir])) {
- /* Only expected error: no such file or direcotry */
- sendMsg("err:chdir %d\n", GetLastError());
- return 1;
- }
-
- if (*argv[ArgEnv]) {
- FILE *envFd;
- long size;
- if (!(envFd = _wfopen(argv[ArgEnv], L"rb"))) {
- fprintf(stderr, "Cannot read creator env file %S: %s\n",
- argv[ArgEnv], strerror(errno));
- doExit(1);
- }
- fseek(envFd, 0, SEEK_END);
- size = ftell(envFd);
- rewind(envFd);
- env = malloc(size);
- if (fread(env, 1, size, envFd) != size) {
- perror("Failed to read env file");
- doExit(1);
- }
- fclose(envFd);
- }
-
- ZeroMemory(&pi, sizeof(pi));
- ZeroMemory(&si, sizeof(si));
- si.cb = sizeof(si);
-
- creationFlags = CREATE_UNICODE_ENVIRONMENT;
- if (!wcscmp(argv[ArgAction], L"debug")) {
- mode = Debug;
- } else if (!wcscmp(argv[ArgAction], L"suspend")) {
- mode = Suspend;
- }
-
- switch (mode) {
- case Debug:
- creationFlags |= DEBUG_ONLY_THIS_PROCESS;
- break;
- case Suspend:
- creationFlags |= CREATE_SUSPENDED;
- break;
- default:
- break;
- }
- if (!CreateProcessW(0, argv[ArgCmdLine], 0, 0, FALSE, creationFlags, env, 0, &si, &pi)) {
- /* Only expected error: no such file or direcotry, i.e. executable not found */
- sendMsg("err:exec %d\n", GetLastError());
- doExit(1);
- }
-
- /* This is somewhat convoluted. What we actually want is creating a
- suspended process and letting gdb attach to it. Unfortunately,
- the Windows kernel runs amok when we attempt this.
- So instead we start a debugged process, eat all the initial
- debug events, suspend the process and detach from it. If gdb
- tries to attach *now*, everything goes smoothly. Yay. */
- if (mode == Debug) {
- do {
- if (!WaitForDebugEvent (&dbev, INFINITE))
- systemError("Cannot fetch debug event, error %d\n");
- if (dbev.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT)
- image = dbev.u.CreateProcessInfo.hFile;
- if (dbev.dwDebugEventCode == EXCEPTION_DEBUG_EVENT) {
- /* The first exception to be delivered is a trap
- which indicates completion of startup. */
- if (SuspendThread(pi.hThread) == (DWORD)-1)
- systemError("Cannot suspend debugee, error %d\n");
- }
- if (!ContinueDebugEvent(dbev.dwProcessId, dbev.dwThreadId, DBG_CONTINUE))
- systemError("Cannot continue debug event, error %d\n");
- } while (dbev.dwDebugEventCode != EXCEPTION_DEBUG_EVENT);
- if (!DebugActiveProcessStop(dbev.dwProcessId))
- systemError("Cannot detach from debugee, error %d\n");
- if (image)
- CloseHandle(image);
- }
-
- SetConsoleCtrlHandler(ctrlHandler, TRUE);
-
- sendMsg("thread %d\n", pi.dwThreadId);
- sendMsg("pid %d\n", pi.dwProcessId);
-
- if (WaitForSingleObject(pi.hProcess, INFINITE) == WAIT_FAILED)
- systemError("Wait for debugee failed, error %d\n");
-
- /* Don't close the process/thread handles, so that the kernel doesn't free
- the resources before ConsoleProcess is able to obtain handles to them
- - this would be a problem if the child process exits very quickly. */
- doExit(0);
-
- return 0;
-}
diff --git a/src/libs/utils/processenums.h b/src/libs/utils/processenums.h
index 6ea37a2d37a..5ce782cd946 100644
--- a/src/libs/utils/processenums.h
+++ b/src/libs/utils/processenums.h
@@ -26,7 +26,6 @@ enum class TerminalMode {
Off,
Run,
Debug,
- Suspend,
On = Run // Default mode for terminal set to on
};
diff --git a/src/libs/utils/processinfo.cpp b/src/libs/utils/processinfo.cpp
index 366800d3161..d6ecd4d2344 100644
--- a/src/libs/utils/processinfo.cpp
+++ b/src/libs/utils/processinfo.cpp
@@ -3,14 +3,13 @@
#include "processinfo.h"
+#include "algorithm.h"
#include "qtcprocess.h"
-#if defined(Q_OS_UNIX)
#include <QDir>
-#include <signal.h>
-#include <errno.h>
-#include <string.h>
-#include <unistd.h>
+#include <QRegularExpression>
+
+#if defined(Q_OS_UNIX)
#elif defined(Q_OS_WIN)
#include "winutils.h"
#ifdef QTCREATOR_PCH_H
@@ -32,82 +31,64 @@ bool ProcessInfo::operator<(const ProcessInfo &other) const
return commandLine < other.commandLine;
}
-#if defined(Q_OS_UNIX)
+// Determine UNIX processes by reading "/proc". Default to ps if
+// it does not exist
-static bool isUnixProcessId(const QString &procname)
+static QList<ProcessInfo> getLocalProcessesUsingProc(const FilePath &procDir)
{
- for (int i = 0; i != procname.size(); ++i)
- if (!procname.at(i).isDigit())
- return false;
- return true;
-}
+ static const QString execs = "-exec test -f {}/exe \\; "
+ "-exec test -f {}/cmdline \\; "
+ "-exec echo -en 'p{}\\ne' \\; "
+ "-exec readlink {}/exe \\; "
+ "-exec echo -n c \\; "
+ "-exec head -n 1 {}/cmdline \\; "
+ "-exec echo \\; "
+ "-exec echo __SKIP_ME__ \\;";
-// Determine UNIX processes by reading "/proc". Default to ps if
-// it does not exist
+ CommandLine cmd{procDir.withNewPath("find"),
+ {procDir.nativePath(), "-maxdepth", "1", "-type", "d", "-name", "[0-9]*"}};
-static const char procDirC[] = "/proc/";
+ cmd.addArgs(execs, CommandLine::Raw);
+
+ QtcProcess procProcess;
+ procProcess.setCommand(cmd);
+ procProcess.runBlocking();
-static QList<ProcessInfo> getLocalProcessesUsingProc()
-{
QList<ProcessInfo> processes;
- const QString procDirPath = QLatin1String(procDirC);
- const QDir procDir = QDir(QLatin1String(procDirC));
- const QStringList procIds = procDir.entryList();
- for (const QString &procId : procIds) {
- if (!isUnixProcessId(procId))
- continue;
- ProcessInfo proc;
- proc.processId = procId.toInt();
- const QString root = procDirPath + procId;
-
- const QFile exeFile(root + QLatin1String("/exe"));
- proc.executable = exeFile.symLinkTarget();
-
- QFile cmdLineFile(root + QLatin1String("/cmdline"));
- if (cmdLineFile.open(QIODevice::ReadOnly)) { // process may have exited
- const QList<QByteArray> tokens = cmdLineFile.readAll().split('\0');
- if (!tokens.isEmpty()) {
- if (proc.executable.isEmpty())
- proc.executable = QString::fromLocal8Bit(tokens.front());
- for (const QByteArray &t : tokens) {
- if (!proc.commandLine.isEmpty())
- proc.commandLine.append(QLatin1Char(' '));
- proc.commandLine.append(QString::fromLocal8Bit(t));
- }
- }
- }
- if (proc.executable.isEmpty()) {
- QFile statFile(root + QLatin1String("/stat"));
- if (statFile.open(QIODevice::ReadOnly)) {
- const QStringList data = QString::fromLocal8Bit(statFile.readAll()).split(QLatin1Char(' '));
- if (data.size() < 2)
- continue;
- proc.executable = data.at(1);
- proc.commandLine = data.at(1); // PPID is element 3
- if (proc.executable.startsWith(QLatin1Char('(')) && proc.executable.endsWith(QLatin1Char(')'))) {
- proc.executable.truncate(proc.executable.size() - 1);
- proc.executable.remove(0, 1);
- }
- }
+ const auto lines = procProcess.readAllStandardOutput().split('\n');
+ for (auto it = lines.begin(); it != lines.end(); ++it) {
+ if (it->startsWith('p')) {
+ ProcessInfo proc;
+ bool ok;
+ proc.processId = FilePath::fromUserInput(it->mid(1).trimmed()).fileName().toInt(&ok);
+ QTC_ASSERT(ok, continue);
+ ++it;
+
+ QTC_ASSERT(it->startsWith('e'), continue);
+ proc.executable = it->mid(1).trimmed();
+ ++it;
+
+ QTC_ASSERT(it->startsWith('c'), continue);
+ proc.commandLine = it->mid(1).trimmed().replace('\0', ' ');
+ if (!proc.commandLine.contains("__SKIP_ME__"))
+ processes.append(proc);
}
- if (!proc.executable.isEmpty())
- processes.push_back(proc);
}
+
return processes;
}
// Determine UNIX processes by running ps
-static QMap<qint64, QString> getLocalProcessDataUsingPs(const QString &column)
+static QMap<qint64, QString> getLocalProcessDataUsingPs(const FilePath &deviceRoot,
+ const QString &column)
{
QtcProcess process;
- process.setCommand({"ps", {"-e", "-o", "pid," + column}});
- process.start();
- if (!process.waitForFinished())
- return {};
+ process.setCommand({deviceRoot.withNewPath("ps"), {"-e", "-o", "pid," + column}});
+ process.runBlocking();
// Split "457 /Users/foo.app arg1 arg2"
- const QStringList lines = process.stdOut().split(QLatin1Char('\n'));
+ const QStringList lines = process.readAllStandardOutput().split(QLatin1Char('\n'));
QMap<qint64, QString> result;
for (int i = 1; i < lines.size(); ++i) { // Skip header
const QString line = lines.at(i).trimmed();
@@ -118,14 +99,14 @@ static QMap<qint64, QString> getLocalProcessDataUsingPs(const QString &column)
return result;
}
-static QList<ProcessInfo> getLocalProcessesUsingPs()
+static QList<ProcessInfo> getLocalProcessesUsingPs(const FilePath &deviceRoot)
{
QList<ProcessInfo> processes;
// cmdLines are full command lines, usually with absolute path,
// exeNames only the file part of the executable's path.
- const QMap<qint64, QString> exeNames = getLocalProcessDataUsingPs("comm");
- const QMap<qint64, QString> cmdLines = getLocalProcessDataUsingPs("args");
+ const QMap<qint64, QString> exeNames = getLocalProcessDataUsingPs(deviceRoot, "comm");
+ const QMap<qint64, QString> cmdLines = getLocalProcessDataUsingPs(deviceRoot, "args");
for (auto it = exeNames.begin(), end = exeNames.end(); it != end; ++it) {
const qint64 pid = it.key();
@@ -146,16 +127,68 @@ static QList<ProcessInfo> getLocalProcessesUsingPs()
return processes;
}
-QList<ProcessInfo> ProcessInfo::processInfoList()
+static QList<ProcessInfo> getProcessesUsingPidin(const FilePath &pidin)
{
- const QDir procDir = QDir(QLatin1String(procDirC));
- return procDir.exists() ? getLocalProcessesUsingProc() : getLocalProcessesUsingPs();
+ QtcProcess process;
+ process.setCommand({pidin, {"-F", "%a %A {/%n}"}});
+ process.runBlocking();
+
+ QList<ProcessInfo> processes;
+ QStringList lines = process.readAllStandardOutput().split(QLatin1Char('\n'));
+ if (lines.isEmpty())
+ return processes;
+
+ lines.pop_front(); // drop headers
+ const QRegularExpression re("\\s*(\\d+)\\s+(.*){(.*)}");
+
+ for (const QString &line : std::as_const(lines)) {
+ const QRegularExpressionMatch match = re.match(line);
+ if (match.hasMatch()) {
+ const QStringList captures = match.capturedTexts();
+ if (captures.size() == 4) {
+ const int pid = captures[1].toInt();
+ const QString args = captures[2];
+ const QString exe = captures[3];
+ ProcessInfo deviceProcess;
+ deviceProcess.processId = pid;
+ deviceProcess.executable = exe.trimmed();
+ deviceProcess.commandLine = args.trimmed();
+ processes.append(deviceProcess);
+ }
+ }
+ }
+
+ return Utils::sorted(std::move(processes));
+}
+
+static QList<ProcessInfo> processInfoListUnix(const FilePath &deviceRoot)
+{
+ const FilePath procDir = deviceRoot.withNewPath("/proc");
+ const FilePath pidin = deviceRoot.withNewPath("pidin").searchInPath();
+
+ if (pidin.isExecutableFile())
+ return getProcessesUsingPidin(pidin);
+
+ if (procDir.isReadableDir())
+ return getLocalProcessesUsingProc(procDir);
+
+ return getLocalProcessesUsingPs(deviceRoot);
+}
+
+#if defined(Q_OS_UNIX)
+
+QList<ProcessInfo> ProcessInfo::processInfoList(const FilePath &deviceRoot)
+{
+ return processInfoListUnix(deviceRoot);
}
#elif defined(Q_OS_WIN)
-QList<ProcessInfo> ProcessInfo::processInfoList()
+QList<ProcessInfo> ProcessInfo::processInfoList(const FilePath &deviceRoot)
{
+ if (deviceRoot.needsDevice())
+ return processInfoListUnix(deviceRoot);
+
QList<ProcessInfo> processes;
PROCESSENTRY32 pe;
diff --git a/src/libs/utils/processinfo.h b/src/libs/utils/processinfo.h
index 47fd2d6d0de..90c1a97374d 100644
--- a/src/libs/utils/processinfo.h
+++ b/src/libs/utils/processinfo.h
@@ -5,6 +5,8 @@
#include "utils_global.h"
+#include "filepath.h"
+
#include <QList>
#include <QString>
@@ -19,7 +21,7 @@ public:
bool operator<(const ProcessInfo &other) const;
- static QList<ProcessInfo> processInfoList();
+ static QList<ProcessInfo> processInfoList(const Utils::FilePath &deviceRoot = Utils::FilePath());
};
} // namespace Utils
diff --git a/src/libs/utils/processinterface.cpp b/src/libs/utils/processinterface.cpp
index 9e13494cda1..a5e60c97a31 100644
--- a/src/libs/utils/processinterface.cpp
+++ b/src/libs/utils/processinterface.cpp
@@ -7,6 +7,17 @@
namespace Utils {
+namespace Pty {
+
+void Data::resize(const QSize &size)
+{
+ m_size = size;
+ if (m_data->m_handler)
+ m_data->m_handler(size);
+}
+
+} // namespace Pty
+
/*!
* \brief controlSignalToInt
* \param controlSignal
diff --git a/src/libs/utils/processinterface.h b/src/libs/utils/processinterface.h
index d398d8fe139..bd02428ef9b 100644
--- a/src/libs/utils/processinterface.h
+++ b/src/libs/utils/processinterface.h
@@ -10,11 +10,39 @@
#include "processenums.h"
#include <QProcess>
+#include <QSize>
namespace Utils {
namespace Internal { class QtcProcessPrivate; }
+namespace Pty {
+
+using ResizeHandler = std::function<void(const QSize &)>;
+
+class QTCREATOR_UTILS_EXPORT SharedData
+{
+public:
+ ResizeHandler m_handler;
+};
+
+class QTCREATOR_UTILS_EXPORT Data
+{
+public:
+ Data() : m_data(new SharedData) {}
+
+ void setResizeHandler(const ResizeHandler &handler) { m_data->m_handler = handler; }
+
+ QSize size() const { return m_size; }
+ void resize(const QSize &size);
+
+private:
+ QSize m_size{80, 60};
+ QSharedPointer<SharedData> m_data;
+};
+
+} // namespace Pty
+
class QTCREATOR_UTILS_EXPORT ProcessSetupData
{
public:
@@ -22,6 +50,7 @@ public:
ProcessMode m_processMode = ProcessMode::Reader;
TerminalMode m_terminalMode = TerminalMode::Off;
+ std::optional<Pty::Data> m_ptyData;
CommandLine m_commandLine;
FilePath m_workingDirectory;
Environment m_environment;
@@ -39,6 +68,7 @@ public:
bool m_unixTerminalDisabled = false;
bool m_useCtrlCStub = false;
bool m_belowNormalPriority = false; // internal, dependent on other fields and specific code path
+ bool m_createConsoleOnWindows = false;
};
class QTCREATOR_UTILS_EXPORT ProcessResultData
diff --git a/src/libs/utils/processutils.cpp b/src/libs/utils/processutils.cpp
index c8394e37dd2..b534d6f8bb2 100644
--- a/src/libs/utils/processutils.cpp
+++ b/src/libs/utils/processutils.cpp
@@ -45,16 +45,6 @@ void ProcessStartHandler::handleProcessStarted()
}
}
-void ProcessStartHandler::setBelowNormalPriority()
-{
-#ifdef Q_OS_WIN
- m_process->setCreateProcessArgumentsModifier(
- [](QProcess::CreateProcessArguments *args) {
- args->flags |= BELOW_NORMAL_PRIORITY_CLASS;
- });
-#endif // Q_OS_WIN
-}
-
void ProcessStartHandler::setNativeArguments(const QString &arguments)
{
#ifdef Q_OS_WIN
@@ -65,6 +55,29 @@ void ProcessStartHandler::setNativeArguments(const QString &arguments)
#endif // Q_OS_WIN
}
+void ProcessStartHandler::setWindowsSpecificStartupFlags(bool belowNormalPriority,
+ bool createConsoleWindow)
+{
+#ifdef Q_OS_WIN
+ if (!belowNormalPriority && !createConsoleWindow)
+ return;
+
+ m_process->setCreateProcessArgumentsModifier(
+ [belowNormalPriority, createConsoleWindow](QProcess::CreateProcessArguments *args) {
+ if (createConsoleWindow) {
+ args->flags |= CREATE_NEW_CONSOLE;
+ args->startupInfo->dwFlags &= ~STARTF_USESTDHANDLES;
+ }
+
+ if (belowNormalPriority)
+ args->flags |= BELOW_NORMAL_PRIORITY_CLASS;
+ });
+#else // Q_OS_WIN
+ Q_UNUSED(belowNormalPriority)
+ Q_UNUSED(createConsoleWindow)
+#endif
+}
+
#ifdef Q_OS_WIN
static BOOL sendMessage(UINT message, HWND hwnd, LPARAM lParam)
{
diff --git a/src/libs/utils/processutils.h b/src/libs/utils/processutils.h
index 60161f83984..fa915e2ac42 100644
--- a/src/libs/utils/processutils.h
+++ b/src/libs/utils/processutils.h
@@ -20,8 +20,8 @@ public:
QIODevice::OpenMode openMode() const;
void handleProcessStart();
void handleProcessStarted();
- void setBelowNormalPriority();
void setNativeArguments(const QString &arguments);
+ void setWindowsSpecificStartupFlags(bool belowNormalPriority, bool createConsoleWindow);
private:
ProcessMode m_processMode = ProcessMode::Reader;
diff --git a/src/libs/utils/qrcparser.h b/src/libs/utils/qrcparser.h
index 42cd9b6666f..14352e26b8d 100644
--- a/src/libs/utils/qrcparser.h
+++ b/src/libs/utils/qrcparser.h
@@ -4,7 +4,8 @@
#pragma once
#include "utils_global.h"
-#include "utils/filepath.h"
+
+#include "filepath.h"
#include <QSharedPointer>
#include <QString>
diff --git a/src/libs/utils/qtcolorbutton.cpp b/src/libs/utils/qtcolorbutton.cpp
index ca0aa631ba5..e6735baae2a 100644
--- a/src/libs/utils/qtcolorbutton.cpp
+++ b/src/libs/utils/qtcolorbutton.cpp
@@ -3,12 +3,13 @@
#include "qtcolorbutton.h"
-#include <QMimeData>
#include <QApplication>
#include <QColorDialog>
+#include <QDrag>
#include <QDragEnterEvent>
+#include <QMimeData>
#include <QPainter>
-#include <QDrag>
+#include <QStyleOption>
namespace Utils {
@@ -157,51 +158,36 @@ bool QtColorButton::isDialogOpen() const
void QtColorButton::paintEvent(QPaintEvent *event)
{
- QToolButton::paintEvent(event);
- if (!isEnabled())
- return;
-
- const int pixSize = 10;
- QBrush br(d_ptr->shownColor());
- if (d_ptr->m_backgroundCheckered) {
- QPixmap pm(2 * pixSize, 2 * pixSize);
- QPainter pmp(&pm);
- pmp.fillRect(0, 0, pixSize, pixSize, Qt::white);
- pmp.fillRect(pixSize, pixSize, pixSize, pixSize, Qt::white);
- pmp.fillRect(0, pixSize, pixSize, pixSize, Qt::black);
- pmp.fillRect(pixSize, 0, pixSize, pixSize, Qt::black);
- pmp.fillRect(0, 0, 2 * pixSize, 2 * pixSize, d_ptr->shownColor());
- br = QBrush(pm);
- }
+ Q_UNUSED(event)
QPainter p(this);
- const int corr = 5;
- QRect r = rect().adjusted(corr, corr, -corr, -corr);
- p.setBrushOrigin((r.width() % pixSize + pixSize) / 2 + corr, (r.height() % pixSize + pixSize) / 2 + corr);
- p.fillRect(r, br);
+ if (isEnabled()) {
+ QBrush br(d_ptr->shownColor());
+ if (d_ptr->m_backgroundCheckered) {
+ const int pixSize = 10;
+ QPixmap pm(2 * pixSize, 2 * pixSize);
+ pm.fill(Qt::white);
+ QPainter pmp(&pm);
+ pmp.fillRect(0, pixSize, pixSize, pixSize, Qt::black);
+ pmp.fillRect(pixSize, 0, pixSize, pixSize, Qt::black);
+ pmp.fillRect(pm.rect(), d_ptr->shownColor());
+ br = QBrush(pm);
+ p.setBrushOrigin((width() - pixSize) / 2, (height() - pixSize) / 2);
+ }
+ p.fillRect(rect(), br);
+ }
- //const int adjX = qRound(r.width() / 4.0);
- //const int adjY = qRound(r.height() / 4.0);
- //p.fillRect(r.adjusted(adjX, adjY, -adjX, -adjY),
- // QColor(d_ptr->shownColor().rgb()));
- /*
- p.fillRect(r.adjusted(0, r.height() * 3 / 4, 0, 0),
- QColor(d_ptr->shownColor().rgb()));
- p.fillRect(r.adjusted(0, 0, 0, -r.height() * 3 / 4),
- QColor(d_ptr->shownColor().rgb()));
- */
- /*
- const QColor frameColor0(0, 0, 0, qRound(0.2 * (0xFF - d_ptr->shownColor().alpha())));
- p.setPen(frameColor0);
- p.drawRect(r.adjusted(adjX, adjY, -adjX - 1, -adjY - 1));
- */
-
- const QColor frameColor1(0, 0, 0, 26);
- p.setPen(frameColor1);
- p.drawRect(r.adjusted(1, 1, -2, -2));
- const QColor frameColor2(0, 0, 0, 51);
- p.setPen(frameColor2);
- p.drawRect(r.adjusted(0, 0, -1, -1));
+ if (hasFocus()) {
+ QPen pen;
+ pen.setBrush(Qt::white);
+ pen.setStyle(Qt::DotLine);
+ p.setPen(pen);
+ p.setCompositionMode(QPainter::CompositionMode_Difference);
+ } else {
+ p.setPen(palette().text().color());
+ p.setOpacity(0.25);
+ }
+ p.drawRect(rect().adjusted(0, 0, -1, -1));
}
void QtColorButton::mousePressEvent(QMouseEvent *event)
diff --git a/src/libs/utils/qtcprocess.cpp b/src/libs/utils/qtcprocess.cpp
index 74bd28560b3..5220e8b5d56 100644
--- a/src/libs/utils/qtcprocess.cpp
+++ b/src/libs/utils/qtcprocess.cpp
@@ -12,10 +12,13 @@
#include "processreaper.h"
#include "processutils.h"
#include "stringutils.h"
-#include "terminalprocess_p.h"
+#include "terminalhooks.h"
#include "threadutils.h"
#include "utilstr.h"
+#include <iptyprocess.h>
+#include <ptyqt.h>
+
#include <QCoreApplication>
#include <QDebug>
#include <QDir>
@@ -304,6 +307,107 @@ private:
QProcess *m_process = nullptr;
};
+class PtyProcessImpl final : public DefaultImpl
+{
+public:
+ ~PtyProcessImpl() { QTC_CHECK(m_setup.m_ptyData); m_setup.m_ptyData->setResizeHandler({}); }
+
+ qint64 write(const QByteArray &data) final
+ {
+ if (m_ptyProcess)
+ return m_ptyProcess->write(data);
+ return -1;
+ }
+
+ void sendControlSignal(ControlSignal controlSignal) final
+ {
+ if (!m_ptyProcess)
+ return;
+
+ switch (controlSignal) {
+ case ControlSignal::Terminate:
+ m_ptyProcess.reset();
+ break;
+ case ControlSignal::Kill:
+ m_ptyProcess->kill();
+ break;
+ default:
+ QTC_CHECK(false);
+ }
+ }
+
+ void doDefaultStart(const QString &program, const QStringList &arguments) final
+ {
+ QTC_CHECK(m_setup.m_ptyData);
+ m_setup.m_ptyData->setResizeHandler([this](const QSize &size) {
+ if (m_ptyProcess)
+ m_ptyProcess->resize(size.width(), size.height());
+ });
+ m_ptyProcess.reset(PtyQt::createPtyProcess(IPtyProcess::AutoPty));
+ if (!m_ptyProcess) {
+ const ProcessResultData result = {-1,
+ QProcess::CrashExit,
+ QProcess::FailedToStart,
+ "Failed to create pty process"};
+ emit done(result);
+ return;
+ }
+
+ QProcessEnvironment penv = m_setup.m_environment.toProcessEnvironment();
+ if (penv.isEmpty())
+ penv = Environment::systemEnvironment().toProcessEnvironment();
+ const QStringList senv = penv.toStringList();
+
+ bool startResult
+ = m_ptyProcess->startProcess(program,
+ HostOsInfo::isWindowsHost()
+ ? QStringList{m_setup.m_nativeArguments} << arguments
+ : arguments,
+ m_setup.m_workingDirectory.nativePath(),
+ senv,
+ m_setup.m_ptyData->size().width(),
+ m_setup.m_ptyData->size().height());
+
+ if (!startResult) {
+ const ProcessResultData result = {-1,
+ QProcess::CrashExit,
+ QProcess::FailedToStart,
+ "Failed to start pty process: "
+ + m_ptyProcess->lastError()};
+ emit done(result);
+ return;
+ }
+
+ if (!m_ptyProcess->lastError().isEmpty()) {
+ const ProcessResultData result
+ = {-1, QProcess::CrashExit, QProcess::FailedToStart, m_ptyProcess->lastError()};
+ emit done(result);
+ return;
+ }
+
+ connect(m_ptyProcess->notifier(), &QIODevice::readyRead, this, [this] {
+ emit readyRead(m_ptyProcess->readAll(), {});
+ });
+
+ connect(m_ptyProcess->notifier(), &QIODevice::aboutToClose, this, [this] {
+ if (m_ptyProcess) {
+ const ProcessResultData result
+ = {m_ptyProcess->exitCode(), QProcess::NormalExit, QProcess::UnknownError, {}};
+ emit done(result);
+ return;
+ }
+
+ const ProcessResultData result = {0, QProcess::NormalExit, QProcess::UnknownError, {}};
+ emit done(result);
+ });
+
+ emit started(m_ptyProcess->pid());
+ }
+
+private:
+ std::unique_ptr<IPtyProcess> m_ptyProcess;
+};
+
class QProcessImpl final : public DefaultImpl
{
public:
@@ -355,10 +459,13 @@ private:
ProcessStartHandler *handler = m_process->processStartHandler();
handler->setProcessMode(m_setup.m_processMode);
handler->setWriteData(m_setup.m_writeData);
- if (m_setup.m_belowNormalPriority)
- handler->setBelowNormalPriority();
handler->setNativeArguments(m_setup.m_nativeArguments);
- m_process->setProcessEnvironment(m_setup.m_environment.toProcessEnvironment());
+ handler->setWindowsSpecificStartupFlags(m_setup.m_belowNormalPriority,
+ m_setup.m_createConsoleOnWindows);
+
+ const QProcessEnvironment penv = m_setup.m_environment.toProcessEnvironment();
+ if (!penv.isEmpty())
+ m_process->setProcessEnvironment(penv);
m_process->setWorkingDirectory(m_setup.m_workingDirectory.path());
m_process->setStandardInputFile(m_setup.m_standardInputFile);
m_process->setProcessChannelMode(m_setup.m_processChannelMode);
@@ -615,7 +722,6 @@ public:
, q(parent)
, m_killTimer(this)
{
- m_setup.m_controlEnvironment = Environment::systemEnvironment();
m_killTimer.setSingleShot(true);
connect(&m_killTimer, &QTimer::timeout, this, [this] {
m_killTimer.stop();
@@ -629,14 +735,16 @@ public:
ProcessInterface *createProcessInterface()
{
+ if (m_setup.m_ptyData)
+ return new PtyProcessImpl;
if (m_setup.m_terminalMode != TerminalMode::Off)
- return new TerminalImpl();
+ return Terminal::Hooks::instance().createTerminalProcessInterface();
const ProcessImpl impl = m_setup.m_processImpl == ProcessImpl::Default
? defaultProcessImpl() : m_setup.m_processImpl;
if (impl == ProcessImpl::QProcess)
- return new QProcessImpl();
- return new ProcessLauncherImpl();
+ return new QProcessImpl;
+ return new ProcessLauncherImpl;
}
void setProcessInterface(ProcessInterface *process)
@@ -667,22 +775,6 @@ public:
return rootCommand;
}
- Environment fullEnvironment() const
- {
- Environment env = m_setup.m_environment;
- if (!env.hasChanges() && env.combineWithDeviceEnvironment()) {
- // FIXME: Either switch to using EnvironmentChange instead of full Environments, or
- // feed the full environment into the QtcProcess instead of fixing it up here.
- // qWarning("QtcProcess::start: Empty environment set when running '%s'.",
- // qPrintable(m_setup.m_commandLine.executable().toString()));
- env = m_setup.m_commandLine.executable().deviceEnvironment();
- }
- // TODO: needs SshSettings
- // if (m_runAsRoot)
- // RunControl::provideAskPassEntry(env);
- return env;
- }
-
QtcProcess *q;
std::unique_ptr<ProcessBlockingInterface> m_blockingInterface;
std::unique_ptr<ProcessInterface> m_process;
@@ -1025,6 +1117,16 @@ void QtcProcess::setProcessImpl(ProcessImpl processImpl)
d->m_setup.m_processImpl = processImpl;
}
+void QtcProcess::setPtyData(const std::optional<Pty::Data> &data)
+{
+ d->m_setup.m_ptyData = data;
+}
+
+std::optional<Pty::Data> QtcProcess::ptyData() const
+{
+ return d->m_setup.m_ptyData;
+}
+
ProcessMode QtcProcess::processMode() const
{
return d->m_setup.m_processMode;
@@ -1115,7 +1217,6 @@ void QtcProcess::start()
d->m_state = QProcess::Starting;
d->m_process->m_setup = d->m_setup;
d->m_process->m_setup.m_commandLine = d->fullCommandLine();
- d->m_process->m_setup.m_environment = d->fullEnvironment();
d->emitGuardedSignal(&QtcProcess::starting);
d->m_process->start();
}
@@ -1202,6 +1303,16 @@ QString QtcProcess::toStandaloneCommandLine() const
return parts.join(" ");
}
+void QtcProcess::setCreateConsoleOnWindows(bool create)
+{
+ d->m_setup.m_createConsoleOnWindows = create;
+}
+
+bool QtcProcess::createConsoleOnWindows() const
+{
+ return d->m_setup.m_createConsoleOnWindows;
+}
+
void QtcProcess::setExtraData(const QString &key, const QVariant &value)
{
d->m_setup.m_extraData.insert(key, value);
diff --git a/src/libs/utils/qtcprocess.h b/src/libs/utils/qtcprocess.h
index 67898ba855a..e016b50d613 100644
--- a/src/libs/utils/qtcprocess.h
+++ b/src/libs/utils/qtcprocess.h
@@ -21,6 +21,7 @@ class tst_QtcProcess;
namespace Utils {
namespace Internal { class QtcProcessPrivate; }
+namespace Pty { class Data; }
class Environment;
class DeviceProcessHooks;
@@ -76,6 +77,9 @@ public:
void setProcessImpl(ProcessImpl processImpl);
+ void setPtyData(const std::optional<Pty::Data> &data);
+ std::optional<Pty::Data> ptyData() const;
+
void setTerminalMode(TerminalMode mode);
TerminalMode terminalMode() const;
bool usesTerminal() const { return terminalMode() != TerminalMode::Off; }
@@ -177,6 +181,9 @@ public:
QString toStandaloneCommandLine() const;
+ void setCreateConsoleOnWindows(bool create);
+ bool createConsoleOnWindows() const;
+
signals:
void starting(); // On NotRunning -> Starting state transition
void started(); // On Starting -> Running state transition
diff --git a/src/libs/utils/stringtable.cpp b/src/libs/utils/stringtable.cpp
index 6345b80c32e..e501203a10a 100644
--- a/src/libs/utils/stringtable.cpp
+++ b/src/libs/utils/stringtable.cpp
@@ -3,7 +3,7 @@
#include "stringtable.h"
-#include "runextensions.h"
+#include "asynctask.h"
#include <QDebug>
#include <QElapsedTimer>
@@ -34,7 +34,7 @@ public:
void cancelAndWait();
QString insert(const QString &string);
void startGC();
- void GC(QFutureInterface<void> &futureInterface);
+ void GC(QPromise<void> &promise);
QFuture<void> m_future;
QMutex m_lock;
@@ -90,7 +90,7 @@ void StringTablePrivate::startGC()
{
QMutexLocker locker(&m_lock);
cancelAndWait();
- m_future = Utils::runAsync(&StringTablePrivate::GC, this);
+ m_future = Utils::asyncRun(&StringTablePrivate::GC, this);
}
QTCREATOR_UTILS_EXPORT void scheduleGC()
@@ -113,7 +113,7 @@ static inline bool isQStringInUse(const QString &string)
return data_ptr->isShared() || !data_ptr->isMutable() /* QStringLiteral ? */;
}
-void StringTablePrivate::GC(QFutureInterface<void> &futureInterface)
+void StringTablePrivate::GC(QPromise<void> &promise)
{
int initialSize = 0;
bytesSaved = 0;
@@ -125,7 +125,7 @@ void StringTablePrivate::GC(QFutureInterface<void> &futureInterface)
// Collect all QStrings which have refcount 1. (One reference in m_strings and nowhere else.)
for (QSet<QString>::iterator i = m_strings.begin(); i != m_strings.end();) {
- if (futureInterface.isCanceled())
+ if (promise.isCanceled())
return;
if (!isQStringInUse(*i))
diff --git a/src/libs/utils/stringutils.cpp b/src/libs/utils/stringutils.cpp
index 3a1afe1fa18..7315a69da2a 100644
--- a/src/libs/utils/stringutils.cpp
+++ b/src/libs/utils/stringutils.cpp
@@ -527,4 +527,23 @@ QTCREATOR_UTILS_EXPORT FilePath appendHelper(const FilePath &base, int n)
return base.stringAppended(QString::number(n));
}
+QTCREATOR_UTILS_EXPORT QPair<QStringView, QStringView> splitAtFirst(const QStringView &stringView,
+ QChar ch)
+{
+ int splitIdx = stringView.indexOf(ch);
+ if (splitIdx == -1)
+ return {stringView, {}};
+
+ QStringView left = stringView.mid(0, splitIdx);
+ QStringView right = stringView.mid(splitIdx + 1);
+
+ return {left, right};
+}
+
+QTCREATOR_UTILS_EXPORT QPair<QStringView, QStringView> splitAtFirst(const QString &string, QChar ch)
+{
+ QStringView view = string;
+ return splitAtFirst(view, ch);
+}
+
} // namespace Utils
diff --git a/src/libs/utils/stringutils.h b/src/libs/utils/stringutils.h
index 5cf2978c07b..865a1dea122 100644
--- a/src/libs/utils/stringutils.h
+++ b/src/libs/utils/stringutils.h
@@ -115,4 +115,8 @@ QTCREATOR_UTILS_EXPORT QString trimFront(const QString &string, QChar ch);
QTCREATOR_UTILS_EXPORT QString trimBack(const QString &string, QChar ch);
QTCREATOR_UTILS_EXPORT QString trim(const QString &string, QChar ch);
+QTCREATOR_UTILS_EXPORT QPair<QStringView, QStringView> splitAtFirst(const QString &string, QChar ch);
+QTCREATOR_UTILS_EXPORT QPair<QStringView, QStringView> splitAtFirst(const QStringView &stringView,
+ QChar ch);
+
} // namespace Utils
diff --git a/src/libs/utils/tasktree.cpp b/src/libs/utils/tasktree.cpp
index 90e4073e033..8ea51369322 100644
--- a/src/libs/utils/tasktree.cpp
+++ b/src/libs/utils/tasktree.cpp
@@ -74,6 +74,54 @@ void TreeStorageBase::activateStorage(int id) const
m_storageData->m_activeStorage = id;
}
+Condition::Condition()
+ : m_conditionData(new ConditionData()) {}
+
+Condition::ConditionData::~ConditionData()
+{
+ QTC_CHECK(m_activatorHash.isEmpty());
+ qDeleteAll(m_activatorHash);
+}
+
+ConditionActivator *Condition::activator() const
+{
+ QTC_ASSERT(m_conditionData->m_activeActivator, return nullptr);
+ const auto it = m_conditionData->m_activatorHash.constFind(m_conditionData->m_activeActivator);
+ QTC_ASSERT(it != m_conditionData->m_activatorHash.constEnd(), return nullptr);
+ return it.value();
+}
+
+int Condition::createActivator(TaskNode *node) const
+{
+ QTC_ASSERT(m_conditionData->m_activeActivator == 0, return 0); // TODO: should be allowed?
+ const int newId = ++m_conditionData->m_activatorCounter;
+ m_conditionData->m_activatorHash.insert(newId, new ConditionActivator(node));
+ return newId;
+}
+
+void Condition::deleteActivator(int id) const
+{
+ QTC_ASSERT(m_conditionData->m_activeActivator == 0, return); // TODO: should be allowed?
+ const auto it = m_conditionData->m_activatorHash.constFind(id);
+ QTC_ASSERT(it != m_conditionData->m_activatorHash.constEnd(), return);
+ delete it.value();
+ m_conditionData->m_activatorHash.erase(it);
+}
+
+// passing 0 deactivates currently active condition
+void Condition::activateActivator(int id) const
+{
+ if (id == 0) {
+ QTC_ASSERT(m_conditionData->m_activeActivator, return);
+ m_conditionData->m_activeActivator = 0;
+ return;
+ }
+ QTC_ASSERT(m_conditionData->m_activeActivator == 0, return);
+ const auto it = m_conditionData->m_activatorHash.find(id);
+ QTC_ASSERT(it != m_conditionData->m_activatorHash.end(), return);
+ m_conditionData->m_activeActivator = id;
+}
+
ParallelLimit sequential(1);
ParallelLimit parallel(0);
Workflow stopOnError(WorkflowPolicy::StopOnError);
@@ -84,20 +132,21 @@ Workflow optional(WorkflowPolicy::Optional);
void TaskItem::addChildren(const QList<TaskItem> &children)
{
- QTC_ASSERT(m_type == Type::Group, qWarning("Only Task may have children, skipping..."); return);
+ QTC_ASSERT(m_type == Type::Group, qWarning("Only Group may have children, skipping...");
+ return);
for (const TaskItem &child : children) {
switch (child.m_type) {
case Type::Group:
m_children.append(child);
break;
case Type::Limit:
- QTC_ASSERT(m_type == Type::Group,
- qWarning("Mode may only be a child of Group, skipping..."); return);
+ QTC_ASSERT(m_type == Type::Group, qWarning("Execution Mode may only be a child of a "
+ "Group, skipping..."); return);
m_parallelLimit = child.m_parallelLimit; // TODO: Assert on redefinition?
break;
case Type::Policy:
- QTC_ASSERT(m_type == Type::Group,
- qWarning("Workflow Policy may only be a child of Group, skipping..."); return);
+ QTC_ASSERT(m_type == Type::Group, qWarning("Workflow Policy may only be a child of a "
+ "Group, skipping..."); return);
m_workflowPolicy = child.m_workflowPolicy; // TODO: Assert on redefinition?
break;
case Type::TaskHandler:
@@ -109,7 +158,7 @@ void TaskItem::addChildren(const QList<TaskItem> &children)
break;
case Type::GroupHandler:
QTC_ASSERT(m_type == Type::Group, qWarning("Group Handler may only be a "
- "child of Group, skipping..."); break);
+ "child of a Group, skipping..."); break);
QTC_ASSERT(!child.m_groupHandler.m_setupHandler
|| !m_groupHandler.m_setupHandler,
qWarning("Group Setup Handler redefinition, overriding..."));
@@ -126,6 +175,12 @@ void TaskItem::addChildren(const QList<TaskItem> &children)
if (child.m_groupHandler.m_errorHandler)
m_groupHandler.m_errorHandler = child.m_groupHandler.m_errorHandler;
break;
+ case Type::Condition:
+ QTC_ASSERT(m_type == Type::Group, qWarning("WaitFor may only be a child of a Group, "
+ "skipping..."); break);
+ QTC_ASSERT(!m_condition, qWarning("WaitFor redefinition, overriding..."));
+ m_condition = child.m_condition;
+ break;
case Type::Storage:
m_storageList.append(child.m_storageList);
break;
@@ -140,26 +195,82 @@ using namespace Tasking;
class TaskTreePrivate;
class TaskNode;
+class TaskTreePrivate
+{
+public:
+ TaskTreePrivate(TaskTree *taskTree)
+ : q(taskTree) {}
+
+ void start();
+ void stop();
+ void advanceProgress(int byValue);
+ void emitStartedAndProgress();
+ void emitProgress();
+ void emitDone();
+ void emitError();
+ bool addCondition(const TaskItem &task, TaskContainer *container);
+ void createConditionActivators();
+ void deleteConditionActivators();
+ void activateConditions();
+ void deactivateConditions();
+ QList<TreeStorageBase> addStorages(const QList<TreeStorageBase> &storages);
+ void callSetupHandler(TreeStorageBase storage, int storageId) {
+ callStorageHandler(storage, storageId, &StorageHandler::m_setupHandler);
+ }
+ void callDoneHandler(TreeStorageBase storage, int storageId) {
+ callStorageHandler(storage, storageId, &StorageHandler::m_doneHandler);
+ }
+ struct StorageHandler {
+ TaskTree::StorageVoidHandler m_setupHandler = {};
+ TaskTree::StorageVoidHandler m_doneHandler = {};
+ };
+ typedef TaskTree::StorageVoidHandler StorageHandler::*HandlerPtr; // ptr to class member
+ void callStorageHandler(TreeStorageBase storage, int storageId, HandlerPtr ptr)
+ {
+ const auto it = m_storageHandlers.constFind(storage);
+ if (it == m_storageHandlers.constEnd())
+ return;
+ GuardLocker locker(m_guard);
+ const StorageHandler storageHandler = *it;
+ storage.activateStorage(storageId);
+ if (storageHandler.*ptr)
+ (storageHandler.*ptr)(storage.activeStorageVoid());
+ storage.activateStorage(0);
+ }
+
+ TaskTree *q = nullptr;
+ Guard m_guard;
+ int m_progressValue = 0;
+ QHash<Condition, TaskContainer *> m_conditions;
+ QSet<TreeStorageBase> m_storages;
+ QHash<TreeStorageBase, StorageHandler> m_storageHandlers;
+ std::unique_ptr<TaskNode> m_root = nullptr; // Keep me last in order to destruct first
+};
+
class TaskContainer
{
public:
TaskContainer(TaskTreePrivate *taskTreePrivate, const TaskItem &task,
- TaskContainer *parentContainer)
- : m_constData(taskTreePrivate, task, parentContainer, this) {}
+ TaskNode *parentNode, TaskContainer *parentContainer)
+ : m_constData(taskTreePrivate, task, parentNode, parentContainer, this)
+ , m_conditionData(taskTreePrivate->addCondition(task, this)
+ ? ConditionData() : std::optional<ConditionData>()) {}
TaskAction start();
TaskAction continueStart(TaskAction startAction, int nextChild);
TaskAction startChildren(int nextChild);
TaskAction childDone(bool success);
+ void activateCondition();
void stop();
void invokeEndHandler();
bool isRunning() const { return m_runtimeData.has_value(); }
bool isStarting() const { return isRunning() && m_runtimeData->m_startGuard.isLocked(); }
struct ConstData {
- ConstData(TaskTreePrivate *taskTreePrivate, const TaskItem &task,
+ ConstData(TaskTreePrivate *taskTreePrivate, const TaskItem &task, TaskNode *parentNode,
TaskContainer *parentContainer, TaskContainer *thisContainer);
~ConstData() { qDeleteAll(m_children); }
TaskTreePrivate * const m_taskTreePrivate = nullptr;
+ TaskNode * const m_parentNode = nullptr;
TaskContainer * const m_parentContainer = nullptr;
const int m_parallelLimit = 1;
@@ -170,6 +281,11 @@ public:
const int m_taskCount = 0;
};
+ struct ConditionData {
+ bool m_activated = false;
+ int m_conditionId = 0;
+ };
+
struct RuntimeData {
RuntimeData(const ConstData &constData);
~RuntimeData();
@@ -187,6 +303,7 @@ public:
};
const ConstData m_constData;
+ std::optional<ConditionData> m_conditionData;
std::optional<RuntimeData> m_runtimeData;
};
@@ -196,7 +313,7 @@ public:
TaskNode(TaskTreePrivate *taskTreePrivate, const TaskItem &task,
TaskContainer *parentContainer)
: m_taskHandler(task.taskHandler())
- , m_container(taskTreePrivate, task, parentContainer)
+ , m_container(taskTreePrivate, task, this, parentContainer)
{}
// If returned value != Continue, childDone() needs to be called in parent container (in caller)
@@ -208,6 +325,7 @@ public:
bool isTask() const { return m_taskHandler.m_createHandler && m_taskHandler.m_setupHandler; }
int taskCount() const { return isTask() ? 1 : m_container.m_constData.m_taskCount; }
TaskContainer *parentContainer() const { return m_container.m_constData.m_parentContainer; }
+ void activateCondition();
private:
const TaskItem::TaskHandler m_taskHandler;
@@ -215,129 +333,160 @@ private:
std::unique_ptr<TaskInterface> m_task;
};
-class TaskTreePrivate
+void TaskTreePrivate::start()
{
-public:
- TaskTreePrivate(TaskTree *taskTree)
- : q(taskTree) {}
-
- void start() {
- QTC_ASSERT(m_root, return);
- m_progressValue = 0;
- emitStartedAndProgress();
- // TODO: check storage handlers for not existing storages in tree
- for (auto it = m_storageHandlers.cbegin(); it != m_storageHandlers.cend(); ++it) {
- QTC_ASSERT(m_storages.contains(it.key()), qWarning("The registered storage doesn't "
- "exist in task tree. Its handlers will never be called."));
- }
- m_root->start();
- }
- void stop() {
- QTC_ASSERT(m_root, return);
- if (!m_root->isRunning())
- return;
- // TODO: should we have canceled flag (passed to handler)?
- // Just one done handler with result flag:
- // FinishedWithSuccess, FinishedWithError, Canceled, TimedOut.
- // Canceled either directly by user, or by workflow policy - doesn't matter, in both
- // cases canceled from outside.
- m_root->stop();
- emitError();
- }
- void advanceProgress(int byValue) {
- if (byValue == 0)
- return;
- QTC_CHECK(byValue > 0);
- QTC_CHECK(m_progressValue + byValue <= m_root->taskCount());
- m_progressValue += byValue;
- emitProgress();
- }
- void emitStartedAndProgress() {
- GuardLocker locker(m_guard);
- emit q->started();
- emit q->progressValueChanged(m_progressValue);
- }
- void emitProgress() {
- GuardLocker locker(m_guard);
- emit q->progressValueChanged(m_progressValue);
+ QTC_ASSERT(m_root, return);
+ m_progressValue = 0;
+ emitStartedAndProgress();
+ // TODO: check storage handlers for not existing storages in tree
+ for (auto it = m_storageHandlers.cbegin(); it != m_storageHandlers.cend(); ++it) {
+ QTC_ASSERT(m_storages.contains(it.key()), qWarning("The registered storage doesn't "
+ "exist in task tree. Its handlers will never be called."));
}
- void emitDone() {
- QTC_CHECK(m_progressValue == m_root->taskCount());
- GuardLocker locker(m_guard);
- emit q->done();
- }
- void emitError() {
- QTC_CHECK(m_progressValue == m_root->taskCount());
- GuardLocker locker(m_guard);
- emit q->errorOccurred();
- }
- QList<TreeStorageBase> addStorages(const QList<TreeStorageBase> &storages) {
- QList<TreeStorageBase> addedStorages;
- for (const TreeStorageBase &storage : storages) {
- QTC_ASSERT(!m_storages.contains(storage), qWarning("Can't add the same storage into "
- "one TaskTree twice, skipping..."); continue);
- addedStorages << storage;
- m_storages << storage;
- }
- return addedStorages;
- }
- void callSetupHandler(TreeStorageBase storage, int storageId) {
- callStorageHandler(storage, storageId, &StorageHandler::m_setupHandler);
+ createConditionActivators();
+ m_root->start();
+}
+
+void TaskTreePrivate::stop()
+{
+ QTC_ASSERT(m_root, return);
+ if (!m_root->isRunning())
+ return;
+ // TODO: should we have canceled flag (passed to handler)?
+ // Just one done handler with result flag:
+ // FinishedWithSuccess, FinishedWithError, Canceled, TimedOut.
+ // Canceled either directly by user, or by workflow policy - doesn't matter, in both
+ // cases canceled from outside.
+ m_root->stop();
+ emitError();
+}
+
+void TaskTreePrivate::advanceProgress(int byValue)
+{
+ if (byValue == 0)
+ return;
+ QTC_CHECK(byValue > 0);
+ QTC_CHECK(m_progressValue + byValue <= m_root->taskCount());
+ m_progressValue += byValue;
+ emitProgress();
+}
+
+void TaskTreePrivate::emitStartedAndProgress()
+{
+ GuardLocker locker(m_guard);
+ emit q->started();
+ emit q->progressValueChanged(m_progressValue);
+}
+
+void TaskTreePrivate::emitProgress()
+{
+ GuardLocker locker(m_guard);
+ emit q->progressValueChanged(m_progressValue);
+}
+
+void TaskTreePrivate::emitDone()
+{
+ deleteConditionActivators();
+ QTC_CHECK(m_progressValue == m_root->taskCount());
+ GuardLocker locker(m_guard);
+ emit q->done();
+}
+
+void TaskTreePrivate::emitError()
+{
+ deleteConditionActivators();
+ QTC_CHECK(m_progressValue == m_root->taskCount());
+ GuardLocker locker(m_guard);
+ emit q->errorOccurred();
+}
+
+bool TaskTreePrivate::addCondition(const TaskItem &task, TaskContainer *container)
+{
+ if (!task.condition())
+ return false;
+ QTC_ASSERT(!m_conditions.contains(*task.condition()), qWarning("Can't add the same condition "
+ "into one TaskTree twice, skipping..."); return false);
+ m_conditions.insert(*task.condition(), container);
+ return true;
+}
+
+void TaskTreePrivate::createConditionActivators()
+{
+ for (auto it = m_conditions.cbegin(); it != m_conditions.cend(); ++it) {
+ Condition condition = it.key();
+ TaskContainer *container = it.value();
+ container->m_conditionData->m_conditionId
+ = condition.createActivator(container->m_constData.m_parentNode);
}
- void callDoneHandler(TreeStorageBase storage, int storageId) {
- callStorageHandler(storage, storageId, &StorageHandler::m_doneHandler);
+}
+
+void TaskTreePrivate::deleteConditionActivators()
+{
+ for (auto it = m_conditions.cbegin(); it != m_conditions.cend(); ++it) {
+ Condition condition = it.key();
+ TaskContainer *container = it.value();
+ condition.deleteActivator(container->m_conditionData->m_conditionId);
+ container->m_conditionData = TaskContainer::ConditionData();
}
- struct StorageHandler {
- TaskTree::StorageVoidHandler m_setupHandler = {};
- TaskTree::StorageVoidHandler m_doneHandler = {};
- };
- typedef TaskTree::StorageVoidHandler StorageHandler::*HandlerPtr; // ptr to class member
- void callStorageHandler(TreeStorageBase storage, int storageId, HandlerPtr ptr)
- {
- const auto it = m_storageHandlers.constFind(storage);
- if (it == m_storageHandlers.constEnd())
- return;
- GuardLocker locker(m_guard);
- const StorageHandler storageHandler = *it;
- storage.activateStorage(storageId);
- if (storageHandler.*ptr)
- (storageHandler.*ptr)(storage.activeStorageVoid());
- storage.activateStorage(0);
+}
+
+void TaskTreePrivate::activateConditions()
+{
+ for (auto it = m_conditions.cbegin(); it != m_conditions.cend(); ++it) {
+ Condition condition = it.key();
+ TaskContainer *container = it.value();
+ condition.activateActivator(container->m_conditionData->m_conditionId);
}
+}
- TaskTree *q = nullptr;
- Guard m_guard;
- int m_progressValue = 0;
- QSet<TreeStorageBase> m_storages;
- QHash<TreeStorageBase, StorageHandler> m_storageHandlers;
- std::unique_ptr<TaskNode> m_root = nullptr; // Keep me last in order to destruct first
-};
+void TaskTreePrivate::deactivateConditions()
+{
+ for (auto it = m_conditions.cbegin(); it != m_conditions.cend(); ++it)
+ it.key().activateActivator(0);
+}
-class StorageActivator
+QList<TreeStorageBase> TaskTreePrivate::addStorages(const QList<TreeStorageBase> &storages)
+{
+ QList<TreeStorageBase> addedStorages;
+ for (const TreeStorageBase &storage : storages) {
+ QTC_ASSERT(!m_storages.contains(storage), qWarning("Can't add the same storage into "
+ "one TaskTree twice, skipping..."); continue);
+ addedStorages << storage;
+ m_storages << storage;
+ }
+ return addedStorages;
+}
+
+// TODO: Activate/deactivate Conditions
+class ExecutionContextActivator
{
public:
- StorageActivator(TaskContainer *container)
- : m_container(container) { activateStorages(m_container); }
- ~StorageActivator() { deactivateStorages(m_container); }
+ ExecutionContextActivator(TaskContainer *container)
+ : m_container(container) { activateContext(m_container); }
+ ~ExecutionContextActivator() { deactivateContext(m_container); }
private:
- static void activateStorages(TaskContainer *container)
+ static void activateContext(TaskContainer *container)
{
QTC_ASSERT(container && container->isRunning(), return);
const TaskContainer::ConstData &constData = container->m_constData;
if (constData.m_parentContainer)
- activateStorages(constData.m_parentContainer);
+ activateContext(constData.m_parentContainer);
+ else
+ constData.m_taskTreePrivate->activateConditions();
for (int i = 0; i < constData.m_storageList.size(); ++i)
constData.m_storageList[i].activateStorage(container->m_runtimeData->m_storageIdList.value(i));
}
- static void deactivateStorages(TaskContainer *container)
+ static void deactivateContext(TaskContainer *container)
{
QTC_ASSERT(container && container->isRunning(), return);
const TaskContainer::ConstData &constData = container->m_constData;
for (int i = constData.m_storageList.size() - 1; i >= 0; --i) // iterate in reverse order
constData.m_storageList[i].activateStorage(0);
if (constData.m_parentContainer)
- deactivateStorages(constData.m_parentContainer);
+ deactivateContext(constData.m_parentContainer);
+ else
+ constData.m_taskTreePrivate->deactivateConditions();
}
TaskContainer *m_container = nullptr;
};
@@ -346,7 +495,7 @@ template <typename Handler, typename ...Args,
typename ReturnType = typename std::invoke_result_t<Handler, Args...>>
ReturnType invokeHandler(TaskContainer *container, Handler &&handler, Args &&...args)
{
- StorageActivator activator(container);
+ ExecutionContextActivator activator(container);
GuardLocker locker(container->m_constData.m_taskTreePrivate->m_guard);
return std::invoke(std::forward<Handler>(handler), std::forward<Args>(args)...);
}
@@ -362,8 +511,10 @@ static QList<TaskNode *> createChildren(TaskTreePrivate *taskTreePrivate, TaskCo
}
TaskContainer::ConstData::ConstData(TaskTreePrivate *taskTreePrivate, const TaskItem &task,
- TaskContainer *parentContainer, TaskContainer *thisContainer)
+ TaskNode *parentNode, TaskContainer *parentContainer,
+ TaskContainer *thisContainer)
: m_taskTreePrivate(taskTreePrivate)
+ , m_parentNode(parentNode)
, m_parentContainer(parentContainer)
, m_parallelLimit(task.parallelLimit())
, m_workflowPolicy(task.workflowPolicy())
@@ -440,8 +591,12 @@ TaskAction TaskContainer::start()
if (startAction != TaskAction::Continue)
m_constData.m_taskTreePrivate->advanceProgress(m_constData.m_taskCount);
}
- if (m_constData.m_children.isEmpty() && startAction == TaskAction::Continue)
- startAction = TaskAction::StopWithDone;
+ if (startAction == TaskAction::Continue) {
+ if (m_conditionData && !m_conditionData->m_activated) // Group has condition and it wasn't activated yet
+ return TaskAction::Continue;
+ if (m_constData.m_children.isEmpty())
+ startAction = TaskAction::StopWithDone;
+ }
return continueStart(startAction, 0);
}
@@ -513,6 +668,35 @@ TaskAction TaskContainer::childDone(bool success)
return continueStart(startAction, limit);
}
+void ConditionActivator::activate()
+{
+ m_node->activateCondition();
+}
+
+void TaskContainer::activateCondition()
+{
+ QTC_ASSERT(m_conditionData, return);
+ if (!m_constData.m_taskTreePrivate->m_root->isRunning())
+ return;
+
+ if (!isRunning())
+ return; // Condition not run yet or group already skipped or stopped
+
+ if (!m_conditionData->m_activated)
+ return; // May it happen that scheduled call is coming from previous TaskTree's start?
+
+ if (m_runtimeData->m_doneCount != 0)
+ return; // In meantime the group was started
+
+ for (TaskNode *child : m_constData.m_children) {
+ if (child->isRunning())
+ return; // In meantime the group was started
+ }
+ const TaskAction startAction = m_constData.m_children.isEmpty() ? TaskAction::StopWithDone
+ : TaskAction::Continue;
+ continueStart(startAction, 0);
+}
+
void TaskContainer::stop()
{
if (!isRunning())
@@ -597,6 +781,26 @@ void TaskNode::invokeEndHandler(bool success)
m_container.m_constData.m_taskTreePrivate->advanceProgress(1);
}
+void TaskNode::activateCondition()
+{
+ QTC_ASSERT(m_container.m_conditionData, return);
+ QTC_ASSERT(m_container.m_constData.m_taskTreePrivate->m_root->isRunning(), return);
+
+ if (m_container.m_conditionData->m_activated)
+ return; // Was already activated
+
+ m_container.m_conditionData->m_activated = true;
+ if (!isRunning())
+ return; // Condition not run yet or group already skipped or stopped
+
+ QTC_CHECK(m_container.m_runtimeData->m_doneCount == 0);
+ for (TaskNode *child : m_container.m_constData.m_children)
+ QTC_CHECK(!child->isRunning());
+
+ QMetaObject::invokeMethod(this, [this] { m_container.activateCondition(); },
+ Qt::QueuedConnection);
+}
+
/*!
\class Utils::TaskTree
\inheaderfile utils/tasktree.h
@@ -1324,6 +1528,9 @@ TaskTree::~TaskTree()
{
QTC_ASSERT(!d->m_guard.isLocked(), qWarning("Deleting TaskTree instance directly from "
"one of its handlers will lead to crash!"));
+ if (isRunning())
+ d->deleteConditionActivators();
+ // TODO: delete storages explicitly here?
delete d;
}
@@ -1332,6 +1539,7 @@ void TaskTree::setupRoot(const Tasking::Group &root)
QTC_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return);
QTC_ASSERT(!d->m_guard.isLocked(), qWarning("The setupRoot() is called from one of the"
"TaskTree handlers, ingoring..."); return);
+ d->m_conditions.clear();
d->m_storages.clear();
d->m_root.reset(new TaskNode(d, root, nullptr));
}
diff --git a/src/libs/utils/tasktree.h b/src/libs/utils/tasktree.h
index 54a0cfda534..8e9c0a6c17b 100644
--- a/src/libs/utils/tasktree.h
+++ b/src/libs/utils/tasktree.h
@@ -11,8 +11,9 @@
namespace Utils {
-class StorageActivator;
+class ExecutionContextActivator;
class TaskContainer;
+class TaskNode;
class TaskTreePrivate;
namespace Tasking {
@@ -66,7 +67,7 @@ private:
QSharedPointer<StorageData> m_storageData;
friend TaskContainer;
friend TaskTreePrivate;
- friend StorageActivator;
+ friend ExecutionContextActivator;
};
template <typename StorageStruct>
@@ -74,6 +75,7 @@ class TreeStorage : public TreeStorageBase
{
public:
TreeStorage() : TreeStorageBase(TreeStorage::ctor(), TreeStorage::dtor()) {}
+ StorageStruct &operator*() const noexcept { return *activeStorage(); }
StorageStruct *operator->() const noexcept { return activeStorage(); }
StorageStruct *activeStorage() const {
return static_cast<StorageStruct *>(activeStorageVoid());
@@ -86,20 +88,65 @@ private:
}
};
-// 4 policies:
+class QTCREATOR_UTILS_EXPORT ConditionActivator
+{
+public:
+ void activate();
+
+private:
+ ConditionActivator(TaskNode *container) : m_node(container) {}
+ TaskNode *m_node = nullptr;
+ friend class Condition;
+};
+
+class QTCREATOR_UTILS_EXPORT Condition
+{
+public:
+ Condition();
+ ConditionActivator &operator*() const noexcept { return *activator(); }
+ ConditionActivator *operator->() const noexcept { return activator(); }
+ ConditionActivator *activator() const;
+
+private:
+ int createActivator(TaskNode *node) const;
+ void deleteActivator(int id) const;
+ void activateActivator(int id) const;
+
+ friend bool operator==(const Condition &first, const Condition &second)
+ { return first.m_conditionData == second.m_conditionData; }
+
+ friend bool operator!=(const Condition &first, const Condition &second)
+ { return first.m_conditionData != second.m_conditionData; }
+
+ friend size_t qHash(const Condition &storage, uint seed = 0)
+ { return size_t(storage.m_conditionData.get()) ^ seed; }
+
+ struct ConditionData {
+ ~ConditionData();
+ QHash<int, ConditionActivator *> m_activatorHash = {};
+ int m_activeActivator = 0; // 0 means no active activator
+ int m_activatorCounter = 0;
+ };
+ QSharedPointer<ConditionData> m_conditionData;
+ friend TaskTreePrivate;
+ friend ExecutionContextActivator;
+};
+
+// WorkflowPolicy:
// 1. When all children finished with done -> report done, otherwise:
// a) Report error on first error and stop executing other children (including their subtree)
-// b) On first error - wait for all children to be finished and report error afterwards
+// b) On first error - continue executing all children and report error afterwards
// 2. When all children finished with error -> report error, otherwise:
// a) Report done on first done and stop executing other children (including their subtree)
-// b) On first done - wait for all children to be finished and report done afterwards
+// b) On first done - continue executing all children and report done afterwards
+// 3. Always run all children, ignore their result and report done afterwards
enum class WorkflowPolicy {
- StopOnError, // 1a - Will report error on any child error, otherwise done (if all children were done)
- ContinueOnError, // 1b - the same. When no children it reports done.
- StopOnDone, // 2a - Will report done on any child done, otherwise error (if all children were error)
- ContinueOnDone, // 2b - the same. When no children it reports done. (?)
- Optional // Returns always done after all children finished
+ StopOnError, // 1a - Reports error on first child error, otherwise done (if all children were done)
+ ContinueOnError, // 1b - The same, but children execution continues. When no children it reports done.
+ StopOnDone, // 2a - Reports done on first child done, otherwise error (if all children were error)
+ ContinueOnDone, // 2b - The same, but children execution continues. When no children it reports done. (?)
+ Optional // 3 - Always reports done after all children finished
};
enum class TaskAction
@@ -141,11 +188,13 @@ public:
TaskHandler taskHandler() const { return m_taskHandler; }
GroupHandler groupHandler() const { return m_groupHandler; }
QList<TaskItem> children() const { return m_children; }
+ std::optional<Condition> condition() const { return m_condition; }
QList<TreeStorageBase> storageList() const { return m_storageList; }
protected:
enum class Type {
Group,
+ Condition,
Storage,
Limit,
Policy,
@@ -166,6 +215,9 @@ protected:
TaskItem(const GroupHandler &handler)
: m_type(Type::GroupHandler)
, m_groupHandler(handler) {}
+ TaskItem(const Condition &condition)
+ : m_type(Type::Condition)
+ , m_condition{condition} {}
TaskItem(const TreeStorageBase &storage)
: m_type(Type::Storage)
, m_storageList{storage} {}
@@ -177,6 +229,7 @@ private:
WorkflowPolicy m_workflowPolicy = WorkflowPolicy::StopOnError;
TaskHandler m_taskHandler;
GroupHandler m_groupHandler;
+ std::optional<Condition> m_condition;
QList<TreeStorageBase> m_storageList;
QList<TaskItem> m_children;
};
@@ -194,6 +247,12 @@ public:
Storage(const TreeStorageBase &storage) : TaskItem(storage) { }
};
+class QTCREATOR_UTILS_EXPORT WaitFor : public TaskItem
+{
+public:
+ WaitFor(const Condition &condition) : TaskItem(condition) { }
+};
+
class QTCREATOR_UTILS_EXPORT ParallelLimit : public TaskItem
{
public:
@@ -244,6 +303,16 @@ public:
OnGroupError(const GroupEndHandler &handler) : TaskItem({{}, {}, handler}) {}
};
+// Synchronous invocation. Similarly to Group - isn't counted as a task inside taskCount()
+class QTCREATOR_UTILS_EXPORT Sync : public Group
+{
+public:
+ using SynchronousMethod = std::function<bool()>;
+ Sync(const SynchronousMethod &sync)
+ : Group({OnGroupSetup([sync] { return sync() ? TaskAction::StopWithDone
+ : TaskAction::StopWithError; })}) {}
+};
+
QTCREATOR_UTILS_EXPORT extern ParallelLimit sequential;
QTCREATOR_UTILS_EXPORT extern ParallelLimit parallel;
QTCREATOR_UTILS_EXPORT extern Workflow stopOnError;
@@ -309,7 +378,7 @@ private:
class TaskTreePrivate;
-class QTCREATOR_UTILS_EXPORT TaskTree : public QObject
+class QTCREATOR_UTILS_EXPORT TaskTree final : public QObject
{
Q_OBJECT
diff --git a/src/libs/utils/terminalcommand.cpp b/src/libs/utils/terminalcommand.cpp
index c25b379f321..102fc42e05d 100644
--- a/src/libs/utils/terminalcommand.cpp
+++ b/src/libs/utils/terminalcommand.cpp
@@ -65,13 +65,7 @@ TerminalCommand TerminalCommand::defaultTerminalEmulator()
if (defaultTerm.command.isEmpty()) {
if (HostOsInfo::isMacHost()) {
- const FilePath termCmd = FilePath::fromString(QCoreApplication::applicationDirPath())
- / "../Resources/scripts/openTerminal.py";
- if (termCmd.exists())
- defaultTerm = {termCmd, "", ""};
- else
- defaultTerm = {"/usr/X11/bin/xterm", "", "-e"};
-
+ return {"Terminal.app", "", ""};
} else if (HostOsInfo::isAnyUnixHost()) {
defaultTerm = {"xterm", "", "-e"};
const Environment env = Environment::systemEnvironment();
diff --git a/src/libs/utils/terminalhooks.cpp b/src/libs/utils/terminalhooks.cpp
new file mode 100644
index 00000000000..6922f5c4304
--- /dev/null
+++ b/src/libs/utils/terminalhooks.cpp
@@ -0,0 +1,190 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "terminalhooks.h"
+
+#include "filepath.h"
+#include "qtcprocess.h"
+#include "terminalcommand.h"
+#include "terminalinterface.h"
+
+#include <QMutex>
+#include <QTemporaryFile>
+
+namespace Utils::Terminal {
+
+FilePath defaultShellForDevice(const FilePath &deviceRoot)
+{
+ if (deviceRoot.osType() == OsTypeWindows)
+ return deviceRoot.withNewPath("cmd.exe").searchInPath();
+
+ const Environment env = deviceRoot.deviceEnvironment();
+ FilePath shell = FilePath::fromUserInput(env.value_or("SHELL", "/bin/sh"));
+
+ if (!shell.isAbsolutePath())
+ shell = env.searchInPath(shell.nativePath());
+
+ if (shell.isEmpty())
+ return shell;
+
+ return shell.onDevice(deviceRoot);
+}
+
+class ExternalTerminalProcessImpl final : public TerminalInterface
+{
+ class ProcessStubCreator : public StubCreator
+ {
+ public:
+ ProcessStubCreator(ExternalTerminalProcessImpl *interface)
+ : m_interface(interface)
+ {}
+
+ void startStubProcess(const CommandLine &cmd, const ProcessSetupData &) override
+ {
+ const TerminalCommand terminal = TerminalCommand::terminalEmulator();
+
+ if (HostOsInfo::isWindowsHost()) {
+ m_terminalProcess.setCommand(cmd);
+ QObject::connect(&m_terminalProcess, &QtcProcess::done, this, [this] {
+ m_interface->onStubExited();
+ });
+ m_terminalProcess.setCreateConsoleOnWindows(true);
+ m_terminalProcess.setProcessMode(ProcessMode::Writer);
+ m_terminalProcess.start();
+ } else if (HostOsInfo::isMacHost() && terminal.command == "Terminal.app") {
+ QTemporaryFile f;
+ f.setAutoRemove(false);
+ f.open();
+ f.setPermissions(QFile::ExeUser | QFile::ReadUser | QFile::WriteUser);
+ f.write("#!/bin/sh\n");
+ f.write("clear\n");
+ f.write(QString("exec '%1' %2\n")
+ .arg(cmd.executable().nativePath())
+ .arg(cmd.arguments())
+ .toUtf8());
+ f.close();
+
+ const QString path = f.fileName();
+ const QString exe
+ = QString("tell app \"Terminal\" to do script \"'%1'; rm -f '%1'; exit\"")
+ .arg(path);
+
+ m_terminalProcess.setCommand(
+ {"osascript", {"-e", "tell app \"Terminal\" to activate", "-e", exe}});
+ m_terminalProcess.runBlocking();
+ } else {
+ CommandLine cmdLine = {terminal.command, {terminal.executeArgs}};
+ cmdLine.addCommandLineAsArgs(cmd, CommandLine::Raw);
+
+ m_terminalProcess.setCommand(cmdLine);
+ m_terminalProcess.start();
+ }
+ }
+
+ ExternalTerminalProcessImpl *m_interface;
+ QtcProcess m_terminalProcess;
+ };
+
+public:
+ ExternalTerminalProcessImpl() { setStubCreator(new ProcessStubCreator(this)); }
+};
+
+class HooksPrivate
+{
+public:
+ HooksPrivate()
+ : m_getTerminalCommandsForDevicesHook([] { return QList<NameAndCommandLine>{}; })
+ {
+ auto openTerminal = [](const OpenTerminalParameters &parameters) {
+ DeviceFileHooks::instance().openTerminal(parameters.workingDirectory.value_or(
+ FilePath{}),
+ parameters.environment.value_or(Environment{}));
+ };
+ auto createProcessInterface = []() { return new ExternalTerminalProcessImpl(); };
+
+ addCallbackSet("External", {openTerminal, createProcessInterface});
+ }
+
+ void addCallbackSet(const QString &name, const Hooks::CallbackSet &callbackSet)
+ {
+ QMutexLocker lk(&m_mutex);
+ m_callbackSets.push_back(qMakePair(name, callbackSet));
+
+ m_createTerminalProcessInterface
+ = m_callbackSets.back().second.createTerminalProcessInterface;
+ m_openTerminal = m_callbackSets.back().second.openTerminal;
+ }
+
+ void removeCallbackSet(const QString &name)
+ {
+ if (name == "External")
+ return;
+
+ QMutexLocker lk(&m_mutex);
+ m_callbackSets.removeIf([name](const auto &pair) { return pair.first == name; });
+
+ m_createTerminalProcessInterface
+ = m_callbackSets.back().second.createTerminalProcessInterface;
+ m_openTerminal = m_callbackSets.back().second.openTerminal;
+ }
+
+ Hooks::CreateTerminalProcessInterface createTerminalProcessInterface()
+ {
+ QMutexLocker lk(&m_mutex);
+ return m_createTerminalProcessInterface;
+ }
+
+ Hooks::OpenTerminal openTerminal()
+ {
+ QMutexLocker lk(&m_mutex);
+ return m_openTerminal;
+ }
+
+ Hooks::GetTerminalCommandsForDevicesHook m_getTerminalCommandsForDevicesHook;
+
+private:
+ Hooks::OpenTerminal m_openTerminal;
+ Hooks::CreateTerminalProcessInterface m_createTerminalProcessInterface;
+
+ QMutex m_mutex;
+ QList<QPair<QString, Hooks::CallbackSet>> m_callbackSets;
+};
+
+Hooks &Hooks::instance()
+{
+ static Hooks manager;
+ return manager;
+}
+
+Hooks::Hooks()
+ : d(new HooksPrivate())
+{}
+
+Hooks::~Hooks() = default;
+
+void Hooks::openTerminal(const OpenTerminalParameters &parameters) const
+{
+ d->openTerminal()(parameters);
+}
+
+ProcessInterface *Hooks::createTerminalProcessInterface() const
+{
+ return d->createTerminalProcessInterface()();
+}
+
+Hooks::GetTerminalCommandsForDevicesHook &Hooks::getTerminalCommandsForDevicesHook()
+{
+ return d->m_getTerminalCommandsForDevicesHook;
+}
+
+void Hooks::addCallbackSet(const QString &name, const CallbackSet &callbackSet)
+{
+ d->addCallbackSet(name, callbackSet);
+}
+
+void Hooks::removeCallbackSet(const QString &name)
+{
+ d->removeCallbackSet(name);
+}
+
+} // namespace Utils::Terminal
diff --git a/src/libs/utils/terminalhooks.h b/src/libs/utils/terminalhooks.h
new file mode 100644
index 00000000000..5a614400f4f
--- /dev/null
+++ b/src/libs/utils/terminalhooks.h
@@ -0,0 +1,93 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "commandline.h"
+#include "environment.h"
+#include "filepath.h"
+#include "id.h"
+
+#include <functional>
+#include <memory>
+
+namespace Utils {
+class ProcessInterface;
+
+template<typename R, typename... Params>
+class Hook
+{
+public:
+ using Callback = std::function<R(Params...)>;
+
+public:
+ Hook() = delete;
+ Hook(const Hook &other) = delete;
+ Hook(Hook &&other) = delete;
+ Hook &operator=(const Hook &other) = delete;
+ Hook &operator=(Hook &&other) = delete;
+
+ explicit Hook(Callback defaultCallback) { set(defaultCallback); }
+
+ void set(Callback cb) { m_callback = cb; }
+ R operator()(Params &&...params) { return m_callback(std::forward<Params>(params)...); }
+
+private:
+ Callback m_callback;
+};
+
+namespace Terminal {
+class HooksPrivate;
+
+enum class ExitBehavior { Close, Restart, Keep };
+
+struct OpenTerminalParameters
+{
+ std::optional<CommandLine> shellCommand;
+ std::optional<FilePath> workingDirectory;
+ std::optional<Environment> environment;
+ ExitBehavior m_exitBehavior{ExitBehavior::Close};
+ std::optional<Id> identifier{std::nullopt};
+};
+
+struct NameAndCommandLine
+{
+ QString name;
+ CommandLine commandLine;
+};
+
+QTCREATOR_UTILS_EXPORT FilePath defaultShellForDevice(const FilePath &deviceRoot);
+
+class QTCREATOR_UTILS_EXPORT Hooks
+{
+public:
+ using OpenTerminal = std::function<void(const OpenTerminalParameters &)>;
+ using CreateTerminalProcessInterface = std::function<ProcessInterface *()>;
+
+ struct CallbackSet
+ {
+ OpenTerminal openTerminal;
+ CreateTerminalProcessInterface createTerminalProcessInterface;
+ };
+
+ using GetTerminalCommandsForDevicesHook = Hook<QList<NameAndCommandLine>>;
+
+public:
+ static Hooks &instance();
+ ~Hooks();
+
+ GetTerminalCommandsForDevicesHook &getTerminalCommandsForDevicesHook();
+
+ void openTerminal(const OpenTerminalParameters &parameters) const;
+ ProcessInterface *createTerminalProcessInterface() const;
+
+ void addCallbackSet(const QString &name, const CallbackSet &callbackSet);
+ void removeCallbackSet(const QString &name);
+
+private:
+ Hooks();
+ std::unique_ptr<HooksPrivate> d;
+};
+
+} // namespace Terminal
+} // namespace Utils
diff --git a/src/libs/utils/terminalinterface.cpp b/src/libs/utils/terminalinterface.cpp
new file mode 100644
index 00000000000..58d48cb910a
--- /dev/null
+++ b/src/libs/utils/terminalinterface.cpp
@@ -0,0 +1,411 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "terminalinterface.h"
+
+#include "utilstr.h"
+
+#include <QLocalServer>
+#include <QLocalSocket>
+#include <QLoggingCategory>
+#include <QTemporaryDir>
+#include <QTemporaryFile>
+#include <QTextStream>
+#include <QThread>
+#include <QTimer>
+
+Q_LOGGING_CATEGORY(terminalInterfaceLog, "qtc.terminalinterface", QtWarningMsg)
+
+namespace Utils {
+
+static QString msgCommChannelFailed(const QString &error)
+{
+ return Tr::tr("Cannot set up communication channel: %1").arg(error);
+}
+
+static QString msgCannotCreateTempFile(const QString &why)
+{
+ return Tr::tr("Cannot create temporary file: %1").arg(why);
+}
+
+static QString msgCannotWriteTempFile()
+{
+ return Tr::tr("Cannot write temporary file. Disk full?");
+}
+
+static QString msgCannotCreateTempDir(const QString &dir, const QString &why)
+{
+ return Tr::tr("Cannot create temporary directory \"%1\": %2").arg(dir, why);
+}
+
+static QString msgUnexpectedOutput(const QByteArray &what)
+{
+ return Tr::tr("Unexpected output from helper program (%1).").arg(QString::fromLatin1(what));
+}
+
+static QString msgCannotChangeToWorkDir(const FilePath &dir, const QString &why)
+{
+ return Tr::tr("Cannot change to working directory \"%1\": %2").arg(dir.toString(), why);
+}
+
+static QString msgCannotExecute(const QString &p, const QString &why)
+{
+ return Tr::tr("Cannot execute \"%1\": %2").arg(p, why);
+}
+
+static QString msgPromptToClose()
+{
+ // Shown in a terminal which might have a different character set on Windows.
+ return Tr::tr("Press <RETURN> to close this window...");
+}
+
+class TerminalInterfacePrivate : public QObject
+{
+ Q_OBJECT
+public:
+ TerminalInterfacePrivate(TerminalInterface *p, bool waitOnExit)
+ : q(p)
+ , waitOnExit(waitOnExit)
+ {
+ connect(&stubServer,
+ &QLocalServer::newConnection,
+ q,
+ &TerminalInterface::onNewStubConnection);
+ }
+
+public:
+ QLocalServer stubServer;
+ QLocalSocket *stubSocket = nullptr;
+
+ int stubProcessId = 0;
+ int inferiorProcessId = 0;
+ int inferiorThreadId = 0;
+
+ std::unique_ptr<QTemporaryFile> envListFile;
+ QTemporaryDir tempDir;
+
+ std::unique_ptr<QTimer> stubConnectTimeoutTimer;
+
+ ProcessResultData processResultData;
+ TerminalInterface *q;
+
+ StubCreator *stubCreator{nullptr};
+
+ const bool waitOnExit;
+};
+
+TerminalInterface::TerminalInterface(bool waitOnExit)
+ : d(new TerminalInterfacePrivate(this, waitOnExit))
+{}
+
+TerminalInterface::~TerminalInterface()
+{
+ if (d->stubSocket && d->stubSocket->state() == QLocalSocket::ConnectedState) {
+ if (d->inferiorProcessId)
+ killInferiorProcess();
+ killStubProcess();
+ }
+ if (d->stubCreator)
+ d->stubCreator->deleteLater();
+ delete d;
+}
+
+void TerminalInterface::setStubCreator(StubCreator *creator)
+{
+ d->stubCreator = creator;
+}
+
+int TerminalInterface::inferiorProcessId() const
+{
+ return d->inferiorProcessId;
+}
+
+int TerminalInterface::inferiorThreadId() const
+{
+ return d->inferiorThreadId;
+}
+
+static QString errnoToString(int code)
+{
+ return QString::fromLocal8Bit(strerror(code));
+}
+
+void TerminalInterface::onNewStubConnection()
+{
+ d->stubConnectTimeoutTimer.reset();
+
+ d->stubSocket = d->stubServer.nextPendingConnection();
+ if (!d->stubSocket)
+ return;
+
+ connect(d->stubSocket, &QIODevice::readyRead, this, &TerminalInterface::onStubReadyRead);
+
+ if (HostOsInfo::isAnyUnixHost())
+ connect(d->stubSocket, &QLocalSocket::disconnected, this, &TerminalInterface::onStubExited);
+}
+
+void TerminalInterface::onStubExited()
+{
+ // The stub exit might get noticed before we read the pid for the kill on Windows
+ // or the error status elsewhere.
+ if (d->stubSocket && d->stubSocket->state() == QLocalSocket::ConnectedState)
+ d->stubSocket->waitForDisconnected();
+
+ shutdownStubServer();
+ d->envListFile.reset();
+
+ if (d->inferiorProcessId)
+ emitFinished(-1, QProcess::CrashExit);
+}
+
+void TerminalInterface::onStubReadyRead()
+{
+ while (d->stubSocket && d->stubSocket->canReadLine()) {
+ QByteArray out = d->stubSocket->readLine();
+ out.chop(1); // remove newline
+ if (out.startsWith("err:chdir ")) {
+ emitError(QProcess::FailedToStart,
+ msgCannotChangeToWorkDir(m_setup.m_workingDirectory,
+ errnoToString(out.mid(10).toInt())));
+ } else if (out.startsWith("err:exec ")) {
+ emitError(QProcess::FailedToStart,
+ msgCannotExecute(m_setup.m_commandLine.executable().toString(),
+ errnoToString(out.mid(9).toInt())));
+ } else if (out.startsWith("spid ")) {
+ d->envListFile.reset();
+ d->envListFile = nullptr;
+ } else if (out.startsWith("pid ")) {
+ d->inferiorProcessId = out.mid(4).toInt();
+ emit started(d->inferiorProcessId, d->inferiorThreadId);
+ } else if (out.startsWith("thread ")) { // Windows only
+ d->inferiorThreadId = out.mid(7).toLongLong();
+ } else if (out.startsWith("exit ")) {
+ emitFinished(out.mid(5).toInt(), QProcess::NormalExit);
+ } else if (out.startsWith("crash ")) {
+ emitFinished(out.mid(6).toInt(), QProcess::CrashExit);
+ } else {
+ emitError(QProcess::UnknownError, msgUnexpectedOutput(out));
+ break;
+ }
+ }
+}
+
+expected_str<void> TerminalInterface::startStubServer()
+{
+ if (HostOsInfo::isWindowsHost()) {
+ if (d->stubServer.listen(QString::fromLatin1("creator-%1-%2")
+ .arg(QCoreApplication::applicationPid())
+ .arg(rand())))
+ return {};
+ return make_unexpected(d->stubServer.errorString());
+ }
+
+ // We need to put the socket in a private directory, as some systems simply do not
+ // check the file permissions of sockets.
+ if (!QDir(d->tempDir.path())
+ .mkdir("socket")) { // QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner
+ return make_unexpected(msgCannotCreateTempDir(d->tempDir.filePath("socket"),
+ QString::fromLocal8Bit(strerror(errno))));
+ }
+
+ if (!QFile::setPermissions(d->tempDir.filePath("socket"),
+ QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner)) {
+ return make_unexpected(Tr::tr("Cannot set permissions on temporary directory \"%1\": %2")
+ .arg(d->tempDir.filePath("socket"))
+ .arg(QString::fromLocal8Bit(strerror(errno))));
+ }
+
+ const QString socketPath = d->tempDir.filePath("socket/stub-socket");
+ if (!d->stubServer.listen(socketPath)) {
+ return make_unexpected(
+ Tr::tr("Cannot create socket \"%1\": %2").arg(socketPath, d->stubServer.errorString()));
+ }
+ return {};
+}
+
+void TerminalInterface::shutdownStubServer()
+{
+ if (d->stubSocket) {
+ // Read potentially remaining data
+ onStubReadyRead();
+ // avoid getting queued readyRead signals
+ d->stubSocket->disconnect();
+ // we might be called from the disconnected signal of stubSocket
+ d->stubSocket->deleteLater();
+ }
+ d->stubSocket = nullptr;
+ if (d->stubServer.isListening())
+ d->stubServer.close();
+}
+
+void TerminalInterface::emitError(QProcess::ProcessError error, const QString &errorString)
+{
+ d->processResultData.m_error = error;
+ d->processResultData.m_errorString = errorString;
+ if (error == QProcess::FailedToStart)
+ emit done(d->processResultData);
+}
+
+void TerminalInterface::emitFinished(int exitCode, QProcess::ExitStatus exitStatus)
+{
+ d->inferiorProcessId = 0;
+ d->inferiorThreadId = 0;
+ d->processResultData.m_exitCode = exitCode;
+ d->processResultData.m_exitStatus = exitStatus;
+ emit done(d->processResultData);
+}
+
+bool TerminalInterface::isRunning() const
+{
+ return d->stubSocket && d->stubSocket->isOpen();
+}
+
+void TerminalInterface::cleanupAfterStartFailure(const QString &errorMessage)
+{
+ shutdownStubServer();
+ emitError(QProcess::FailedToStart, errorMessage);
+ d->envListFile.reset();
+}
+
+void TerminalInterface::sendCommand(char c)
+{
+ if (d->stubSocket && d->stubSocket->isWritable()) {
+ d->stubSocket->write(&c, 1);
+ d->stubSocket->flush();
+ }
+}
+
+void TerminalInterface::killInferiorProcess()
+{
+ sendCommand('k');
+ if (d->stubSocket)
+ d->stubSocket->waitForReadyRead();
+}
+
+void TerminalInterface::killStubProcess()
+{
+ if (!isRunning())
+ return;
+
+ sendCommand('s');
+ if (d->stubSocket)
+ d->stubSocket->waitForReadyRead();
+ shutdownStubServer();
+}
+
+void TerminalInterface::start()
+{
+ if (isRunning())
+ return;
+
+ const expected_str<void> result = startStubServer();
+ if (!result) {
+ emitError(QProcess::FailedToStart, msgCommChannelFailed(result.error()));
+ return;
+ }
+
+ Environment finalEnv = m_setup.m_environment;
+
+ if (HostOsInfo::isWindowsHost()) {
+ if (!finalEnv.hasKey("PATH")) {
+ const QString path = qtcEnvironmentVariable("PATH");
+ if (!path.isEmpty())
+ finalEnv.set("PATH", path);
+ }
+ if (!finalEnv.hasKey("SystemRoot")) {
+ const QString systemRoot = qtcEnvironmentVariable("SystemRoot");
+ if (!systemRoot.isEmpty())
+ finalEnv.set("SystemRoot", systemRoot);
+ }
+ } else if (HostOsInfo::isMacHost()) {
+ finalEnv.set("TERM", "xterm-256color");
+ }
+
+ if (finalEnv.hasChanges()) {
+ d->envListFile = std::make_unique<QTemporaryFile>(this);
+ if (!d->envListFile->open()) {
+ cleanupAfterStartFailure(msgCannotCreateTempFile(d->envListFile->errorString()));
+ return;
+ }
+ QTextStream stream(d->envListFile.get());
+ finalEnv.forEachEntry([&stream](const QString &key, const QString &value, bool) {
+ stream << key << '=' << value << '\0';
+ });
+
+ if (d->envListFile->error() != QFile::NoError) {
+ cleanupAfterStartFailure(msgCannotWriteTempFile());
+ return;
+ }
+ }
+
+ const FilePath stubPath = FilePath::fromUserInput(QCoreApplication::applicationDirPath())
+ .pathAppended(QLatin1String(RELATIVE_LIBEXEC_PATH))
+ .pathAppended((HostOsInfo::isWindowsHost()
+ ? QLatin1String("qtcreator_process_stub.exe")
+ : QLatin1String("qtcreator_process_stub")));
+
+ CommandLine cmd{stubPath, {"-s", d->stubServer.fullServerName()}};
+
+ if (!m_setup.m_workingDirectory.isEmpty())
+ cmd.addArgs({"-w", m_setup.m_workingDirectory.nativePath()});
+
+ if (m_setup.m_terminalMode == TerminalMode::Debug)
+ cmd.addArg("-d");
+
+ if (terminalInterfaceLog().isDebugEnabled())
+ cmd.addArg("-v");
+
+ if (d->envListFile)
+ cmd.addArgs({"-e", d->envListFile->fileName()});
+
+ cmd.addArgs({"--wait", d->waitOnExit ? msgPromptToClose() : ""});
+
+ cmd.addArgs({"--", m_setup.m_commandLine.executable().nativePath()});
+ cmd.addArgs(m_setup.m_commandLine.arguments(), CommandLine::Raw);
+
+ QTC_ASSERT(d->stubCreator, return);
+
+ QMetaObject::invokeMethod(
+ d->stubCreator,
+ [cmd, this] { d->stubCreator->startStubProcess(cmd, m_setup); },
+ d->stubCreator->thread() == QThread::currentThread() ? Qt::DirectConnection
+ : Qt::BlockingQueuedConnection);
+
+ d->stubConnectTimeoutTimer = std::make_unique<QTimer>();
+
+ connect(d->stubConnectTimeoutTimer.get(), &QTimer::timeout, this, [this] {
+ killInferiorProcess();
+ killStubProcess();
+ });
+ d->stubConnectTimeoutTimer->setSingleShot(true);
+ d->stubConnectTimeoutTimer->start(10000);
+}
+
+qint64 TerminalInterface::write(const QByteArray &data)
+{
+ Q_UNUSED(data);
+ QTC_CHECK(false);
+ return -1;
+}
+void TerminalInterface::sendControlSignal(ControlSignal controlSignal)
+{
+ switch (controlSignal) {
+ case ControlSignal::Terminate:
+ case ControlSignal::Kill:
+ killInferiorProcess();
+ break;
+ case ControlSignal::Interrupt:
+ sendCommand('i');
+ break;
+ case ControlSignal::KickOff:
+ sendCommand('c');
+ break;
+ case ControlSignal::CloseWriteChannel:
+ QTC_CHECK(false);
+ break;
+ }
+}
+
+} // namespace Utils
+
+#include "terminalinterface.moc"
diff --git a/src/libs/utils/terminalinterface.h b/src/libs/utils/terminalinterface.h
new file mode 100644
index 00000000000..feb19875ba2
--- /dev/null
+++ b/src/libs/utils/terminalinterface.h
@@ -0,0 +1,61 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "commandline.h"
+#include "expected.h"
+#include "processinterface.h"
+
+namespace Utils {
+
+class TerminalInterfacePrivate;
+
+class StubCreator : public QObject
+{
+public:
+ virtual void startStubProcess(const CommandLine &cmd, const ProcessSetupData &setup) = 0;
+};
+
+class QTCREATOR_UTILS_EXPORT TerminalInterface : public ProcessInterface
+{
+ friend class TerminalInterfacePrivate;
+ friend class StubCreator;
+
+public:
+ TerminalInterface(bool waitOnExit = true);
+ ~TerminalInterface() override;
+
+ int inferiorProcessId() const;
+ int inferiorThreadId() const;
+
+ void setStubCreator(StubCreator *creator);
+
+ void emitError(QProcess::ProcessError error, const QString &errorString);
+ void emitFinished(int exitCode, QProcess::ExitStatus exitStatus);
+ void onStubExited();
+
+protected:
+ void onNewStubConnection();
+ void onStubReadyRead();
+
+ void sendCommand(char c);
+
+ void killInferiorProcess();
+ void killStubProcess();
+
+ expected_str<void> startStubServer();
+ void shutdownStubServer();
+ void cleanupAfterStartFailure(const QString &errorMessage);
+
+ bool isRunning() const;
+
+private:
+ void start() override;
+ qint64 write(const QByteArray &data) override;
+ void sendControlSignal(ControlSignal controlSignal) override;
+
+ TerminalInterfacePrivate *d{nullptr};
+};
+
+} // namespace Utils
diff --git a/src/libs/utils/terminalprocess.cpp b/src/libs/utils/terminalprocess.cpp
deleted file mode 100644
index bd0333c9d74..00000000000
--- a/src/libs/utils/terminalprocess.cpp
+++ /dev/null
@@ -1,721 +0,0 @@
-// Copyright (C) 2022 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#include "terminalprocess_p.h"
-
-#include "commandline.h"
-#include "environment.h"
-#include "hostosinfo.h"
-#include "qtcassert.h"
-#include "qtcprocess.h"
-#include "terminalcommand.h"
-#include "utilstr.h"
-
-#include <QCoreApplication>
-#include <QLocalServer>
-#include <QLocalSocket>
-#include <QRegularExpression>
-#include <QTemporaryFile>
-#include <QTextCodec>
-#include <QTimer>
-#include <QWinEventNotifier>
-
-#ifdef Q_OS_WIN
-
-#include "winutils.h"
-
-#include <cstring>
-#include <stdlib.h>
-#include <windows.h>
-
-#else
-
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <errno.h>
-#include <string.h>
-#include <unistd.h>
-
-#endif
-
-namespace Utils {
-namespace Internal {
-
-static QString modeOption(TerminalMode m)
-{
- switch (m) {
- case TerminalMode::Run:
- return QLatin1String("run");
- case TerminalMode::Debug:
- return QLatin1String("debug");
- case TerminalMode::Suspend:
- return QLatin1String("suspend");
- case TerminalMode::Off:
- QTC_CHECK(false);
- break;
- }
- return {};
-}
-
-static QString msgCommChannelFailed(const QString &error)
-{
- return Tr::tr("Cannot set up communication channel: %1").arg(error);
-}
-
-static QString msgPromptToClose()
-{
- // Shown in a terminal which might have a different character set on Windows.
- return Tr::tr("Press <RETURN> to close this window...");
-}
-
-static QString msgCannotCreateTempFile(const QString &why)
-{
- return Tr::tr("Cannot create temporary file: %1").arg(why);
-}
-
-static QString msgCannotWriteTempFile()
-{
- return Tr::tr("Cannot write temporary file. Disk full?");
-}
-
-static QString msgCannotCreateTempDir(const QString & dir, const QString &why)
-{
- return Tr::tr("Cannot create temporary directory \"%1\": %2").arg(dir, why);
-}
-
-static QString msgUnexpectedOutput(const QByteArray &what)
-{
- return Tr::tr("Unexpected output from helper program (%1).")
- .arg(QString::fromLatin1(what));
-}
-
-static QString msgCannotChangeToWorkDir(const FilePath &dir, const QString &why)
-{
- return Tr::tr("Cannot change to working directory \"%1\": %2").arg(dir.toString(), why);
-}
-
-static QString msgCannotExecute(const QString & p, const QString &why)
-{
- return Tr::tr("Cannot execute \"%1\": %2").arg(p, why);
-}
-
-class TerminalProcessPrivate
-{
-public:
- TerminalProcessPrivate(QObject *parent)
- : m_stubServer(parent)
- , m_process(parent) {}
-
- qint64 m_processId = 0;
- ProcessResultData m_result;
- QLocalServer m_stubServer;
- QLocalSocket *m_stubSocket = nullptr;
- QTemporaryFile *m_tempFile = nullptr;
-
- // Used on Unix only
- QtcProcess m_process;
- QTimer *m_stubConnectTimer = nullptr;
- QByteArray m_stubServerDir;
-
- // Used on Windows only
- qint64 m_appMainThreadId = 0;
-
-#ifdef Q_OS_WIN
- PROCESS_INFORMATION *m_pid = nullptr;
- HANDLE m_hInferior = NULL;
- QWinEventNotifier *inferiorFinishedNotifier = nullptr;
- QWinEventNotifier *processFinishedNotifier = nullptr;
-#endif
-};
-
-TerminalImpl::TerminalImpl()
- : d(new TerminalProcessPrivate(this))
-{
- connect(&d->m_stubServer, &QLocalServer::newConnection,
- this, &TerminalImpl::stubConnectionAvailable);
-
- d->m_process.setProcessChannelMode(QProcess::ForwardedChannels);
-}
-
-TerminalImpl::~TerminalImpl()
-{
- stopProcess();
- delete d;
-}
-
-void TerminalImpl::start()
-{
- if (isRunning())
- return;
-
- d->m_result = {};
-
-#ifdef Q_OS_WIN
-
- QString pcmd;
- QString pargs;
- if (m_setup.m_terminalMode != TerminalMode::Run) { // The debugger engines already pre-process the arguments.
- pcmd = m_setup.m_commandLine.executable().toString();
- pargs = m_setup.m_commandLine.arguments();
- } else {
- ProcessArgs outArgs;
- ProcessArgs::prepareCommand(m_setup.m_commandLine, &pcmd, &outArgs,
- &m_setup.m_environment, &m_setup.m_workingDirectory);
- pargs = outArgs.toWindowsArgs();
- }
-
- const QString err = stubServerListen();
- if (!err.isEmpty()) {
- emitError(QProcess::FailedToStart, msgCommChannelFailed(err));
- return;
- }
-
- QStringList env = m_setup.m_environment.toStringList();
- if (!env.isEmpty()) {
- d->m_tempFile = new QTemporaryFile();
- if (!d->m_tempFile->open()) {
- cleanupAfterStartFailure(msgCannotCreateTempFile(d->m_tempFile->errorString()));
- return;
- }
- QString outString;
- QTextStream out(&outString);
- // Add PATH and SystemRoot environment variables in case they are missing
- const QStringList fixedEnvironment = [env] {
- QStringList envStrings = env;
- // add PATH if necessary (for DLL loading)
- if (envStrings.filter(QRegularExpression("^PATH=.*", QRegularExpression::CaseInsensitiveOption)).isEmpty()) {
- const QString path = qtcEnvironmentVariable("PATH");
- if (!path.isEmpty())
- envStrings.prepend(QString::fromLatin1("PATH=%1").arg(path));
- }
- // add systemroot if needed
- if (envStrings.filter(QRegularExpression("^SystemRoot=.*", QRegularExpression::CaseInsensitiveOption)).isEmpty()) {
- const QString systemRoot = qtcEnvironmentVariable("SystemRoot");
- if (!systemRoot.isEmpty())
- envStrings.prepend(QString::fromLatin1("SystemRoot=%1").arg(systemRoot));
- }
- return envStrings;
- }();
-
- for (const QString &var : fixedEnvironment)
- out << var << QChar(0);
- out << QChar(0);
- const QTextCodec *textCodec = QTextCodec::codecForName("UTF-16LE");
- QTC_CHECK(textCodec);
- const QByteArray outBytes = textCodec ? textCodec->fromUnicode(outString) : QByteArray();
- if (!textCodec || d->m_tempFile->write(outBytes) < 0) {
- cleanupAfterStartFailure(msgCannotWriteTempFile());
- return;
- }
- d->m_tempFile->flush();
- }
-
- STARTUPINFO si;
- ZeroMemory(&si, sizeof(si));
- si.cb = sizeof(si);
-
- d->m_pid = new PROCESS_INFORMATION;
- ZeroMemory(d->m_pid, sizeof(PROCESS_INFORMATION));
-
- QString workDir = m_setup.m_workingDirectory.toUserOutput();
- if (!workDir.isEmpty() && !workDir.endsWith(QLatin1Char('\\')))
- workDir.append(QLatin1Char('\\'));
-
- // Quote a Windows command line correctly for the "CreateProcess" API
- static const auto quoteWinCommand = [](const QString &program) {
- const QChar doubleQuote = QLatin1Char('"');
-
- // add the program as the first arg ... it works better
- QString programName = program;
- programName.replace(QLatin1Char('/'), QLatin1Char('\\'));
- if (!programName.startsWith(doubleQuote) && !programName.endsWith(doubleQuote)
- && programName.contains(QLatin1Char(' '))) {
- programName.prepend(doubleQuote);
- programName.append(doubleQuote);
- }
- return programName;
- };
- static const auto quoteWinArgument = [](const QString &arg) {
- if (arg.isEmpty())
- return QString::fromLatin1("\"\"");
-
- QString ret(arg);
- // Quotes are escaped and their preceding backslashes are doubled.
- ret.replace(QRegularExpression("(\\\\*)\""), "\\1\\1\\\"");
- if (ret.contains(QRegularExpression("\\s"))) {
- // The argument must not end with a \ since this would be interpreted
- // as escaping the quote -- rather put the \ behind the quote: e.g.
- // rather use "foo"\ than "foo\"
- int i = ret.length();
- while (i > 0 && ret.at(i - 1) == QLatin1Char('\\'))
- --i;
- ret.insert(i, QLatin1Char('"'));
- ret.prepend(QLatin1Char('"'));
- }
- return ret;
- };
- static const auto createWinCommandlineMultiArgs = [](const QString &program, const QStringList &args) {
- QString programName = quoteWinCommand(program);
- for (const QString &arg : args) {
- programName += QLatin1Char(' ');
- programName += quoteWinArgument(arg);
- }
- return programName;
- };
- static const auto createWinCommandlineSingleArg = [](const QString &program, const QString &args)
- {
- QString programName = quoteWinCommand(program);
- if (!args.isEmpty()) {
- programName += QLatin1Char(' ');
- programName += args;
- }
- return programName;
- };
-
- QStringList stubArgs;
- stubArgs << modeOption(m_setup.m_terminalMode)
- << d->m_stubServer.fullServerName()
- << workDir
- << (d->m_tempFile ? d->m_tempFile->fileName() : QString())
- << createWinCommandlineSingleArg(pcmd, pargs)
- << msgPromptToClose();
-
- const QString cmdLine = createWinCommandlineMultiArgs(
- QCoreApplication::applicationDirPath() + QLatin1String("/qtcreator_process_stub.exe"), stubArgs);
-
- bool success = CreateProcessW(0, (WCHAR*)cmdLine.utf16(),
- 0, 0, FALSE, CREATE_NEW_CONSOLE,
- 0, 0,
- &si, d->m_pid);
-
- if (!success) {
- delete d->m_pid;
- d->m_pid = nullptr;
- const QString msg = Tr::tr("The process \"%1\" could not be started: %2")
- .arg(cmdLine, winErrorMessage(GetLastError()));
- cleanupAfterStartFailure(msg);
- return;
- }
-
- d->processFinishedNotifier = new QWinEventNotifier(d->m_pid->hProcess, this);
- connect(d->processFinishedNotifier, &QWinEventNotifier::activated,
- this, &TerminalImpl::stubExited);
-
-#else
-
- ProcessArgs::SplitError perr;
- ProcessArgs pargs = ProcessArgs::prepareArgs(m_setup.m_commandLine.arguments(),
- &perr,
- HostOsInfo::hostOs(),
- &m_setup.m_environment,
- &m_setup.m_workingDirectory,
- m_setup.m_abortOnMetaChars);
-
- QString pcmd;
- if (perr == ProcessArgs::SplitOk) {
- pcmd = m_setup.m_commandLine.executable().toString();
- } else {
- if (perr != ProcessArgs::FoundMeta) {
- emitError(QProcess::FailedToStart, Tr::tr("Quoting error in command."));
- return;
- }
- if (m_setup.m_terminalMode == TerminalMode::Debug) {
- // FIXME: QTCREATORBUG-2809
- emitError(QProcess::FailedToStart,
- Tr::tr("Debugging complex shell commands in a terminal"
- " is currently not supported."));
- return;
- }
- pcmd = qtcEnvironmentVariable("SHELL", "/bin/sh");
- pargs = ProcessArgs::createUnixArgs(
- {"-c", (ProcessArgs::quoteArg(m_setup.m_commandLine.executable().toString())
- + ' ' + m_setup.m_commandLine.arguments())});
- }
-
- ProcessArgs::SplitError qerr;
- const TerminalCommand terminal = TerminalCommand::terminalEmulator();
- const ProcessArgs terminalArgs = ProcessArgs::prepareArgs(terminal.executeArgs,
- &qerr,
- HostOsInfo::hostOs(),
- &m_setup.m_environment,
- &m_setup.m_workingDirectory);
- if (qerr != ProcessArgs::SplitOk) {
- emitError(QProcess::FailedToStart,
- qerr == ProcessArgs::BadQuoting
- ? Tr::tr("Quoting error in terminal command.")
- : Tr::tr("Terminal command may not be a shell command."));
- return;
- }
-
- const QString err = stubServerListen();
- if (!err.isEmpty()) {
- emitError(QProcess::FailedToStart, msgCommChannelFailed(err));
- return;
- }
-
- m_setup.m_environment.unset(QLatin1String("TERM"));
-
- const QStringList env = m_setup.m_environment.toStringList();
- if (!env.isEmpty()) {
- d->m_tempFile = new QTemporaryFile(this);
- if (!d->m_tempFile->open()) {
- cleanupAfterStartFailure(msgCannotCreateTempFile(d->m_tempFile->errorString()));
- return;
- }
- QByteArray contents;
- for (const QString &var : env) {
- const QByteArray l8b = var.toLocal8Bit();
- contents.append(l8b.constData(), l8b.size() + 1);
- }
- if (d->m_tempFile->write(contents) != contents.size() || !d->m_tempFile->flush()) {
- cleanupAfterStartFailure(msgCannotWriteTempFile());
- return;
- }
- }
-
- const QString stubPath = QCoreApplication::applicationDirPath()
- + QLatin1String("/" RELATIVE_LIBEXEC_PATH "/qtcreator_process_stub");
-
- QStringList allArgs = terminalArgs.toUnixArgs();
-
- allArgs << stubPath
- << modeOption(m_setup.m_terminalMode)
- << d->m_stubServer.fullServerName()
- << msgPromptToClose()
- << m_setup.m_workingDirectory.path()
- << (d->m_tempFile ? d->m_tempFile->fileName() : QString())
- << QString::number(getpid())
- << pcmd
- << pargs.toUnixArgs();
-
- if (terminal.needsQuotes)
- allArgs = QStringList { ProcessArgs::joinArgs(allArgs) };
-
- d->m_process.setEnvironment(m_setup.m_environment);
- d->m_process.setCommand({terminal.command, allArgs});
- d->m_process.setProcessImpl(m_setup.m_processImpl);
- d->m_process.setReaperTimeout(m_setup.m_reaperTimeout);
-
- d->m_process.start();
- if (!d->m_process.waitForStarted()) {
- const QString msg = Tr::tr("Cannot start the terminal emulator \"%1\", change the "
- "setting in the Environment preferences. (%2)")
- .arg(terminal.command.toUserOutput(), d->m_process.errorString());
- cleanupAfterStartFailure(msg);
- return;
- }
- d->m_stubConnectTimer = new QTimer(this);
- connect(d->m_stubConnectTimer, &QTimer::timeout, this, &TerminalImpl::stopProcess);
- d->m_stubConnectTimer->setSingleShot(true);
- d->m_stubConnectTimer->start(10000);
-
-#endif
-}
-
-void TerminalImpl::cleanupAfterStartFailure(const QString &errorMessage)
-{
- stubServerShutdown();
- emitError(QProcess::FailedToStart, errorMessage);
- delete d->m_tempFile;
- d->m_tempFile = nullptr;
-}
-
-void TerminalImpl::sendControlSignal(ControlSignal controlSignal)
-{
- switch (controlSignal) {
- case ControlSignal::Terminate:
- case ControlSignal::Kill:
- killProcess();
- if (HostOsInfo::isWindowsHost())
- killStub();
- break;
- case ControlSignal::Interrupt:
- sendCommand('i');
- break;
- case ControlSignal::KickOff:
- sendCommand('c');
- break;
- case ControlSignal::CloseWriteChannel:
- QTC_CHECK(false);
- break;
- }
-}
-
-void TerminalImpl::sendCommand(char c)
-{
-#ifdef Q_OS_WIN
- Q_UNUSED(c)
-#else
- if (d->m_stubSocket && d->m_stubSocket->isWritable()) {
- d->m_stubSocket->write(&c, 1);
- d->m_stubSocket->flush();
- }
-#endif
-}
-
-void TerminalImpl::killProcess()
-{
-#ifdef Q_OS_WIN
- if (d->m_hInferior != NULL) {
- TerminateProcess(d->m_hInferior, (unsigned)-1);
- cleanupInferior();
- }
-#else
- sendCommand('k');
-#endif
- d->m_processId = 0;
-}
-
-void TerminalImpl::killStub()
-{
- if (!isRunning())
- return;
-
-#ifdef Q_OS_WIN
- TerminateProcess(d->m_pid->hProcess, (unsigned)-1);
- WaitForSingleObject(d->m_pid->hProcess, INFINITE);
- cleanupStub();
- emitFinished(-1, QProcess::CrashExit);
-#else
- sendCommand('s');
- stubServerShutdown();
- d->m_process.stop();
- d->m_process.waitForFinished();
-#endif
-}
-
-void TerminalImpl::stopProcess()
-{
- killProcess();
- killStub();
-}
-
-bool TerminalImpl::isRunning() const
-{
-#ifdef Q_OS_WIN
- return d->m_pid != nullptr;
-#else
- return d->m_process.state() != QProcess::NotRunning
- || (d->m_stubSocket && d->m_stubSocket->isOpen());
-#endif
-}
-
-QString TerminalImpl::stubServerListen()
-{
-#ifdef Q_OS_WIN
- if (d->m_stubServer.listen(QString::fromLatin1("creator-%1-%2")
- .arg(QCoreApplication::applicationPid())
- .arg(rand())))
- return QString();
- return d->m_stubServer.errorString();
-#else
- // We need to put the socket in a private directory, as some systems simply do not
- // check the file permissions of sockets.
- QString stubFifoDir;
- while (true) {
- {
- QTemporaryFile tf;
- if (!tf.open())
- return msgCannotCreateTempFile(tf.errorString());
- stubFifoDir = tf.fileName();
- }
- // By now the temp file was deleted again
- d->m_stubServerDir = QFile::encodeName(stubFifoDir);
- if (!::mkdir(d->m_stubServerDir.constData(), 0700))
- break;
- if (errno != EEXIST)
- return msgCannotCreateTempDir(stubFifoDir, QString::fromLocal8Bit(strerror(errno)));
- }
- const QString stubServer = stubFifoDir + QLatin1String("/stub-socket");
- if (!d->m_stubServer.listen(stubServer)) {
- ::rmdir(d->m_stubServerDir.constData());
- return Tr::tr("Cannot create socket \"%1\": %2")
- .arg(stubServer, d->m_stubServer.errorString());
- }
- return {};
-#endif
-}
-
-void TerminalImpl::stubServerShutdown()
-{
-#ifdef Q_OS_WIN
- delete d->m_stubSocket;
- d->m_stubSocket = nullptr;
- if (d->m_stubServer.isListening())
- d->m_stubServer.close();
-#else
- if (d->m_stubSocket) {
- readStubOutput(); // we could get the shutdown signal before emptying the buffer
- d->m_stubSocket->disconnect(); // avoid getting queued readyRead signals
- d->m_stubSocket->deleteLater(); // we might be called from the disconnected signal of m_stubSocket
- }
- d->m_stubSocket = nullptr;
- if (d->m_stubServer.isListening()) {
- d->m_stubServer.close();
- ::rmdir(d->m_stubServerDir.constData());
- }
-#endif
-}
-
-void TerminalImpl::stubConnectionAvailable()
-{
- if (d->m_stubConnectTimer) {
- delete d->m_stubConnectTimer;
- d->m_stubConnectTimer = nullptr;
- }
-
- d->m_stubSocket = d->m_stubServer.nextPendingConnection();
- connect(d->m_stubSocket, &QIODevice::readyRead, this, &TerminalImpl::readStubOutput);
-
- if (HostOsInfo::isAnyUnixHost())
- connect(d->m_stubSocket, &QLocalSocket::disconnected, this, &TerminalImpl::stubExited);
-}
-
-static QString errorMsg(int code)
-{
- return QString::fromLocal8Bit(strerror(code));
-}
-
-void TerminalImpl::readStubOutput()
-{
- while (d->m_stubSocket->canReadLine()) {
- QByteArray out = d->m_stubSocket->readLine();
-#ifdef Q_OS_WIN
- out.chop(2); // \r\n
- if (out.startsWith("err:chdir ")) {
- emitError(QProcess::FailedToStart,
- msgCannotChangeToWorkDir(m_setup.m_workingDirectory, winErrorMessage(out.mid(10).toInt())));
- } else if (out.startsWith("err:exec ")) {
- emitError(QProcess::FailedToStart,
- msgCannotExecute(m_setup.m_commandLine.executable().toUserOutput(), winErrorMessage(out.mid(9).toInt())));
- } else if (out.startsWith("thread ")) { // Windows only
- // TODO: ensure that it comes before "pid " comes
- d->m_appMainThreadId = out.mid(7).toLongLong();
- } else if (out.startsWith("pid ")) {
- // Will not need it any more
- delete d->m_tempFile;
- d->m_tempFile = nullptr;
- d->m_processId = out.mid(4).toLongLong();
-
- d->m_hInferior = OpenProcess(
- SYNCHRONIZE | PROCESS_QUERY_INFORMATION | PROCESS_TERMINATE,
- FALSE, d->m_processId);
- if (d->m_hInferior == NULL) {
- emitError(QProcess::FailedToStart,
- Tr::tr("Cannot obtain a handle to the inferior: %1")
- .arg(winErrorMessage(GetLastError())));
- // Uhm, and now what?
- continue;
- }
- d->inferiorFinishedNotifier = new QWinEventNotifier(d->m_hInferior, this);
- connect(d->inferiorFinishedNotifier, &QWinEventNotifier::activated, this, [this] {
- DWORD chldStatus;
-
- if (!GetExitCodeProcess(d->m_hInferior, &chldStatus))
- emitError(QProcess::UnknownError,
- Tr::tr("Cannot obtain exit status from inferior: %1")
- .arg(winErrorMessage(GetLastError())));
- cleanupInferior();
- emitFinished(chldStatus, QProcess::NormalExit);
- });
-
- emit started(d->m_processId, d->m_appMainThreadId);
- } else {
- emitError(QProcess::UnknownError, msgUnexpectedOutput(out));
- TerminateProcess(d->m_pid->hProcess, (unsigned)-1);
- break;
- }
-#else
- out.chop(1); // \n
- if (out.startsWith("err:chdir ")) {
- emitError(QProcess::FailedToStart,
- msgCannotChangeToWorkDir(m_setup.m_workingDirectory, errorMsg(out.mid(10).toInt())));
- } else if (out.startsWith("err:exec ")) {
- emitError(QProcess::FailedToStart,
- msgCannotExecute(m_setup.m_commandLine.executable().toString(), errorMsg(out.mid(9).toInt())));
- } else if (out.startsWith("spid ")) {
- delete d->m_tempFile;
- d->m_tempFile = nullptr;
- } else if (out.startsWith("pid ")) {
- d->m_processId = out.mid(4).toInt();
- emit started(d->m_processId);
- } else if (out.startsWith("exit ")) {
- emitFinished(out.mid(5).toInt(), QProcess::NormalExit);
- } else if (out.startsWith("crash ")) {
- emitFinished(out.mid(6).toInt(), QProcess::CrashExit);
- } else {
- emitError(QProcess::UnknownError, msgUnexpectedOutput(out));
- d->m_process.terminate();
- break;
- }
-#endif
- } // while
-}
-
-void TerminalImpl::stubExited()
-{
- // The stub exit might get noticed before we read the pid for the kill on Windows
- // or the error status elsewhere.
- if (d->m_stubSocket && d->m_stubSocket->state() == QLocalSocket::ConnectedState)
- d->m_stubSocket->waitForDisconnected();
-
-#ifdef Q_OS_WIN
- cleanupStub();
- if (d->m_hInferior != NULL) {
- TerminateProcess(d->m_hInferior, (unsigned)-1);
- cleanupInferior();
- emitFinished(-1, QProcess::CrashExit);
- }
-#else
- stubServerShutdown();
- delete d->m_tempFile;
- d->m_tempFile = nullptr;
- if (d->m_processId)
- emitFinished(-1, QProcess::CrashExit);
-#endif
-}
-
-void TerminalImpl::cleanupInferior()
-{
-#ifdef Q_OS_WIN
- delete d->inferiorFinishedNotifier;
- d->inferiorFinishedNotifier = nullptr;
- CloseHandle(d->m_hInferior);
- d->m_hInferior = NULL;
-#endif
-}
-
-void TerminalImpl::cleanupStub()
-{
-#ifdef Q_OS_WIN
- stubServerShutdown();
- delete d->processFinishedNotifier;
- d->processFinishedNotifier = nullptr;
- CloseHandle(d->m_pid->hThread);
- CloseHandle(d->m_pid->hProcess);
- delete d->m_pid;
- d->m_pid = nullptr;
- delete d->m_tempFile;
- d->m_tempFile = nullptr;
-#endif
-}
-
-void TerminalImpl::emitError(QProcess::ProcessError error, const QString &errorString)
-{
- d->m_result.m_error = error;
- d->m_result.m_errorString = errorString;
- if (error == QProcess::FailedToStart)
- emit done(d->m_result);
-}
-
-void TerminalImpl::emitFinished(int exitCode, QProcess::ExitStatus exitStatus)
-{
- d->m_processId = 0;
- d->m_result.m_exitCode = exitCode;
- d->m_result.m_exitStatus = exitStatus;
- emit done(d->m_result);
-}
-
-
-} // Internal
-} // Utils
diff --git a/src/libs/utils/terminalprocess_p.h b/src/libs/utils/terminalprocess_p.h
deleted file mode 100644
index 27c99cee26e..00000000000
--- a/src/libs/utils/terminalprocess_p.h
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright (C) 2022 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#pragma once
-
-#include "processenums.h"
-#include "processinterface.h"
-#include "qtcassert.h"
-
-#include <QProcess>
-
-namespace Utils {
-
-class CommandLine;
-class Environment;
-class FilePath;
-
-namespace Internal {
-
-class TerminalImpl final : public ProcessInterface
-{
-public:
- TerminalImpl();
- ~TerminalImpl() final;
-
-private:
- void start() final;
- qint64 write(const QByteArray &) final { QTC_CHECK(false); return -1; }
- void sendControlSignal(ControlSignal controlSignal) final;
-
- // OK, however, impl looks a bit different (!= NotRunning vs == Running).
- // Most probably changing it into (== Running) should be OK.
- bool isRunning() const;
-
- void stopProcess();
- void stubConnectionAvailable();
- void readStubOutput();
- void stubExited();
- void cleanupAfterStartFailure(const QString &errorMessage);
- void killProcess();
- void killStub();
- void emitError(QProcess::ProcessError error, const QString &errorString);
- void emitFinished(int exitCode, QProcess::ExitStatus exitStatus);
- QString stubServerListen();
- void stubServerShutdown();
- void cleanupStub();
- void cleanupInferior();
- void sendCommand(char c);
-
- class TerminalProcessPrivate *d;
-};
-
-} // Internal
-} // Utils
diff --git a/src/libs/utils/theme/theme.h b/src/libs/utils/theme/theme.h
index 1fbae4934e2..762f4490f9b 100644
--- a/src/libs/utils/theme/theme.h
+++ b/src/libs/utils/theme/theme.h
@@ -36,7 +36,6 @@ public:
BadgeLabelBackgroundColorUnchecked,
BadgeLabelTextColorChecked,
BadgeLabelTextColorUnchecked,
- CanceledSearchTextColor,
ComboBoxArrowColor,
ComboBoxArrowColorDisabled,
ComboBoxTextColor,
@@ -420,6 +419,28 @@ public:
DSstatePanelBackground,
DSstateHighlight,
+
+ TerminalForeground,
+ TerminalBackground,
+ TerminalSelection,
+ TerminalFindMatch,
+
+ TerminalAnsi0,
+ TerminalAnsi1,
+ TerminalAnsi2,
+ TerminalAnsi3,
+ TerminalAnsi4,
+ TerminalAnsi5,
+ TerminalAnsi6,
+ TerminalAnsi7,
+ TerminalAnsi8,
+ TerminalAnsi9,
+ TerminalAnsi10,
+ TerminalAnsi11,
+ TerminalAnsi12,
+ TerminalAnsi13,
+ TerminalAnsi14,
+ TerminalAnsi15,
};
enum ImageFile {
diff --git a/src/libs/utils/utils.qbs b/src/libs/utils/utils.qbs
index 11c0e342826..35fe753d1c5 100644
--- a/src/libs/utils/utils.qbs
+++ b/src/libs/utils/utils.qbs
@@ -36,6 +36,7 @@ Project {
Depends { name: "Qt"; submodules: ["concurrent", "core-private", "network", "qml", "widgets", "xml"] }
Depends { name: "Qt.macextras"; condition: Qt.core.versionMajor < 6 && qbs.targetOS.contains("macos") }
Depends { name: "app_version_header" }
+ Depends { name: "ptyqt" }
files: [
"QtConcurrentTools",
@@ -126,6 +127,10 @@ Project {
"filepath.h",
"filesearch.cpp",
"filesearch.h",
+ "filestreamer.cpp",
+ "filestreamer.h",
+ "filestreamermanager.cpp",
+ "filestreamermanager.h",
"filesystemmodel.cpp",
"filesystemmodel.h",
"filesystemwatcher.cpp",
@@ -307,8 +312,10 @@ Project {
"temporaryfile.h",
"terminalcommand.cpp",
"terminalcommand.h",
- "terminalprocess.cpp",
- "terminalprocess_p.h",
+ "terminalhooks.cpp",
+ "terminalhooks.h",
+ "terminalinterface.cpp",
+ "terminalinterface.h",
"textfieldcheckbox.cpp",
"textfieldcheckbox.h",
"textfieldcombobox.cpp",
diff --git a/src/libs/utils/utils.qrc b/src/libs/utils/utils.qrc
index 1f2ef64898e..c0f2d2559a1 100644
--- a/src/libs/utils/utils.qrc
+++ b/src/libs/utils/utils.qrc
@@ -36,6 +36,8 @@
<file>images/[email protected]</file>
<file>images/pinned.png</file>
<file>images/[email protected]</file>
+ <file>images/pinned_small.png</file>
+ <file>images/[email protected]</file>
<file>images/broken.png</file>
<file>images/[email protected]</file>
<file>images/notloaded.png</file>
@@ -173,6 +175,8 @@
<file>images/[email protected]</file>
<file>images/iconoverlay_add_background.png</file>
<file>images/[email protected]</file>
+ <file>images/iconoverlay_close_small.png</file>
+ <file>images/[email protected]</file>
<file>images/iconoverlay_error.png</file>
<file>images/[email protected]</file>
<file>images/iconoverlay_error_background.png</file>
diff --git a/src/libs/utils/utiltypes.h b/src/libs/utils/utiltypes.h
new file mode 100644
index 00000000000..967eecb5a5f
--- /dev/null
+++ b/src/libs/utils/utiltypes.h
@@ -0,0 +1,14 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <functional>
+
+namespace Utils {
+class FilePath;
+
+enum class IterationPolicy { Stop, Continue };
+
+using FilePathPredicate = std::function<bool(const FilePath &)>;
+} // namespace Utils
diff --git a/src/plugins/CMakeLists.txt b/src/plugins/CMakeLists.txt
index 8c4f2e2bb68..02f13199140 100644
--- a/src/plugins/CMakeLists.txt
+++ b/src/plugins/CMakeLists.txt
@@ -27,6 +27,7 @@ add_subdirectory(help)
add_subdirectory(resourceeditor)
add_subdirectory(nim)
add_subdirectory(conan)
+add_subdirectory(vcpkg)
# Level 4: (only depends on Level 3 and below)
add_subdirectory(classview)
@@ -100,3 +101,5 @@ add_subdirectory(qnx)
add_subdirectory(webassembly)
add_subdirectory(mcusupport)
add_subdirectory(saferenderer)
+add_subdirectory(copilot)
+add_subdirectory(terminal)
diff --git a/src/plugins/android/android.qbs b/src/plugins/android/android.qbs
index 5c9b6564c7b..c6d1611636f 100644
--- a/src/plugins/android/android.qbs
+++ b/src/plugins/android/android.qbs
@@ -116,9 +116,7 @@ Project {
"sdkmanageroutputparser.h"
]
- Group {
- name: "Unit tests"
- condition: qtc.testsEnabled
+ QtcTestFiles {
files: [
"android_tst.qrc",
"androidsdkmanager_test.cpp",
diff --git a/src/plugins/android/androidavdmanager.cpp b/src/plugins/android/androidavdmanager.cpp
index 7cf62fadf86..6567c119a6e 100644
--- a/src/plugins/android/androidavdmanager.cpp
+++ b/src/plugins/android/androidavdmanager.cpp
@@ -10,25 +10,21 @@
#include <projectexplorer/projectexplorerconstants.h>
#include <utils/algorithm.h>
+#include <utils/asynctask.h>
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
-#include <utils/runextensions.h>
-#include <QApplication>
#include <QLoggingCategory>
#include <QMainWindow>
#include <QMessageBox>
-#include <QSettings>
#include <chrono>
-#include <functional>
using namespace Utils;
+using namespace std;
namespace Android::Internal {
-using namespace std;
-
const int avdCreateTimeoutMs = 30000;
static Q_LOGGING_CATEGORY(avdManagerLog, "qtc.android.avdManager", QtWarningMsg)
@@ -143,7 +139,7 @@ AndroidAvdManager::~AndroidAvdManager() = default;
QFuture<CreateAvdInfo> AndroidAvdManager::createAvd(CreateAvdInfo info) const
{
- return runAsync(&createAvdCommand, m_config, info);
+ return Utils::asyncRun(&createAvdCommand, m_config, info);
}
bool AndroidAvdManager::removeAvd(const QString &name) const
@@ -221,14 +217,14 @@ static AndroidDeviceInfoList listVirtualDevices(const AndroidConfig &config)
QFuture<AndroidDeviceInfoList> AndroidAvdManager::avdList() const
{
- return runAsync(listVirtualDevices, m_config);
+ return Utils::asyncRun(listVirtualDevices, m_config);
}
QString AndroidAvdManager::startAvd(const QString &name) const
{
if (!findAvd(name).isEmpty() || startAvdAsync(name))
return waitForAvd(name);
- return QString();
+ return {};
}
static bool is32BitUserSpace()
@@ -301,21 +297,21 @@ QString AndroidAvdManager::findAvd(const QString &avdName) const
if (device.avdName == avdName)
return device.serialNumber;
}
- return QString();
+ return {};
}
QString AndroidAvdManager::waitForAvd(const QString &avdName,
- const QFutureInterfaceBase &fi) const
+ const std::optional<QFuture<void>> &future) const
{
// we cannot use adb -e wait-for-device, since that doesn't work if a emulator is already running
// 60 rounds of 2s sleeping, two minutes for the avd to start
QString serialNumber;
for (int i = 0; i < 60; ++i) {
- if (fi.isCanceled())
+ if (future && future->isCanceled())
return {};
serialNumber = findAvd(avdName);
if (!serialNumber.isEmpty())
- return waitForBooted(serialNumber, fi) ? serialNumber : QString();
+ return waitForBooted(serialNumber, future) ? serialNumber : QString();
QThread::sleep(2);
}
return {};
@@ -339,11 +335,11 @@ bool AndroidAvdManager::isAvdBooted(const QString &device) const
}
bool AndroidAvdManager::waitForBooted(const QString &serialNumber,
- const QFutureInterfaceBase &fi) const
+ const std::optional<QFuture<void>> &future) const
{
// found a serial number, now wait until it's done booting...
for (int i = 0; i < 60; ++i) {
- if (fi.isCanceled())
+ if (future && future->isCanceled())
return false;
if (isAvdBooted(serialNumber))
return true;
diff --git a/src/plugins/android/androidavdmanager.h b/src/plugins/android/androidavdmanager.h
index cad3a2efe73..545dedbfe21 100644
--- a/src/plugins/android/androidavdmanager.h
+++ b/src/plugins/android/androidavdmanager.h
@@ -4,8 +4,9 @@
#include "androidconfigurations.h"
-#include <functional>
-#include <memory>
+#include <QFuture>
+
+#include <optional>
namespace Android::Internal {
@@ -22,14 +23,14 @@ public:
QString startAvd(const QString &name) const;
bool startAvdAsync(const QString &avdName) const;
QString findAvd(const QString &avdName) const;
- QString waitForAvd(const QString &avdName, const QFutureInterfaceBase &fi = {}) const;
+ QString waitForAvd(const QString &avdName, const std::optional<QFuture<void>> &future = {}) const;
bool isAvdBooted(const QString &device) const;
static bool avdManagerCommand(const AndroidConfig &config,
const QStringList &args,
QString *output);
private:
- bool waitForBooted(const QString &serialNumber, const QFutureInterfaceBase &fi = {}) const;
+ bool waitForBooted(const QString &serialNumber, const std::optional<QFuture<void>> &future = {}) const;
private:
const AndroidConfig &m_config;
diff --git a/src/plugins/android/androidbuildapkstep.cpp b/src/plugins/android/androidbuildapkstep.cpp
index adf96eb68d7..789e6c92fd7 100644
--- a/src/plugins/android/androidbuildapkstep.cpp
+++ b/src/plugins/android/androidbuildapkstep.cpp
@@ -863,7 +863,7 @@ void AndroidBuildApkStep::updateBuildToolsVersionInJsonFile()
if (!contents)
return;
- QRegularExpression regex(QLatin1String("\"sdkBuildToolsRevision\":.\"[0-9.]+\""));
+ static const QRegularExpression regex(R"("sdkBuildToolsRevision":."[0-9.]+")");
QRegularExpressionMatch match = regex.match(QString::fromUtf8(contents.value()));
const QString version = buildToolsVersion().toString();
if (match.hasMatch() && !version.isEmpty()) {
@@ -925,7 +925,8 @@ void AndroidBuildApkStep::setBuildToolsVersion(const QVersionNumber &version)
void AndroidBuildApkStep::stdError(const QString &output)
{
QString newOutput = output;
- newOutput.remove(QRegularExpression("^(\\n)+"));
+ static const QRegularExpression re("^(\\n)+");
+ newOutput.remove(re);
if (newOutput.isEmpty())
return;
diff --git a/src/plugins/android/androidconfigurations.cpp b/src/plugins/android/androidconfigurations.cpp
index 5477bf14bfe..4616cff2aed 100644
--- a/src/plugins/android/androidconfigurations.cpp
+++ b/src/plugins/android/androidconfigurations.cpp
@@ -19,7 +19,7 @@
#include <projectexplorer/kitmanager.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorerconstants.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/toolchainmanager.h>
#include <debugger/debuggeritemmanager.h>
@@ -306,7 +306,7 @@ void AndroidConfig::parseDependenciesJson()
auto fillQtVersionsRange = [](const QString &shortVersion) {
QList<QVersionNumber> versions;
- const QRegularExpression re(R"(([0-9]\.[0-9]+\.)\[([0-9]+)\-([0-9]+)\])");
+ static const QRegularExpression re(R"(([0-9]\.[0-9]+\.)\[([0-9]+)\-([0-9]+)\])");
QRegularExpressionMatch match = re.match(shortVersion);
if (match.hasMatch() && match.lastCapturedIndex() == 3)
for (int i = match.captured(2).toInt(); i <= match.captured(3).toInt(); ++i)
@@ -892,7 +892,7 @@ QVersionNumber AndroidConfig::ndkVersion(const FilePath &ndkPath)
// r6a
// r10e (64 bit)
QString content = QString::fromUtf8(reader.data());
- QRegularExpression re("(r)(?<major>[0-9]{1,2})(?<minor>[a-z]{1,1})");
+ static const QRegularExpression re("(r)(?<major>[0-9]{1,2})(?<minor>[a-z]{1,1})");
QRegularExpressionMatch match = re.match(content);
if (match.hasMatch()) {
QString major = match.captured("major");
diff --git a/src/plugins/android/androidcreatekeystorecertificate.cpp b/src/plugins/android/androidcreatekeystorecertificate.cpp
index 003eb7e9b98..e6537b8a27f 100644
--- a/src/plugins/android/androidcreatekeystorecertificate.cpp
+++ b/src/plugins/android/androidcreatekeystorecertificate.cpp
@@ -217,7 +217,8 @@ bool AndroidCreateKeystoreCertificate::checkCertificateAlias()
bool AndroidCreateKeystoreCertificate::checkCountryCode()
{
- if (!m_countryLineEdit->text().contains(QRegularExpression("[A-Z]{2}"))) {
+ static const QRegularExpression re("[A-Z]{2}");
+ if (!m_countryLineEdit->text().contains(re)) {
m_infoLabel->show();
m_infoLabel->setText(Tr::tr("Invalid country code."));
return false;
diff --git a/src/plugins/android/androiddeployqtstep.cpp b/src/plugins/android/androiddeployqtstep.cpp
index c305987a502..88092aef529 100644
--- a/src/plugins/android/androiddeployqtstep.cpp
+++ b/src/plugins/android/androiddeployqtstep.cpp
@@ -2,10 +2,11 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#include "androiddeployqtstep.h"
+
#include "androidavdmanager.h"
#include "androidbuildapkstep.h"
#include "androidconstants.h"
-#include "androiddeployqtstep.h"
#include "androiddevice.h"
#include "androidmanager.h"
#include "androidqtversion.h"
@@ -16,6 +17,7 @@
#include <coreplugin/icore.h>
#include <coreplugin/messagemanager.h>
+#include <projectexplorer/abstractprocessstep.h>
#include <projectexplorer/buildconfiguration.h>
#include <projectexplorer/buildsystem.h>
#include <projectexplorer/buildsteplist.h>
@@ -27,13 +29,17 @@
#include <projectexplorer/taskhub.h>
#include <projectexplorer/toolchain.h>
+#include <qtsupport/baseqtversion.h>
#include <qtsupport/qtkitinformation.h>
#include <utils/algorithm.h>
+#include <utils/asynctask.h>
+#include <utils/commandline.h>
+#include <utils/environment.h>
+#include <utils/futuresynchronizer.h>
#include <utils/layoutbuilder.h>
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
-#include <utils/runextensions.h>
#include <QCheckBox>
#include <QFileDialog>
@@ -59,7 +65,78 @@ const QLatin1String InstallFailedVersionDowngrade("INSTALL_FAILED_VERSION_DOWNGR
// AndroidDeployQtStep
-AndroidDeployQtStep::AndroidDeployQtStep(BuildStepList *parent, Utils::Id id)
+class AndroidDeployQtStep : public BuildStep
+{
+ Q_OBJECT
+
+ enum DeployErrorCode
+ {
+ NoError = 0,
+ InconsistentCertificates = 0x0001,
+ UpdateIncompatible = 0x0002,
+ PermissionModelDowngrade = 0x0004,
+ VersionDowngrade = 0x0008,
+ Failure = 0x0010
+ };
+
+public:
+ AndroidDeployQtStep(BuildStepList *bc, Id id);
+
+signals:
+ void askForUninstall(DeployErrorCode errorCode);
+
+private:
+ void runCommand(const CommandLine &command);
+
+ bool init() override;
+ void doRun() override;
+ void doCancel() override;
+ void gatherFilesToPull();
+ DeployErrorCode runDeploy();
+ void slotAskForUninstall(DeployErrorCode errorCode);
+
+ void runImpl(QPromise<bool> &promise);
+
+ QWidget *createConfigWidget() override;
+
+ void processReadyReadStdOutput(DeployErrorCode &errorCode);
+ void stdOutput(const QString &line);
+ void processReadyReadStdError(DeployErrorCode &errorCode);
+ void stdError(const QString &line);
+ DeployErrorCode parseDeployErrors(const QString &deployOutputLine) const;
+
+ friend void operator|=(DeployErrorCode &e1, const DeployErrorCode &e2) {
+ e1 = static_cast<AndroidDeployQtStep::DeployErrorCode>((int)e1 | (int)e2);
+ }
+
+ friend DeployErrorCode operator|(const DeployErrorCode &e1, const DeployErrorCode &e2) {
+ return static_cast<AndroidDeployQtStep::DeployErrorCode>((int)e1 | (int)e2);
+ }
+
+ void reportWarningOrError(const QString &message, Task::TaskType type);
+
+ FilePath m_manifestName;
+ QString m_serialNumber;
+ QString m_avdName;
+ FilePath m_apkPath;
+ QMap<QString, FilePath> m_filesToPull;
+
+ QStringList m_androidABIs;
+ BoolAspect *m_uninstallPreviousPackage = nullptr;
+ bool m_uninstallPreviousPackageRun = false;
+ bool m_useAndroiddeployqt = false;
+ bool m_askForUninstall = false;
+ CommandLine m_androiddeployqtArgs;
+ FilePath m_adbPath;
+ FilePath m_command;
+ FilePath m_workingDirectory;
+ Environment m_environment;
+ AndroidDeviceInfo m_deviceInfo;
+
+ FutureSynchronizer m_synchronizer;
+};
+
+AndroidDeployQtStep::AndroidDeployQtStep(BuildStepList *parent, Id id)
: BuildStep(parent, id)
{
setImmutable(true);
@@ -209,9 +286,9 @@ bool AndroidDeployQtStep::init()
reportWarningOrError(Tr::tr("The deployment step's project node is invalid."), Task::Error);
return false;
}
- m_apkPath = Utils::FilePath::fromString(node->data(Constants::AndroidApk).toString());
+ m_apkPath = FilePath::fromString(node->data(Constants::AndroidApk).toString());
if (!m_apkPath.isEmpty()) {
- m_manifestName = Utils::FilePath::fromString(node->data(Constants::AndroidManifest).toString());
+ m_manifestName = FilePath::fromString(node->data(Constants::AndroidManifest).toString());
m_command = AndroidConfigurations::currentConfig().adbToolPath();
AndroidManager::setManifestPath(target(), m_manifestName);
} else {
@@ -254,7 +331,7 @@ bool AndroidDeployQtStep::init()
m_apkPath = AndroidManager::packagePath(target());
m_workingDirectory = bc ? AndroidManager::buildDirectory(target()): FilePath();
}
- m_environment = bc ? bc->environment() : Utils::Environment();
+ m_environment = bc ? bc->environment() : Environment();
m_adbPath = AndroidConfigurations::currentConfig().adbToolPath();
@@ -401,15 +478,16 @@ void AndroidDeployQtStep::slotAskForUninstall(DeployErrorCode errorCode)
m_askForUninstall = button == QMessageBox::Yes;
}
-void AndroidDeployQtStep::runImpl(QFutureInterface<bool> &fi)
+void AndroidDeployQtStep::runImpl(QPromise<bool> &promise)
{
if (!m_avdName.isEmpty()) {
- QString serialNumber = AndroidAvdManager().waitForAvd(m_avdName, fi);
+ const QString serialNumber = AndroidAvdManager().waitForAvd(m_avdName,
+ QFuture<void>(promise.future()));
qCDebug(deployStepLog) << "Deploying to AVD:" << m_avdName << serialNumber;
if (serialNumber.isEmpty()) {
reportWarningOrError(Tr::tr("The deployment AVD \"%1\" cannot be started.")
.arg(m_avdName), Task::Error);
- fi.reportResult(false);
+ promise.addResult(false);
return;
}
m_serialNumber = serialNumber;
@@ -445,7 +523,7 @@ void AndroidDeployQtStep::runImpl(QFutureInterface<bool> &fi)
reportWarningOrError(error, Task::Error);
}
}
- fi.reportResult(returnValue == NoError);
+ promise.addResult(returnValue == NoError);
}
void AndroidDeployQtStep::gatherFilesToPull()
@@ -485,7 +563,7 @@ void AndroidDeployQtStep::doRun()
emit finished(success);
watcher->deleteLater();
});
- auto future = Utils::runAsync(&AndroidDeployQtStep::runImpl, this);
+ auto future = Utils::asyncRun(&AndroidDeployQtStep::runImpl, this);
watcher->setFuture(future);
m_synchronizer.addFuture(future);
}
@@ -542,7 +620,8 @@ void AndroidDeployQtStep::stdError(const QString &line)
emit addOutput(line, BuildStep::OutputFormat::Stderr, BuildStep::DontAppendNewline);
QString newOutput = line;
- newOutput.remove(QRegularExpression("^(\\n)+"));
+ static const QRegularExpression re("^(\\n)+");
+ newOutput.remove(re);
if (newOutput.isEmpty())
return;
@@ -592,3 +671,5 @@ AndroidDeployQtStepFactory::AndroidDeployQtStepFactory()
}
} // Android::Internal
+
+#include "androiddeployqtstep.moc"
diff --git a/src/plugins/android/androiddeployqtstep.h b/src/plugins/android/androiddeployqtstep.h
index 334be8ea22a..dda4304b6af 100644
--- a/src/plugins/android/androiddeployqtstep.h
+++ b/src/plugins/android/androiddeployqtstep.h
@@ -4,16 +4,7 @@
#pragma once
-#include "androiddeviceinfo.h"
-
-#include <projectexplorer/abstractprocessstep.h>
-#include <qtsupport/baseqtversion.h>
-
-#include <utils/commandline.h>
-#include <utils/environment.h>
-#include <utils/futuresynchronizer.h>
-
-namespace Utils { class QtcProcess; }
+#include <projectexplorer/buildstep.h>
namespace Android::Internal {
@@ -23,76 +14,4 @@ public:
AndroidDeployQtStepFactory();
};
-class AndroidDeployQtStep : public ProjectExplorer::BuildStep
-{
- Q_OBJECT
-
- enum DeployErrorCode
- {
- NoError = 0,
- InconsistentCertificates = 0x0001,
- UpdateIncompatible = 0x0002,
- PermissionModelDowngrade = 0x0004,
- VersionDowngrade = 0x0008,
- Failure = 0x0010
- };
-
-public:
- AndroidDeployQtStep(ProjectExplorer::BuildStepList *bc, Utils::Id id);
-
-signals:
- void askForUninstall(DeployErrorCode errorCode);
-
-private:
- void runCommand(const Utils::CommandLine &command);
-
- bool init() override;
- void doRun() override;
- void doCancel() override;
- void gatherFilesToPull();
- DeployErrorCode runDeploy();
- void slotAskForUninstall(DeployErrorCode errorCode);
-
- void runImpl(QFutureInterface<bool> &fi);
-
- QWidget *createConfigWidget() override;
-
- void processReadyReadStdOutput(DeployErrorCode &errorCode);
- void stdOutput(const QString &line);
- void processReadyReadStdError(DeployErrorCode &errorCode);
- void stdError(const QString &line);
- DeployErrorCode parseDeployErrors(const QString &deployOutputLine) const;
-
- friend void operator|=(DeployErrorCode &e1, const DeployErrorCode &e2) {
- e1 = static_cast<AndroidDeployQtStep::DeployErrorCode>((int)e1 | (int)e2);
- }
-
- friend DeployErrorCode operator|(const DeployErrorCode &e1, const DeployErrorCode &e2) {
- return static_cast<AndroidDeployQtStep::DeployErrorCode>((int)e1 | (int)e2);
- }
-
- void reportWarningOrError(const QString &message, ProjectExplorer::Task::TaskType type);
-
- Utils::FilePath m_manifestName;
- QString m_serialNumber;
- QString m_avdName;
- Utils::FilePath m_apkPath;
- QMap<QString, Utils::FilePath> m_filesToPull;
-
- QStringList m_androidABIs;
- Utils::BoolAspect *m_uninstallPreviousPackage = nullptr;
- bool m_uninstallPreviousPackageRun = false;
- bool m_useAndroiddeployqt = false;
- bool m_askForUninstall = false;
- static const Utils::Id Id;
- Utils::CommandLine m_androiddeployqtArgs;
- Utils::FilePath m_adbPath;
- Utils::FilePath m_command;
- Utils::FilePath m_workingDirectory;
- Utils::Environment m_environment;
- AndroidDeviceInfo m_deviceInfo;
-
- Utils::FutureSynchronizer m_synchronizer;
-};
-
} // Android::Internal
diff --git a/src/plugins/android/androiddevice.cpp b/src/plugins/android/androiddevice.cpp
index 4c034c30af1..6346c47d7eb 100644
--- a/src/plugins/android/androiddevice.cpp
+++ b/src/plugins/android/androiddevice.cpp
@@ -18,12 +18,12 @@
#include <projectexplorer/kitinformation.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/runconfiguration.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
+#include <utils/asynctask.h>
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
-#include <utils/runextensions.h>
#include <utils/url.h>
#include <QEventLoop>
@@ -454,7 +454,7 @@ void AndroidDeviceManager::startAvd(const ProjectExplorer::IDevice::Ptr &device,
const AndroidDevice *androidDev = static_cast<const AndroidDevice *>(device.data());
const QString name = androidDev->avdName();
qCDebug(androidDeviceLog, "Starting Android AVD id \"%s\".", qPrintable(name));
- runAsync([this, name, device] {
+ auto future = Utils::asyncRun([this, name, device] {
const QString serialNumber = m_avdManager.startAvd(name);
// Mark the AVD as ReadyToUse once we know it's started
if (!serialNumber.isEmpty()) {
@@ -462,6 +462,7 @@ void AndroidDeviceManager::startAvd(const ProjectExplorer::IDevice::Ptr &device,
devMgr->setDeviceState(device->id(), IDevice::DeviceReadyToUse);
}
});
+ // TODO: use future!
}
void AndroidDeviceManager::eraseAvd(const IDevice::Ptr &device, QWidget *parent)
@@ -479,7 +480,7 @@ void AndroidDeviceManager::eraseAvd(const IDevice::Ptr &device, QWidget *parent)
return;
qCDebug(androidDeviceLog) << QString("Erasing Android AVD \"%1\" from the system.").arg(name);
- m_removeAvdFutureWatcher.setFuture(runAsync([this, name, device] {
+ m_removeAvdFutureWatcher.setFuture(Utils::asyncRun([this, name, device] {
QPair<IDevice::ConstPtr, bool> pair;
pair.first = device;
pair.second = false;
@@ -847,7 +848,6 @@ AndroidDeviceFactory::AndroidDeviceFactory()
setDisplayName(Tr::tr("Android Device"));
setCombinedIcon(":/android/images/androiddevicesmall.png",
":/android/images/androiddevice.png");
-
setConstructionFunction(&AndroidDevice::create);
if (m_androidConfig.sdkToolsOk()) {
setCreator([this] {
diff --git a/src/plugins/android/androidmanager.cpp b/src/plugins/android/androidmanager.cpp
index a15da300483..fa299ba4e21 100644
--- a/src/plugins/android/androidmanager.cpp
+++ b/src/plugins/android/androidmanager.cpp
@@ -24,7 +24,7 @@
#include <projectexplorer/projectnodes.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectexplorerconstants.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <projectexplorer/buildsystem.h>
@@ -519,9 +519,9 @@ QString AndroidManager::androidNameForApiLevel(int x)
case 31:
return QLatin1String("Android 12.0 (S)");
case 32:
- return QLatin1String("Android 12L (API 32)");
+ return QLatin1String("Android 12L (Sv2, API 32)");
case 33:
- return QLatin1String("Android Tiramisu");
+ return QLatin1String("Android 13.0 (Tiramisu)");
default:
return Tr::tr("Unknown Android version. API Level: %1").arg(x);
}
diff --git a/src/plugins/android/androidmanifesteditorwidget.cpp b/src/plugins/android/androidmanifesteditorwidget.cpp
index 009a0219430..9c6a616abe3 100644
--- a/src/plugins/android/androidmanifesteditorwidget.cpp
+++ b/src/plugins/android/androidmanifesteditorwidget.cpp
@@ -19,8 +19,8 @@
#include <projectexplorer/buildconfiguration.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectnodes.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/projectwindow.h>
-#include <projectexplorer/session.h>
#include <projectexplorer/target.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/kitinformation.h>
@@ -73,7 +73,7 @@ static bool checkPackageName(const QString &packageName)
static Target *androidTarget(const FilePath &fileName)
{
- for (Project *project : SessionManager::projects()) {
+ for (Project *project : ProjectManager::projects()) {
if (Target *target = project->activeTarget()) {
Kit *kit = target->kit();
if (DeviceTypeKitAspect::deviceTypeId(kit) == Android::Constants::ANDROID_DEVICE_TYPE
diff --git a/src/plugins/android/androidplugin.cpp b/src/plugins/android/androidplugin.cpp
index b8ff5dbe0b8..04e9609b85e 100644
--- a/src/plugins/android/androidplugin.cpp
+++ b/src/plugins/android/androidplugin.cpp
@@ -45,11 +45,13 @@
#include <projectexplorer/kitinformation.h>
#include <projectexplorer/kitmanager.h>
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <qtsupport/qtversionmanager.h>
+#include <QTimer>
+
using namespace ProjectExplorer;
using namespace ProjectExplorer::Constants;
diff --git a/src/plugins/android/androidqmlpreviewworker.cpp b/src/plugins/android/androidqmlpreviewworker.cpp
index ab9fb34de5a..f18306f9f8a 100644
--- a/src/plugins/android/androidqmlpreviewworker.cpp
+++ b/src/plugins/android/androidqmlpreviewworker.cpp
@@ -27,8 +27,8 @@
#include <qtsupport/baseqtversion.h>
#include <qtsupport/qtkitinformation.h>
+#include <utils/asynctask.h>
#include <utils/qtcprocess.h>
-#include <utils/runextensions.h>
#include <QDateTime>
#include <QDeadlineTimer>
@@ -163,7 +163,7 @@ bool AndroidQmlPreviewWorker::isPreviewRunning(int lastKnownPid) const
void AndroidQmlPreviewWorker::startPidWatcher()
{
- m_pidFutureWatcher.setFuture(runAsync([this] {
+ m_pidFutureWatcher.setFuture(Utils::asyncRun([this] {
// wait for started
const int sleepTimeMs = 2000;
QDeadlineTimer deadline(20000);
diff --git a/src/plugins/android/androidruncontrol.h b/src/plugins/android/androidruncontrol.h
index a466de72269..f96c19443b0 100644
--- a/src/plugins/android/androidruncontrol.h
+++ b/src/plugins/android/androidruncontrol.h
@@ -3,14 +3,10 @@
#pragma once
-#include "androidrunner.h"
-
-#include <projectexplorer/runconfiguration.h>
+#include <projectexplorer/runcontrol.h>
namespace Android::Internal {
-class AndroidRunner;
-
class AndroidRunWorkerFactory final : public ProjectExplorer::RunWorkerFactory
{
public:
diff --git a/src/plugins/android/androidrunnerworker.cpp b/src/plugins/android/androidrunnerworker.cpp
index e702d487b01..14dc9112e84 100644
--- a/src/plugins/android/androidrunnerworker.cpp
+++ b/src/plugins/android/androidrunnerworker.cpp
@@ -11,8 +11,8 @@
#include <debugger/debuggerrunconfigurationaspect.h>
#include <projectexplorer/buildconfiguration.h>
-#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/environmentaspect.h>
+#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/runconfigurationaspects.h>
#include <projectexplorer/runcontrol.h>
#include <projectexplorer/target.h>
@@ -20,6 +20,7 @@
#include <qtsupport/baseqtversion.h>
#include <qtsupport/qtkitinformation.h>
+#include <utils/asynctask.h>
#include <utils/fileutils.h>
#include <utils/hostosinfo.h>
#include <utils/qtcprocess.h>
@@ -77,7 +78,7 @@ static qint64 extractPID(const QString &output, const QString &packageName)
return pid;
}
-static void findProcessPID(QFutureInterface<qint64> &fi, QStringList selector,
+static void findProcessPID(QPromise<qint64> &promise, QStringList selector,
const QString &packageName, bool preNougat)
{
if (packageName.isEmpty())
@@ -105,11 +106,11 @@ static void findProcessPID(QFutureInterface<qint64> &fi, QStringList selector,
if (!out.isEmpty())
processPID = out.trimmed().toLongLong();
}
- } while ((processPID == -1 || processPID == 0) && !isTimedOut(start) && !fi.isCanceled());
+ } while ((processPID == -1 || processPID == 0) && !isTimedOut(start) && !promise.isCanceled());
qCDebug(androidRunWorkerLog) << "PID found:" << processPID << ", PreNougat:" << preNougat;
- if (!fi.isCanceled())
- fi.reportResult(processPID);
+ if (!promise.isCanceled())
+ promise.addResult(processPID);
}
static void deleter(QProcess *p)
@@ -683,7 +684,7 @@ void AndroidRunnerWorker::asyncStart()
{
asyncStartHelper();
- m_pidFinder = Utils::onResultReady(Utils::runAsync(findProcessPID, selector(),
+ m_pidFinder = Utils::onResultReady(Utils::asyncRun(findProcessPID, selector(),
m_packageName, m_isPreNougat),
bind(&AndroidRunnerWorker::onProcessIdChanged, this, _1));
}
diff --git a/src/plugins/android/androidsdkmanager.cpp b/src/plugins/android/androidsdkmanager.cpp
index 4494f4837fa..af5fde48bea 100644
--- a/src/plugins/android/androidsdkmanager.cpp
+++ b/src/plugins/android/androidsdkmanager.cpp
@@ -8,9 +8,9 @@
#include "sdkmanageroutputparser.h"
#include <utils/algorithm.h>
+#include <utils/asynctask.h>
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
-#include <utils/runextensions.h>
#include <utils/stringutils.h>
#include <QFutureWatcher>
@@ -34,7 +34,7 @@ namespace Internal {
const int sdkManagerCmdTimeoutS = 60;
const int sdkManagerOperationTimeoutS = 600;
-using SdkCmdFutureInterface = QFutureInterface<AndroidSdkManager::OperationOutput>;
+using SdkCmdPromise = QPromise<AndroidSdkManager::OperationOutput>;
static const QRegularExpression &assertionRegExp()
{
@@ -111,7 +111,7 @@ static bool sdkManagerCommand(const AndroidConfig &config, const QStringList &ar
after the lapse of \a timeout seconds. The function blocks the calling thread.
*/
static void sdkManagerCommand(const AndroidConfig &config, const QStringList &args,
- AndroidSdkManager &sdkManager, SdkCmdFutureInterface &fi,
+ AndroidSdkManager &sdkManager, SdkCmdPromise &promise,
AndroidSdkManager::OperationOutput &output, double progressQuota,
bool interruptible = true, int timeout = sdkManagerOperationTimeoutS)
{
@@ -120,19 +120,19 @@ static void sdkManagerCommand(const AndroidConfig &config, const QStringList &ar
qCDebug(sdkManagerLog).noquote() << "Running SDK Manager command (async):"
<< CommandLine(config.sdkManagerToolPath(), newArgs)
.toUserOutput();
- int offset = fi.progressValue();
+ int offset = promise.future().progressValue();
QtcProcess proc;
proc.setEnvironment(AndroidConfigurations::toolsEnvironment(config));
bool assertionFound = false;
proc.setTimeoutS(timeout);
- proc.setStdOutCallback([offset, progressQuota, &proc, &assertionFound, &fi](const QString &out) {
+ proc.setStdOutCallback([offset, progressQuota, &proc, &assertionFound, &promise](const QString &out) {
int progressPercent = parseProgress(out, assertionFound);
if (assertionFound) {
proc.stop();
proc.waitForFinished();
}
if (progressPercent != -1)
- fi.setProgressValue(offset + qRound((progressPercent / 100.0) * progressQuota));
+ promise.setProgressValue(offset + qRound((progressPercent / 100.0) * progressQuota));
});
proc.setStdErrCallback([&output](const QString &err) {
output.stdError = err;
@@ -168,12 +168,12 @@ public:
const AndroidSdkPackageList &allPackages(bool forceUpdate = false);
void refreshSdkPackages(bool forceReload = false);
- void parseCommonArguments(QFutureInterface<QString> &fi);
- void updateInstalled(SdkCmdFutureInterface &fi);
- void update(SdkCmdFutureInterface &fi, const QStringList &install,
+ void parseCommonArguments(QPromise<QString> &promise);
+ void updateInstalled(SdkCmdPromise &fi);
+ void update(SdkCmdPromise &fi, const QStringList &install,
const QStringList &uninstall);
- void checkPendingLicense(SdkCmdFutureInterface &fi);
- void getPendingLicense(SdkCmdFutureInterface &fi);
+ void checkPendingLicense(SdkCmdPromise &fi);
+ void getPendingLicense(SdkCmdPromise &fi);
void addWatcher(const QFuture<AndroidSdkManager::OperationOutput> &future);
void setLicenseInput(bool acceptLicense);
@@ -186,7 +186,7 @@ private:
void reloadSdkPackages();
void clearPackages();
bool onLicenseStdOut(const QString &output, bool notify,
- AndroidSdkManager::OperationOutput &result, SdkCmdFutureInterface &fi);
+ AndroidSdkManager::OperationOutput &result, SdkCmdPromise &fi);
AndroidSdkManager &m_sdkManager;
const AndroidConfig &m_config;
@@ -308,7 +308,7 @@ bool AndroidSdkManager::packageListingSuccessful() const
QFuture<QString> AndroidSdkManager::availableArguments() const
{
- return Utils::runAsync(&AndroidSdkManagerPrivate::parseCommonArguments, m_d.get());
+ return Utils::asyncRun(&AndroidSdkManagerPrivate::parseCommonArguments, m_d.get());
}
QFuture<AndroidSdkManager::OperationOutput> AndroidSdkManager::updateAll()
@@ -316,7 +316,7 @@ QFuture<AndroidSdkManager::OperationOutput> AndroidSdkManager::updateAll()
if (isBusy()) {
return QFuture<AndroidSdkManager::OperationOutput>();
}
- auto future = Utils::runAsync(&AndroidSdkManagerPrivate::updateInstalled, m_d.get());
+ auto future = Utils::asyncRun(&AndroidSdkManagerPrivate::updateInstalled, m_d.get());
m_d->addWatcher(future);
return future;
}
@@ -326,7 +326,7 @@ AndroidSdkManager::update(const QStringList &install, const QStringList &uninsta
{
if (isBusy())
return QFuture<AndroidSdkManager::OperationOutput>();
- auto future = Utils::runAsync(&AndroidSdkManagerPrivate::update, m_d.get(), install, uninstall);
+ auto future = Utils::asyncRun(&AndroidSdkManagerPrivate::update, m_d.get(), install, uninstall);
m_d->addWatcher(future);
return future;
}
@@ -335,7 +335,7 @@ QFuture<AndroidSdkManager::OperationOutput> AndroidSdkManager::checkPendingLicen
{
if (isBusy())
return QFuture<AndroidSdkManager::OperationOutput>();
- auto future = Utils::runAsync(&AndroidSdkManagerPrivate::checkPendingLicense, m_d.get());
+ auto future = Utils::asyncRun(&AndroidSdkManagerPrivate::checkPendingLicense, m_d.get());
m_d->addWatcher(future);
return future;
}
@@ -344,7 +344,7 @@ QFuture<AndroidSdkManager::OperationOutput> AndroidSdkManager::runLicenseCommand
{
if (isBusy())
return QFuture<AndroidSdkManager::OperationOutput>();
- auto future = Utils::runAsync(&AndroidSdkManagerPrivate::getPendingLicense, m_d.get());
+ auto future = Utils::asyncRun(&AndroidSdkManagerPrivate::getPendingLicense, m_d.get());
m_d->addWatcher(future);
return future;
}
@@ -422,29 +422,29 @@ void AndroidSdkManagerPrivate::refreshSdkPackages(bool forceReload)
reloadSdkPackages();
}
-void AndroidSdkManagerPrivate::updateInstalled(SdkCmdFutureInterface &fi)
+void AndroidSdkManagerPrivate::updateInstalled(SdkCmdPromise &promise)
{
- fi.setProgressRange(0, 100);
- fi.setProgressValue(0);
+ promise.setProgressRange(0, 100);
+ promise.setProgressValue(0);
AndroidSdkManager::OperationOutput result;
result.type = AndroidSdkManager::UpdateAll;
result.stdOutput = Tr::tr("Updating installed packages.");
- fi.reportResult(result);
+ promise.addResult(result);
QStringList args("--update");
args << m_config.sdkManagerToolArgs();
- if (!fi.isCanceled())
- sdkManagerCommand(m_config, args, m_sdkManager, fi, result, 100);
+ if (!promise.isCanceled())
+ sdkManagerCommand(m_config, args, m_sdkManager, promise, result, 100);
else
qCDebug(sdkManagerLog) << "Update: Operation cancelled before start";
if (result.stdError.isEmpty() && !result.success)
result.stdError = Tr::tr("Failed.");
result.stdOutput = Tr::tr("Done\n\n");
- fi.reportResult(result);
- fi.setProgressValue(100);
+ promise.addResult(result);
+ promise.setProgressValue(100);
}
-void AndroidSdkManagerPrivate::update(SdkCmdFutureInterface &fi, const QStringList &install,
+void AndroidSdkManagerPrivate::update(SdkCmdPromise &fi, const QStringList &install,
const QStringList &uninstall)
{
fi.setProgressRange(0, 100);
@@ -461,7 +461,7 @@ void AndroidSdkManagerPrivate::update(SdkCmdFutureInterface &fi, const QStringLi
result.type = AndroidSdkManager::UpdatePackage;
result.stdOutput = QString("%1 %2").arg(isInstall ? installTag : uninstallTag)
.arg(packagePath);
- fi.reportResult(result);
+ fi.addResult(result);
if (fi.isCanceled())
qCDebug(sdkManagerLog) << args << "Update: Operation cancelled before start";
else
@@ -471,7 +471,7 @@ void AndroidSdkManagerPrivate::update(SdkCmdFutureInterface &fi, const QStringLi
if (result.stdError.isEmpty() && !result.success)
result.stdError = Tr::tr("AndroidSdkManager", "Failed");
result.stdOutput = Tr::tr("AndroidSdkManager", "Done\n\n");
- fi.reportResult(result);
+ fi.addResult(result);
return fi.isCanceled();
};
@@ -495,7 +495,7 @@ void AndroidSdkManagerPrivate::update(SdkCmdFutureInterface &fi, const QStringLi
fi.setProgressValue(100);
}
-void AndroidSdkManagerPrivate::checkPendingLicense(SdkCmdFutureInterface &fi)
+void AndroidSdkManagerPrivate::checkPendingLicense(SdkCmdPromise &fi)
{
fi.setProgressRange(0, 100);
fi.setProgressValue(0);
@@ -509,11 +509,11 @@ void AndroidSdkManagerPrivate::checkPendingLicense(SdkCmdFutureInterface &fi)
qCDebug(sdkManagerLog) << "Update: Operation cancelled before start";
}
- fi.reportResult(result);
+ fi.addResult(result);
fi.setProgressValue(100);
}
-void AndroidSdkManagerPrivate::getPendingLicense(SdkCmdFutureInterface &fi)
+void AndroidSdkManagerPrivate::getPendingLicense(SdkCmdPromise &fi)
{
fi.setProgressRange(0, 100);
fi.setProgressValue(0);
@@ -549,7 +549,7 @@ void AndroidSdkManagerPrivate::getPendingLicense(SdkCmdFutureInterface &fi)
} else if (assertionFound) {
// The first assertion is to start reviewing licenses. Always accept.
reviewingLicenses = true;
- QRegularExpression reg("(\\d+\\sof\\s)(?<steps>\\d+)");
+ static const QRegularExpression reg(R"((\d+\sof\s)(?<steps>\d+))");
QRegularExpressionMatch match = reg.match(stdOut);
if (match.hasMatch())
steps = match.captured("steps").toInt();
@@ -571,7 +571,7 @@ void AndroidSdkManagerPrivate::getPendingLicense(SdkCmdFutureInterface &fi)
result.success = licenseCommand.exitStatus() == QProcess::NormalExit;
if (!result.success)
result.stdError = Tr::tr("License command failed.\n\n");
- fi.reportResult(result);
+ fi.addResult(result);
fi.setProgressValue(100);
}
@@ -595,14 +595,14 @@ void AndroidSdkManagerPrivate::clearUserInput()
bool AndroidSdkManagerPrivate::onLicenseStdOut(const QString &output, bool notify,
AndroidSdkManager::OperationOutput &result,
- SdkCmdFutureInterface &fi)
+ SdkCmdPromise &fi)
{
m_licenseTextCache.append(output);
const QRegularExpressionMatch assertionMatch = assertionRegExp().match(m_licenseTextCache);
if (assertionMatch.hasMatch()) {
if (notify) {
result.stdOutput = m_licenseTextCache;
- fi.reportResult(result);
+ fi.addResult(result);
}
// Clear the current contents. The found license text is dispatched. Continue collecting the
// next license text.
@@ -620,7 +620,7 @@ void AndroidSdkManagerPrivate::addWatcher(const QFuture<AndroidSdkManager::Opera
m_activeOperation->setFuture(QFuture<void>(future));
}
-void AndroidSdkManagerPrivate::parseCommonArguments(QFutureInterface<QString> &fi)
+void AndroidSdkManagerPrivate::parseCommonArguments(QPromise<QString> &promise)
{
QString argumentDetails;
QString output;
@@ -628,7 +628,7 @@ void AndroidSdkManagerPrivate::parseCommonArguments(QFutureInterface<QString> &f
bool foundTag = false;
const auto lines = output.split('\n');
for (const QString& line : lines) {
- if (fi.isCanceled())
+ if (promise.isCanceled())
break;
if (foundTag)
argumentDetails.append(line + "\n");
@@ -636,8 +636,8 @@ void AndroidSdkManagerPrivate::parseCommonArguments(QFutureInterface<QString> &f
foundTag = true;
}
- if (!fi.isCanceled())
- fi.reportResult(argumentDetails);
+ if (!promise.isCanceled())
+ promise.addResult(argumentDetails);
}
void AndroidSdkManagerPrivate::clearPackages()
diff --git a/src/plugins/autotest/autotest.qbs b/src/plugins/autotest/autotest.qbs
index f0bccd79c33..90f1728c4ae 100644
--- a/src/plugins/autotest/autotest.qbs
+++ b/src/plugins/autotest/autotest.qbs
@@ -125,9 +125,7 @@ QtcPlugin {
]
}
- Group {
- name: "Test sources"
- condition: qtc.testsEnabled
+ QtcTestFiles {
files: [
"autotestunittests.cpp",
"autotestunittests.h",
diff --git a/src/plugins/autotest/autotestplugin.cpp b/src/plugins/autotest/autotestplugin.cpp
index 98b804b16d3..28475f3870b 100644
--- a/src/plugins/autotest/autotestplugin.cpp
+++ b/src/plugins/autotest/autotestplugin.cpp
@@ -32,22 +32,28 @@
#include <coreplugin/icontext.h>
#include <coreplugin/icore.h>
#include <coreplugin/messagemanager.h>
+
#include <cplusplus/CppDocument.h>
#include <cplusplus/LookupContext.h>
#include <cplusplus/Overview.h>
+
#include <cppeditor/cppeditorconstants.h>
#include <cppeditor/cppmodelmanager.h>
+
#include <extensionsystem/pluginmanager.h>
+
#include <projectexplorer/buildmanager.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectexplorericons.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/projectpanelfactory.h>
#include <projectexplorer/runcontrol.h>
-#include <projectexplorer/session.h>
#include <projectexplorer/target.h>
+
#include <texteditor/textdocument.h>
#include <texteditor/texteditor.h>
+
#include <utils/algorithm.h>
#include <utils/textutils.h>
#include <utils/utilsicons.h>
@@ -148,11 +154,11 @@ AutotestPluginPrivate::AutotestPluginPrivate()
m_testTreeModel.synchronizeTestFrameworks();
m_testTreeModel.synchronizeTestTools();
- auto sessionManager = ProjectExplorer::SessionManager::instance();
- connect(sessionManager, &ProjectExplorer::SessionManager::startupProjectChanged,
+ auto sessionManager = ProjectExplorer::ProjectManager::instance();
+ connect(sessionManager, &ProjectExplorer::ProjectManager::startupProjectChanged,
this, [this] { m_runconfigCache.clear(); });
- connect(sessionManager, &ProjectExplorer::SessionManager::aboutToRemoveProject,
+ connect(sessionManager, &ProjectExplorer::ProjectManager::aboutToRemoveProject,
this, [](ProjectExplorer::Project *project) {
const auto it = s_projectSettings.constFind(project);
if (it != s_projectSettings.constEnd()) {
@@ -471,7 +477,7 @@ void AutotestPluginPrivate::onRunUnderCursorTriggered(TestRunMode mode)
TestFrameworks AutotestPlugin::activeTestFrameworks()
{
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
TestFrameworks sorted;
if (!project || projectSettings(project)->useGlobalSettings()) {
sorted = Utils::filtered(TestFrameworkManager::registeredFrameworks(),
@@ -489,7 +495,7 @@ TestFrameworks AutotestPlugin::activeTestFrameworks()
void AutotestPlugin::updateMenuItemsEnabledState()
{
- const ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ const ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
const ProjectExplorer::Target *target = project ? project->activeTarget() : nullptr;
const bool canScan = !dd->m_testRunner.isTestRunning()
&& dd->m_testCodeParser.state() == TestCodeParser::Idle;
diff --git a/src/plugins/autotest/autotestunittests.cpp b/src/plugins/autotest/autotestunittests.cpp
index e72cc0cbbb7..50d0b496c21 100644
--- a/src/plugins/autotest/autotestunittests.cpp
+++ b/src/plugins/autotest/autotestunittests.cpp
@@ -99,8 +99,8 @@ void AutoTestUnitTests::testCodeParser()
CppEditor::Tests::ProjectOpenerAndCloser projectManager;
QVERIFY(projectManager.open(projectFilePath, true, m_kit));
- QSignalSpy parserSpy(m_model->parser(), SIGNAL(parsingFinished()));
- QSignalSpy modelUpdateSpy(m_model, SIGNAL(sweepingDone()));
+ QSignalSpy parserSpy(m_model->parser(), &TestCodeParser::parsingFinished);
+ QSignalSpy modelUpdateSpy(m_model, &TestTreeModel::sweepingDone);
QVERIFY(parserSpy.wait(20000));
QVERIFY(modelUpdateSpy.wait());
@@ -149,8 +149,8 @@ void AutoTestUnitTests::testCodeParserSwitchStartup()
qDebug() << "Opening project" << projectFilePaths.at(i);
QVERIFY(projectManager.open(projectFilePaths.at(i), true, m_kit));
- QSignalSpy parserSpy(m_model->parser(), SIGNAL(parsingFinished()));
- QSignalSpy modelUpdateSpy(m_model, SIGNAL(sweepingDone()));
+ QSignalSpy parserSpy(m_model->parser(), &TestCodeParser::parsingFinished);
+ QSignalSpy modelUpdateSpy(m_model, &TestTreeModel::sweepingDone);
QVERIFY(parserSpy.wait(20000));
QVERIFY(modelUpdateSpy.wait());
@@ -199,8 +199,8 @@ void AutoTestUnitTests::testCodeParserGTest()
CppEditor::Tests::ProjectOpenerAndCloser projectManager;
QVERIFY(projectManager.open(projectFilePath, true, m_kit));
- QSignalSpy parserSpy(m_model->parser(), SIGNAL(parsingFinished()));
- QSignalSpy modelUpdateSpy(m_model, SIGNAL(sweepingDone()));
+ QSignalSpy parserSpy(m_model->parser(), &TestCodeParser::parsingFinished);
+ QSignalSpy modelUpdateSpy(m_model, &TestTreeModel::sweepingDone);
QVERIFY(parserSpy.wait(20000));
QVERIFY(modelUpdateSpy.wait());
@@ -250,8 +250,8 @@ void AutoTestUnitTests::testCodeParserBoostTest()
= projectManager.open(projectFilePath, true, m_kit);
QVERIFY(projectInfo);
- QSignalSpy parserSpy(m_model->parser(), SIGNAL(parsingFinished()));
- QSignalSpy modelUpdateSpy(m_model, SIGNAL(sweepingDone()));
+ QSignalSpy parserSpy(m_model->parser(), &TestCodeParser::parsingFinished);
+ QSignalSpy modelUpdateSpy(m_model, &TestTreeModel::sweepingDone);
QVERIFY(parserSpy.wait(20000));
QVERIFY(modelUpdateSpy.wait());
diff --git a/src/plugins/autotest/boost/boosttestconfiguration.cpp b/src/plugins/autotest/boost/boosttestconfiguration.cpp
index 50d8a863f4a..d680699c36e 100644
--- a/src/plugins/autotest/boost/boosttestconfiguration.cpp
+++ b/src/plugins/autotest/boost/boosttestconfiguration.cpp
@@ -11,18 +11,16 @@
#include "../testsettings.h"
#include <utils/algorithm.h>
-#include <utils/stringutils.h>
using namespace Utils;
namespace Autotest {
namespace Internal {
-TestOutputReader *BoostTestConfiguration::createOutputReader(
- const QFutureInterface<TestResult> &fi, QtcProcess *app) const
+TestOutputReader *BoostTestConfiguration::createOutputReader(QtcProcess *app) const
{
auto settings = static_cast<BoostTestSettings *>(framework()->testSettings());
- return new BoostTestOutputReader(fi, app, buildDirectory(), projectFile(),
+ return new BoostTestOutputReader(app, buildDirectory(), projectFile(),
LogLevel(settings->logLevel.value()),
ReportLevel(settings->reportLevel.value()));
}
diff --git a/src/plugins/autotest/boost/boosttestconfiguration.h b/src/plugins/autotest/boost/boosttestconfiguration.h
index 8afb74049fd..5a764dd8662 100644
--- a/src/plugins/autotest/boost/boosttestconfiguration.h
+++ b/src/plugins/autotest/boost/boosttestconfiguration.h
@@ -13,8 +13,7 @@ class BoostTestConfiguration : public DebuggableTestConfiguration
public:
explicit BoostTestConfiguration(ITestFramework *framework)
: DebuggableTestConfiguration(framework) {}
- TestOutputReader *createOutputReader(const QFutureInterface<TestResult> &fi,
- Utils::QtcProcess *app) const override;
+ TestOutputReader *createOutputReader(Utils::QtcProcess *app) const override;
QStringList argumentsForTestRunner(QStringList *omitted = nullptr) const override;
Utils::Environment filteredEnvironment(const Utils::Environment &original) const override;
};
diff --git a/src/plugins/autotest/boost/boosttestoutputreader.cpp b/src/plugins/autotest/boost/boosttestoutputreader.cpp
index 5f549cca320..3ac49d2aa46 100644
--- a/src/plugins/autotest/boost/boosttestoutputreader.cpp
+++ b/src/plugins/autotest/boost/boosttestoutputreader.cpp
@@ -5,6 +5,7 @@
#include "boosttestsettings.h"
#include "boosttestresult.h"
+
#include "../autotesttr.h"
#include "../testtreeitem.h"
@@ -21,12 +22,11 @@ namespace Internal {
static Q_LOGGING_CATEGORY(orLog, "qtc.autotest.boost.outputreader", QtWarningMsg)
-BoostTestOutputReader::BoostTestOutputReader(const QFutureInterface<TestResult> &futureInterface,
- QtcProcess *testApplication,
+BoostTestOutputReader::BoostTestOutputReader(QtcProcess *testApplication,
const FilePath &buildDirectory,
const FilePath &projectFile,
LogLevel log, ReportLevel report)
- : TestOutputReader(futureInterface, testApplication, buildDirectory)
+ : TestOutputReader(testApplication, buildDirectory)
, m_projectFile(projectFile)
, m_logLevel(log)
, m_reportLevel(report)
diff --git a/src/plugins/autotest/boost/boosttestoutputreader.h b/src/plugins/autotest/boost/boosttestoutputreader.h
index 8449a0708ab..ff2066f7d45 100644
--- a/src/plugins/autotest/boost/boosttestoutputreader.h
+++ b/src/plugins/autotest/boost/boosttestoutputreader.h
@@ -15,8 +15,7 @@ class BoostTestOutputReader : public TestOutputReader
{
Q_OBJECT
public:
- BoostTestOutputReader(const QFutureInterface<TestResult> &futureInterface,
- Utils::QtcProcess *testApplication, const Utils::FilePath &buildDirectory,
+ BoostTestOutputReader(Utils::QtcProcess *testApplication, const Utils::FilePath &buildDirectory,
const Utils::FilePath &projectFile, LogLevel log, ReportLevel report);
protected:
void processOutputLine(const QByteArray &outputLine) override;
diff --git a/src/plugins/autotest/boost/boosttestparser.cpp b/src/plugins/autotest/boost/boosttestparser.cpp
index bbf6dfd56ce..8c701942ada 100644
--- a/src/plugins/autotest/boost/boosttestparser.cpp
+++ b/src/plugins/autotest/boost/boosttestparser.cpp
@@ -9,6 +9,7 @@
#include <cppeditor/cppmodelmanager.h>
#include <QMap>
+#include <QPromise>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
@@ -96,7 +97,7 @@ static BoostTestParseResult *createParseResult(const QString &name, const FilePa
}
-bool BoostTestParser::processDocument(QFutureInterface<TestParseResultPtr> &futureInterface,
+bool BoostTestParser::processDocument(QPromise<TestParseResultPtr> &promise,
const FilePath &fileName)
{
CPlusPlus::Document::Ptr doc = document(fileName);
@@ -148,7 +149,7 @@ bool BoostTestParser::processDocument(QFutureInterface<TestParseResultPtr> &futu
locationAndType.m_type,
tmpInfo);
currentSuite->children.append(funcResult);
- futureInterface.reportResult(TestParseResultPtr(topLevelSuite));
+ promise.addResult(TestParseResultPtr(topLevelSuite));
}
}
return true;
diff --git a/src/plugins/autotest/boost/boosttestparser.h b/src/plugins/autotest/boost/boosttestparser.h
index 049f42d0c93..3ea67815c95 100644
--- a/src/plugins/autotest/boost/boosttestparser.h
+++ b/src/plugins/autotest/boost/boosttestparser.h
@@ -22,7 +22,7 @@ class BoostTestParser : public CppParser
{
public:
explicit BoostTestParser(ITestFramework *framework) : CppParser(framework) {}
- bool processDocument(QFutureInterface<TestParseResultPtr> &futureInterface,
+ bool processDocument(QPromise<TestParseResultPtr> &promise,
const Utils::FilePath &fileName) override;
};
diff --git a/src/plugins/autotest/boost/boosttesttreeitem.cpp b/src/plugins/autotest/boost/boosttesttreeitem.cpp
index 4b53d92754e..957ff73021a 100644
--- a/src/plugins/autotest/boost/boosttesttreeitem.cpp
+++ b/src/plugins/autotest/boost/boosttesttreeitem.cpp
@@ -11,7 +11,9 @@
#include "../itestframework.h"
#include <cppeditor/cppmodelmanager.h>
-#include <projectexplorer/session.h>
+
+#include <projectexplorer/projectmanager.h>
+
#include <utils/qtcassert.h>
#include <QRegularExpression>
@@ -156,7 +158,7 @@ static QString handleSpecialFunctionNames(const QString &name)
QList<ITestConfiguration *> BoostTestTreeItem::getAllTestConfigurations() const
{
QList<ITestConfiguration *> result;
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
if (!project || type() != Root)
return result;
@@ -200,7 +202,7 @@ QList<ITestConfiguration *> BoostTestTreeItem::getTestConfigurations(
std::function<bool(BoostTestTreeItem *)> predicate) const
{
QList<ITestConfiguration *> result;
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
if (!project || type() != Root)
return result;
@@ -261,7 +263,7 @@ QList<ITestConfiguration *> BoostTestTreeItem::getFailedTestConfigurations() con
ITestConfiguration *BoostTestTreeItem::testConfiguration() const
{
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
QTC_ASSERT(project, return nullptr);
const auto cppMM = CppEditor::CppModelManager::instance();
QTC_ASSERT(cppMM, return nullptr);
diff --git a/src/plugins/autotest/catch/catchconfiguration.cpp b/src/plugins/autotest/catch/catchconfiguration.cpp
index c08f7ac82b6..593acc589e7 100644
--- a/src/plugins/autotest/catch/catchconfiguration.cpp
+++ b/src/plugins/autotest/catch/catchconfiguration.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "catchconfiguration.h"
+
#include "catchoutputreader.h"
#include "catchtestsettings.h"
@@ -9,17 +10,14 @@
#include "../itestframework.h"
#include "../testsettings.h"
-#include <utils/stringutils.h>
-
using namespace Utils;
namespace Autotest {
namespace Internal {
-TestOutputReader *CatchConfiguration::createOutputReader(const QFutureInterface<TestResult> &fi,
- QtcProcess *app) const
+TestOutputReader *CatchConfiguration::createOutputReader(QtcProcess *app) const
{
- return new CatchOutputReader(fi, app, buildDirectory(), projectFile());
+ return new CatchOutputReader(app, buildDirectory(), projectFile());
}
static QStringList filterInterfering(const QStringList &provided, QStringList *omitted)
diff --git a/src/plugins/autotest/catch/catchconfiguration.h b/src/plugins/autotest/catch/catchconfiguration.h
index bfa37f01644..90b9b09dceb 100644
--- a/src/plugins/autotest/catch/catchconfiguration.h
+++ b/src/plugins/autotest/catch/catchconfiguration.h
@@ -12,8 +12,7 @@ class CatchConfiguration : public DebuggableTestConfiguration
{
public:
CatchConfiguration(ITestFramework *framework) : DebuggableTestConfiguration(framework) {}
- TestOutputReader *createOutputReader(const QFutureInterface<TestResult> &fi,
- Utils::QtcProcess *app) const override;
+ TestOutputReader *createOutputReader(Utils::QtcProcess *app) const override;
QStringList argumentsForTestRunner(QStringList *omitted = nullptr) const override;
Utils::Environment filteredEnvironment(const Utils::Environment &original) const override;
};
diff --git a/src/plugins/autotest/catch/catchoutputreader.cpp b/src/plugins/autotest/catch/catchoutputreader.cpp
index 95e437fdc4b..9bcb3778058 100644
--- a/src/plugins/autotest/catch/catchoutputreader.cpp
+++ b/src/plugins/autotest/catch/catchoutputreader.cpp
@@ -2,13 +2,11 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "catchoutputreader.h"
+
#include "catchresult.h"
#include "../autotesttr.h"
-#include <utils/fileutils.h>
-#include <utils/qtcassert.h>
-
using namespace Utils;
namespace Autotest {
@@ -31,11 +29,10 @@ namespace CatchXml {
const char TestCaseResultElement[] = "OverallResult";
}
-CatchOutputReader::CatchOutputReader(const QFutureInterface<TestResult> &futureInterface,
- QtcProcess *testApplication,
+CatchOutputReader::CatchOutputReader(QtcProcess *testApplication,
const FilePath &buildDirectory,
const FilePath &projectFile)
- : TestOutputReader (futureInterface, testApplication, buildDirectory)
+ : TestOutputReader(testApplication, buildDirectory)
, m_projectFile(projectFile)
{
}
diff --git a/src/plugins/autotest/catch/catchoutputreader.h b/src/plugins/autotest/catch/catchoutputreader.h
index 51e8c1e3389..d65ebd7650f 100644
--- a/src/plugins/autotest/catch/catchoutputreader.h
+++ b/src/plugins/autotest/catch/catchoutputreader.h
@@ -14,8 +14,7 @@ namespace Internal {
class CatchOutputReader : public TestOutputReader
{
public:
- CatchOutputReader(const QFutureInterface<TestResult> &futureInterface,
- Utils::QtcProcess *testApplication, const Utils::FilePath &buildDirectory,
+ CatchOutputReader(Utils::QtcProcess *testApplication, const Utils::FilePath &buildDirectory,
const Utils::FilePath &projectFile);
protected:
diff --git a/src/plugins/autotest/catch/catchtestparser.cpp b/src/plugins/autotest/catch/catchtestparser.cpp
index dd01c01c90b..3cc7a4540be 100644
--- a/src/plugins/autotest/catch/catchtestparser.cpp
+++ b/src/plugins/autotest/catch/catchtestparser.cpp
@@ -11,6 +11,7 @@
#include <cppeditor/projectpart.h>
#include <utils/qtcassert.h>
+#include <QPromise>
#include <QRegularExpression>
using namespace Utils;
@@ -91,7 +92,7 @@ static bool hasCatchNames(const CPlusPlus::Document::Ptr &document)
return false;
}
-bool CatchTestParser::processDocument(QFutureInterface<TestParseResultPtr> &futureInterface,
+bool CatchTestParser::processDocument(QPromise<TestParseResultPtr> &promise,
const FilePath &fileName)
{
CPlusPlus::Document::Ptr doc = document(fileName);
@@ -144,7 +145,7 @@ bool CatchTestParser::processDocument(QFutureInterface<TestParseResultPtr> &futu
parseResult->children.append(testCase);
}
- futureInterface.reportResult(TestParseResultPtr(parseResult));
+ promise.addResult(TestParseResultPtr(parseResult));
return !foundTests.isEmpty();
}
diff --git a/src/plugins/autotest/catch/catchtestparser.h b/src/plugins/autotest/catch/catchtestparser.h
index 6159ff58133..8b72073204b 100644
--- a/src/plugins/autotest/catch/catchtestparser.h
+++ b/src/plugins/autotest/catch/catchtestparser.h
@@ -23,7 +23,7 @@ class CatchTestParser : public CppParser
public:
CatchTestParser(ITestFramework *framework)
: CppParser(framework) {}
- bool processDocument(QFutureInterface<TestParseResultPtr> &futureInterface,
+ bool processDocument(QPromise<TestParseResultPtr> &promise,
const Utils::FilePath &fileName) override;
};
diff --git a/src/plugins/autotest/catch/catchtreeitem.cpp b/src/plugins/autotest/catch/catchtreeitem.cpp
index fe69fece87e..034c7ab1d04 100644
--- a/src/plugins/autotest/catch/catchtreeitem.cpp
+++ b/src/plugins/autotest/catch/catchtreeitem.cpp
@@ -10,8 +10,10 @@
#include "../itestframework.h"
#include <cppeditor/cppmodelmanager.h>
+
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
+
#include <utils/qtcassert.h>
using namespace Utils;
@@ -30,7 +32,7 @@ static QString nonRootDisplayName(const CatchTreeItem *it)
{
if (it->type() != TestTreeItem::TestSuite)
return it->name();
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
if (!project)
return it->name();
TestTreeItem *parent = it->parentItem();
@@ -141,7 +143,7 @@ bool CatchTreeItem::canProvideDebugConfiguration() const
ITestConfiguration *CatchTreeItem::testConfiguration() const
{
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
QTC_ASSERT(project, return nullptr);
const auto cppMM = CppEditor::CppModelManager::instance();
QTC_ASSERT(cppMM, return nullptr);
@@ -244,7 +246,7 @@ QList<ITestConfiguration *> CatchTreeItem::getSelectedTestConfigurations() const
QList<ITestConfiguration *> CatchTreeItem::getFailedTestConfigurations() const
{
QList<ITestConfiguration *> result;
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
if (!project || type() != Root)
return result;
@@ -271,7 +273,7 @@ QList<ITestConfiguration *> CatchTreeItem::getTestConfigurationsForFile(const Fi
const auto cppMM = CppEditor::CppModelManager::instance();
QTC_ASSERT(cppMM, return result);
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
if (!project || type() != Root)
return result;
@@ -293,7 +295,7 @@ QList<ITestConfiguration *> CatchTreeItem::getTestConfigurationsForFile(const Fi
testConfig = new CatchConfiguration(framework());
testConfig->setTestCases(testCases);
testConfig->setProjectFile(item->proFile());
- testConfig->setProject(ProjectExplorer::SessionManager::startupProject());
+ testConfig->setProject(ProjectExplorer::ProjectManager::startupProject());
testConfig->setInternalTargets(cppMM->internalTargets(item->filePath()));
result << testConfig;
}
@@ -314,7 +316,7 @@ QString CatchTreeItem::stateSuffix() const
QList<ITestConfiguration *> CatchTreeItem::getTestConfigurations(bool ignoreCheckState) const
{
QList<ITestConfiguration *> result;
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
if (!project || type() != Root)
return result;
diff --git a/src/plugins/autotest/ctest/ctestconfiguration.cpp b/src/plugins/autotest/ctest/ctestconfiguration.cpp
index 9208888033e..183d7874e86 100644
--- a/src/plugins/autotest/ctest/ctestconfiguration.cpp
+++ b/src/plugins/autotest/ctest/ctestconfiguration.cpp
@@ -13,10 +13,9 @@ CTestConfiguration::CTestConfiguration(ITestBase *testBase)
setDisplayName("CTest");
}
-TestOutputReader *CTestConfiguration::createOutputReader(const QFutureInterface<TestResult> &fi,
- Utils::QtcProcess *app) const
+TestOutputReader *CTestConfiguration::createOutputReader(Utils::QtcProcess *app) const
{
- return new CTestOutputReader(fi, app, workingDirectory());
+ return new CTestOutputReader(app, workingDirectory());
}
} // namespace Internal
diff --git a/src/plugins/autotest/ctest/ctestconfiguration.h b/src/plugins/autotest/ctest/ctestconfiguration.h
index db0efb17173..4496fb4aaf5 100644
--- a/src/plugins/autotest/ctest/ctestconfiguration.h
+++ b/src/plugins/autotest/ctest/ctestconfiguration.h
@@ -13,8 +13,7 @@ class CTestConfiguration final : public Autotest::TestToolConfiguration
public:
explicit CTestConfiguration(ITestBase *testBase);
- TestOutputReader *createOutputReader(const QFutureInterface<TestResult> &fi,
- Utils::QtcProcess *app) const final;
+ TestOutputReader *createOutputReader(Utils::QtcProcess *app) const final;
};
} // namespace Internal
diff --git a/src/plugins/autotest/ctest/ctestoutputreader.cpp b/src/plugins/autotest/ctest/ctestoutputreader.cpp
index 70ec5f0659b..9a6259d8c0d 100644
--- a/src/plugins/autotest/ctest/ctestoutputreader.cpp
+++ b/src/plugins/autotest/ctest/ctestoutputreader.cpp
@@ -5,13 +5,11 @@
#include "../autotesttr.h"
#include "../testframeworkmanager.h"
-#include "../testresult.h"
#include "../testtreeitem.h"
#include <cmakeprojectmanager/cmakeprojectconstants.h>
#include <utils/qtcassert.h>
-#include <utils/treemodel.h>
#include <QRegularExpression>
@@ -52,10 +50,9 @@ public:
{}
};
-CTestOutputReader::CTestOutputReader(const QFutureInterface<TestResult> &futureInterface,
- QtcProcess *testApplication,
+CTestOutputReader::CTestOutputReader(QtcProcess *testApplication,
const FilePath &buildDirectory)
- : TestOutputReader(futureInterface, testApplication, buildDirectory)
+ : TestOutputReader(testApplication, buildDirectory)
{
}
diff --git a/src/plugins/autotest/ctest/ctestoutputreader.h b/src/plugins/autotest/ctest/ctestoutputreader.h
index 8a6e1f124e4..896f17ba245 100644
--- a/src/plugins/autotest/ctest/ctestoutputreader.h
+++ b/src/plugins/autotest/ctest/ctestoutputreader.h
@@ -13,8 +13,7 @@ namespace Internal {
class CTestOutputReader final : public Autotest::TestOutputReader
{
public:
- CTestOutputReader(const QFutureInterface<TestResult> &futureInterface,
- Utils::QtcProcess *testApplication, const Utils::FilePath &buildDirectory);
+ CTestOutputReader(Utils::QtcProcess *testApplication, const Utils::FilePath &buildDirectory);
protected:
void processOutputLine(const QByteArray &outputLineWithNewLine) final;
diff --git a/src/plugins/autotest/ctest/ctesttreeitem.cpp b/src/plugins/autotest/ctest/ctesttreeitem.cpp
index 3e59c2db81c..dac1b6da008 100644
--- a/src/plugins/autotest/ctest/ctesttreeitem.cpp
+++ b/src/plugins/autotest/ctest/ctesttreeitem.cpp
@@ -14,11 +14,11 @@
#include <projectexplorer/buildsystem.h>
#include <projectexplorer/environmentaspect.h>
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <utils/link.h>
-#include <utils/qtcassert.h>
+#include <utils/qtcassert.h>
using namespace Utils;
@@ -79,7 +79,7 @@ QVariant CTestTreeItem::data(int column, int role) const
QList<ITestConfiguration *> CTestTreeItem::testConfigurationsFor(const QStringList &selected) const
{
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
if (!project)
return {};
diff --git a/src/plugins/autotest/gtest/gtestconfiguration.cpp b/src/plugins/autotest/gtest/gtestconfiguration.cpp
index d2f869d7645..1da1d212e4c 100644
--- a/src/plugins/autotest/gtest/gtestconfiguration.cpp
+++ b/src/plugins/autotest/gtest/gtestconfiguration.cpp
@@ -5,22 +5,21 @@
#include "gtestoutputreader.h"
#include "gtestsettings.h"
+
#include "../autotestplugin.h"
#include "../itestframework.h"
#include "../testsettings.h"
#include <utils/algorithm.h>
-#include <utils/stringutils.h>
using namespace Utils;
namespace Autotest {
namespace Internal {
-TestOutputReader *GTestConfiguration::createOutputReader(const QFutureInterface<TestResult> &fi,
- QtcProcess *app) const
+TestOutputReader *GTestConfiguration::createOutputReader(QtcProcess *app) const
{
- return new GTestOutputReader(fi, app, buildDirectory(), projectFile());
+ return new GTestOutputReader(app, buildDirectory(), projectFile());
}
QStringList filterInterfering(const QStringList &provided, QStringList *omitted)
diff --git a/src/plugins/autotest/gtest/gtestconfiguration.h b/src/plugins/autotest/gtest/gtestconfiguration.h
index 97bcbd654fc..a68a1f9674d 100644
--- a/src/plugins/autotest/gtest/gtestconfiguration.h
+++ b/src/plugins/autotest/gtest/gtestconfiguration.h
@@ -14,8 +14,7 @@ public:
explicit GTestConfiguration(ITestFramework *framework)
: DebuggableTestConfiguration(framework) {}
- TestOutputReader *createOutputReader(const QFutureInterface<TestResult> &fi,
- Utils::QtcProcess *app) const override;
+ TestOutputReader *createOutputReader(Utils::QtcProcess *app) const override;
QStringList argumentsForTestRunner(QStringList *omitted = nullptr) const override;
Utils::Environment filteredEnvironment(const Utils::Environment &original) const override;
};
diff --git a/src/plugins/autotest/gtest/gtestoutputreader.cpp b/src/plugins/autotest/gtest/gtestoutputreader.cpp
index a24377bef7a..6ee766b28a3 100644
--- a/src/plugins/autotest/gtest/gtestoutputreader.cpp
+++ b/src/plugins/autotest/gtest/gtestoutputreader.cpp
@@ -17,11 +17,10 @@ using namespace Utils;
namespace Autotest {
namespace Internal {
-GTestOutputReader::GTestOutputReader(const QFutureInterface<TestResult> &futureInterface,
- QtcProcess *testApplication,
+GTestOutputReader::GTestOutputReader(QtcProcess *testApplication,
const FilePath &buildDirectory,
const FilePath &projectFile)
- : TestOutputReader(futureInterface, testApplication, buildDirectory)
+ : TestOutputReader(testApplication, buildDirectory)
, m_projectFile(projectFile)
{
if (testApplication) {
@@ -116,7 +115,7 @@ void GTestOutputReader::processOutputLine(const QByteArray &outputLine)
testResult.setResult(ResultType::MessageInternal);
testResult.setDescription(Tr::tr("Execution took %1.").arg(match.captured(2)));
reportResult(testResult);
- m_futureInterface.setProgressValue(m_futureInterface.progressValue() + 1);
+ // TODO: bump progress?
} else if (ExactMatch match = testSetFail.match(line)) {
m_testSetStarted = false;
TestResult testResult = createDefaultResult();
@@ -127,7 +126,7 @@ void GTestOutputReader::processOutputLine(const QByteArray &outputLine)
testResult.setResult(ResultType::MessageInternal);
testResult.setDescription(Tr::tr("Execution took %1.").arg(match.captured(2)));
reportResult(testResult);
- m_futureInterface.setProgressValue(m_futureInterface.progressValue() + 1);
+ // TODO: bump progress?
} else if (ExactMatch match = testSetSkipped.match(line)) {
if (!m_testSetStarted) // ignore SKIPPED at summary
return;
diff --git a/src/plugins/autotest/gtest/gtestoutputreader.h b/src/plugins/autotest/gtest/gtestoutputreader.h
index e73ab0e8236..a8ced858dd2 100644
--- a/src/plugins/autotest/gtest/gtestoutputreader.h
+++ b/src/plugins/autotest/gtest/gtestoutputreader.h
@@ -11,8 +11,7 @@ namespace Internal {
class GTestOutputReader : public TestOutputReader
{
public:
- GTestOutputReader(const QFutureInterface<TestResult> &futureInterface,
- Utils::QtcProcess *testApplication, const Utils::FilePath &buildDirectory,
+ GTestOutputReader(Utils::QtcProcess *testApplication, const Utils::FilePath &buildDirectory,
const Utils::FilePath &projectFile);
protected:
void processOutputLine(const QByteArray &outputLine) override;
diff --git a/src/plugins/autotest/gtest/gtestparser.cpp b/src/plugins/autotest/gtest/gtestparser.cpp
index aa59bb94c2e..c8cc92005e8 100644
--- a/src/plugins/autotest/gtest/gtestparser.cpp
+++ b/src/plugins/autotest/gtest/gtestparser.cpp
@@ -10,6 +10,7 @@
#include <cppeditor/cppmodelmanager.h>
#include <cppeditor/projectpart.h>
+#include <QPromise>
#include <QRegularExpression>
#include <QRegularExpressionMatch>
@@ -70,7 +71,7 @@ static bool hasGTestNames(const CPlusPlus::Document::Ptr &document)
return false;
}
-bool GTestParser::processDocument(QFutureInterface<TestParseResultPtr> &futureInterface,
+bool GTestParser::processDocument(QPromise<TestParseResultPtr> &promise,
const FilePath &fileName)
{
CPlusPlus::Document::Ptr doc = document(fileName);
@@ -124,7 +125,7 @@ bool GTestParser::processDocument(QFutureInterface<TestParseResultPtr> &futureIn
parseResult->children.append(testSet);
}
- futureInterface.reportResult(TestParseResultPtr(parseResult));
+ promise.addResult(TestParseResultPtr(parseResult));
}
return !result.isEmpty();
}
diff --git a/src/plugins/autotest/gtest/gtestparser.h b/src/plugins/autotest/gtest/gtestparser.h
index 665a8a496c2..52febe6b890 100644
--- a/src/plugins/autotest/gtest/gtestparser.h
+++ b/src/plugins/autotest/gtest/gtestparser.h
@@ -22,7 +22,7 @@ class GTestParser : public CppParser
{
public:
explicit GTestParser(ITestFramework *framework) : CppParser(framework) {}
- bool processDocument(QFutureInterface<TestParseResultPtr> &futureInterface,
+ bool processDocument(QPromise<TestParseResultPtr> &futureInterface,
const Utils::FilePath &fileName) override;
};
diff --git a/src/plugins/autotest/gtest/gtesttreeitem.cpp b/src/plugins/autotest/gtest/gtesttreeitem.cpp
index c79ae93d1b9..f91b68f0e38 100644
--- a/src/plugins/autotest/gtest/gtesttreeitem.cpp
+++ b/src/plugins/autotest/gtest/gtesttreeitem.cpp
@@ -10,7 +10,9 @@
#include "../autotesttr.h"
#include <cppeditor/cppmodelmanager.h>
-#include <projectexplorer/session.h>
+
+#include <projectexplorer/projectmanager.h>
+#include <projectexplorer/projectmanager.h>
#include <utils/algorithm.h>
#include <utils/icon.h>
@@ -146,7 +148,7 @@ QVariant GTestTreeItem::data(int column, int role) const
ITestConfiguration *GTestTreeItem::testConfiguration() const
{
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
QTC_ASSERT(project, return nullptr);
GTestConfiguration *config = nullptr;
@@ -252,7 +254,7 @@ static void collectFailedTestInfo(const GTestTreeItem *item,
QList<ITestConfiguration *> GTestTreeItem::getTestConfigurations(bool ignoreCheckState) const
{
QList<ITestConfiguration *> result;
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
if (!project || type() != Root)
return result;
@@ -291,7 +293,7 @@ QList<ITestConfiguration *> GTestTreeItem::getSelectedTestConfigurations() const
QList<ITestConfiguration *> GTestTreeItem::getFailedTestConfigurations() const
{
QList<ITestConfiguration *> result;
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
if (!project || type() != Root)
return result;
@@ -316,7 +318,7 @@ QList<ITestConfiguration *> GTestTreeItem::getFailedTestConfigurations() const
QList<ITestConfiguration *> GTestTreeItem::getTestConfigurationsForFile(const FilePath &fileName) const
{
QList<ITestConfiguration *> result;
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
if (!project || type() != Root)
return result;
@@ -503,7 +505,7 @@ QSet<QString> internalTargets(const TestTreeItem &item)
{
QSet<QString> result;
const auto cppMM = CppEditor::CppModelManager::instance();
- const auto projectInfo = cppMM->projectInfo(ProjectExplorer::SessionManager::startupProject());
+ const auto projectInfo = cppMM->projectInfo(ProjectExplorer::ProjectManager::startupProject());
if (!projectInfo)
return {};
const FilePath filePath = item.filePath();
diff --git a/src/plugins/autotest/itestparser.h b/src/plugins/autotest/itestparser.h
index 7e16a61b90d..24b132c610c 100644
--- a/src/plugins/autotest/itestparser.h
+++ b/src/plugins/autotest/itestparser.h
@@ -9,9 +9,9 @@
#include <cppeditor/cppworkingcopy.h>
#include <qmljs/qmljsdocument.h>
-#include <QFutureInterface>
-
QT_BEGIN_NAMESPACE
+template <class T>
+class QPromise;
class QRegularExpression;
QT_END_NAMESPACE
@@ -46,7 +46,7 @@ public:
explicit ITestParser(ITestFramework *framework) : m_framework(framework) {}
virtual ~ITestParser() { }
virtual void init(const Utils::FilePaths &filesToParse, bool fullParse) = 0;
- virtual bool processDocument(QFutureInterface<TestParseResultPtr> &futureInterface,
+ virtual bool processDocument(QPromise<TestParseResultPtr> &futureInterface,
const Utils::FilePath &fileName) = 0;
virtual QStringList supportedExtensions() const { return {}; }
diff --git a/src/plugins/autotest/qtest/qttestconfiguration.cpp b/src/plugins/autotest/qtest/qttestconfiguration.cpp
index 8a77f305e34..9210a9685f1 100644
--- a/src/plugins/autotest/qtest/qttestconfiguration.cpp
+++ b/src/plugins/autotest/qtest/qttestconfiguration.cpp
@@ -2,16 +2,16 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "qttestconfiguration.h"
-#include "qttestconstants.h"
+
#include "qttestoutputreader.h"
#include "qttestsettings.h"
#include "qttest_utils.h"
+
#include "../autotestplugin.h"
#include "../itestframework.h"
#include "../testsettings.h"
#include <utils/algorithm.h>
-#include <utils/stringutils.h>
using namespace Utils;
@@ -28,14 +28,13 @@ static QStringList quoteIfNeeded(const QStringList &testCases, bool debugMode)
});
}
-TestOutputReader *QtTestConfiguration::createOutputReader(const QFutureInterface<TestResult> &fi,
- QtcProcess *app) const
+TestOutputReader *QtTestConfiguration::createOutputReader(QtcProcess *app) const
{
auto qtSettings = static_cast<QtTestSettings *>(framework()->testSettings());
const QtTestOutputReader::OutputMode mode = qtSettings && qtSettings->useXMLOutput.value()
? QtTestOutputReader::XML
: QtTestOutputReader::PlainText;
- return new QtTestOutputReader(fi, app, buildDirectory(), projectFile(), mode, TestType::QtTest);
+ return new QtTestOutputReader(app, buildDirectory(), projectFile(), mode, TestType::QtTest);
}
QStringList QtTestConfiguration::argumentsForTestRunner(QStringList *omitted) const
diff --git a/src/plugins/autotest/qtest/qttestconfiguration.h b/src/plugins/autotest/qtest/qttestconfiguration.h
index a99c93f0b75..f370f97c786 100644
--- a/src/plugins/autotest/qtest/qttestconfiguration.h
+++ b/src/plugins/autotest/qtest/qttestconfiguration.h
@@ -13,8 +13,7 @@ class QtTestConfiguration : public DebuggableTestConfiguration
public:
explicit QtTestConfiguration(ITestFramework *framework)
: DebuggableTestConfiguration(framework) {}
- TestOutputReader *createOutputReader(const QFutureInterface<TestResult> &fi,
- Utils::QtcProcess *app) const override;
+ TestOutputReader *createOutputReader(Utils::QtcProcess *app) const override;
QStringList argumentsForTestRunner(QStringList *omitted = nullptr) const override;
Utils::Environment filteredEnvironment(const Utils::Environment &original) const override;
};
diff --git a/src/plugins/autotest/qtest/qttestoutputreader.cpp b/src/plugins/autotest/qtest/qttestoutputreader.cpp
index 073ef0e9fed..8916b8d9818 100644
--- a/src/plugins/autotest/qtest/qttestoutputreader.cpp
+++ b/src/plugins/autotest/qtest/qttestoutputreader.cpp
@@ -12,8 +12,6 @@
#include <QRegularExpression>
-#include <cctype>
-
using namespace Utils;
namespace Autotest {
@@ -99,18 +97,15 @@ static QString constructBenchmarkInformation(const QString &metric, double value
else if (metric == "CPUCycles") // -perf
metricsText = "CPU cycles";
return Tr::tr("%1 %2 per iteration (total: %3, iterations: %4)")
- .arg(formatResult(value))
- .arg(metricsText)
- .arg(formatResult(value * double(iterations)))
+ .arg(formatResult(value), metricsText, formatResult(value * double(iterations)))
.arg(iterations);
}
-QtTestOutputReader::QtTestOutputReader(const QFutureInterface<TestResult> &futureInterface,
- QtcProcess *testApplication,
+QtTestOutputReader::QtTestOutputReader(QtcProcess *testApplication,
const FilePath &buildDirectory,
const FilePath &projectFile,
OutputMode mode, TestType type)
- : TestOutputReader(futureInterface, testApplication, buildDirectory)
+ : TestOutputReader(testApplication, buildDirectory)
, m_projectFile(projectFile)
, m_mode(mode)
, m_testType(type)
@@ -177,8 +172,6 @@ void QtTestOutputReader::processXMLOutput(const QByteArray &outputLine)
m_xmlReader.addData("\n");
m_xmlReader.addData(QString::fromUtf8(outputLine));
while (!m_xmlReader.atEnd()) {
- if (m_futureInterface.isCanceled())
- return;
QXmlStreamReader::TokenType token = m_xmlReader.readNext();
switch (token) {
case QXmlStreamReader::StartDocument:
@@ -277,7 +270,7 @@ void QtTestOutputReader::processXMLOutput(const QByteArray &outputLine)
const QStringView currentTag = m_xmlReader.name();
if (currentTag == QStringLiteral("TestFunction")) {
sendFinishMessage(true);
- m_futureInterface.setProgressValue(m_futureInterface.progressValue() + 1);
+ // TODO: bump progress?
m_dataTag.clear();
m_formerTestCase = m_testCase;
m_testCase.clear();
@@ -347,9 +340,6 @@ void QtTestOutputReader::processPlainTextOutput(const QByteArray &outputLine)
static const QRegularExpression locationUnix(QT_TEST_FAIL_UNIX_REGEXP);
static const QRegularExpression locationWin(QT_TEST_FAIL_WIN_REGEXP);
- if (m_futureInterface.isCanceled())
- return;
-
const QString line = QString::fromUtf8(outputLine);
QRegularExpressionMatch match;
diff --git a/src/plugins/autotest/qtest/qttestoutputreader.h b/src/plugins/autotest/qtest/qttestoutputreader.h
index 6db9aa87810..a730d540dd5 100644
--- a/src/plugins/autotest/qtest/qttestoutputreader.h
+++ b/src/plugins/autotest/qtest/qttestoutputreader.h
@@ -3,9 +3,10 @@
#pragma once
-#include "qttestconstants.h"
#include "../testoutputreader.h"
+#include "qttestconstants.h"
+
#include <QXmlStreamReader>
namespace Autotest {
@@ -22,8 +23,7 @@ public:
PlainText
};
- QtTestOutputReader(const QFutureInterface<TestResult> &futureInterface,
- Utils::QtcProcess *testApplication, const Utils::FilePath &buildDirectory,
+ QtTestOutputReader(Utils::QtcProcess *testApplication, const Utils::FilePath &buildDirectory,
const Utils::FilePath &projectFile, OutputMode mode, TestType type);
protected:
void processOutputLine(const QByteArray &outputLine) override;
diff --git a/src/plugins/autotest/qtest/qttestparser.cpp b/src/plugins/autotest/qtest/qttestparser.cpp
index 2b13133f03f..a532a13cb3d 100644
--- a/src/plugins/autotest/qtest/qttestparser.cpp
+++ b/src/plugins/autotest/qtest/qttestparser.cpp
@@ -10,6 +10,7 @@
#include <cplusplus/TypeOfExpression.h>
#include <utils/algorithm.h>
+#include <QPromise>
#include <QRegularExpressionMatchIterator>
using namespace Utils;
@@ -292,7 +293,7 @@ static bool isQObject(const CPlusPlus::Document::Ptr &declaringDoc)
|| file.endsWith("QtCore/qobject.h") || file.endsWith("kernel/qobject.h");
}
-bool QtTestParser::processDocument(QFutureInterface<TestParseResultPtr> &futureInterface,
+bool QtTestParser::processDocument(QPromise<TestParseResultPtr> &promise,
const FilePath &fileName)
{
CPlusPlus::Document::Ptr doc = document(fileName);
@@ -325,7 +326,7 @@ bool QtTestParser::processDocument(QFutureInterface<TestParseResultPtr> &futureI
data.multipleTestCases = testCase.multipleTestCases;
QtTestParseResult *parseResult
= createParseResult(testCase.name, data, projectParts.first()->projectFile);
- futureInterface.reportResult(TestParseResultPtr(parseResult));
+ promise.addResult(TestParseResultPtr(parseResult));
reported = true;
}
}
diff --git a/src/plugins/autotest/qtest/qttestparser.h b/src/plugins/autotest/qtest/qttestparser.h
index db677929ec8..9dafcdc77e1 100644
--- a/src/plugins/autotest/qtest/qttestparser.h
+++ b/src/plugins/autotest/qtest/qttestparser.h
@@ -36,7 +36,7 @@ public:
void init(const Utils::FilePaths &filesToParse, bool fullParse) override;
void release() override;
- bool processDocument(QFutureInterface<TestParseResultPtr> &futureInterface,
+ bool processDocument(QPromise<TestParseResultPtr> &promise,
const Utils::FilePath &fileName) override;
private:
diff --git a/src/plugins/autotest/qtest/qttesttreeitem.cpp b/src/plugins/autotest/qtest/qttesttreeitem.cpp
index 2f846372822..81f95c17752 100644
--- a/src/plugins/autotest/qtest/qttesttreeitem.cpp
+++ b/src/plugins/autotest/qtest/qttesttreeitem.cpp
@@ -9,7 +9,9 @@
#include "../itestframework.h"
#include <cppeditor/cppmodelmanager.h>
-#include <projectexplorer/session.h>
+
+#include <projectexplorer/projectmanager.h>
+
#include <utils/qtcassert.h>
using namespace Utils;
@@ -116,7 +118,7 @@ bool QtTestTreeItem::canProvideDebugConfiguration() const
ITestConfiguration *QtTestTreeItem::testConfiguration() const
{
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
QTC_ASSERT(project, return nullptr);
const auto cppMM = CppEditor::CppModelManager::instance();
QTC_ASSERT(cppMM, return nullptr);
@@ -195,7 +197,7 @@ static void fillTestConfigurationsFromCheckState(const TestTreeItem *item,
testConfig = new QtTestConfiguration(item->framework());
testConfig->setTestCases(testCases);
testConfig->setProjectFile(item->proFile());
- testConfig->setProject(ProjectExplorer::SessionManager::startupProject());
+ testConfig->setProject(ProjectExplorer::ProjectManager::startupProject());
testConfig->setInternalTargets(cppMM->internalTargets(item->filePath()));
testConfigurations << testConfig;
}
@@ -229,7 +231,7 @@ static void collectFailedTestInfo(TestTreeItem *item, QList<ITestConfiguration *
QtTestConfiguration *testConfig = new QtTestConfiguration(item->framework());
testConfig->setTestCases(testCases);
testConfig->setProjectFile(item->proFile());
- testConfig->setProject(ProjectExplorer::SessionManager::startupProject());
+ testConfig->setProject(ProjectExplorer::ProjectManager::startupProject());
testConfig->setInternalTargets(cppMM->internalTargets(item->filePath()));
testConfigs << testConfig;
}
@@ -246,7 +248,7 @@ QList<ITestConfiguration *> QtTestTreeItem::getAllTestConfigurations() const
{
QList<ITestConfiguration *> result;
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
if (!project || type() != Root)
return result;
@@ -269,7 +271,7 @@ QList<ITestConfiguration *> QtTestTreeItem::getAllTestConfigurations() const
QList<ITestConfiguration *> QtTestTreeItem::getSelectedTestConfigurations() const
{
QList<ITestConfiguration *> result;
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
if (!project || type() != Root)
return result;
@@ -292,7 +294,7 @@ QList<ITestConfiguration *> QtTestTreeItem::getTestConfigurationsForFile(const F
{
QList<ITestConfiguration *> result;
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
if (!project || type() != Root)
return result;
diff --git a/src/plugins/autotest/quick/quicktestconfiguration.cpp b/src/plugins/autotest/quick/quicktestconfiguration.cpp
index c7cce31acf1..1d00c48c201 100644
--- a/src/plugins/autotest/quick/quicktestconfiguration.cpp
+++ b/src/plugins/autotest/quick/quicktestconfiguration.cpp
@@ -2,16 +2,14 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "quicktestconfiguration.h"
-#include "../qtest/qttestconstants.h"
+
+#include "../autotestplugin.h"
+#include "../itestframework.h"
#include "../qtest/qttestoutputreader.h"
#include "../qtest/qttestsettings.h"
#include "../qtest/qttest_utils.h"
-#include "../autotestplugin.h"
-#include "../itestframework.h"
#include "../testsettings.h"
-#include <utils/stringutils.h>
-
using namespace Utils;
namespace Autotest {
@@ -23,15 +21,13 @@ QuickTestConfiguration::QuickTestConfiguration(ITestFramework *framework)
setMixedDebugging(true);
}
-TestOutputReader *QuickTestConfiguration::createOutputReader(
- const QFutureInterface<TestResult> &fi, QtcProcess *app) const
+TestOutputReader *QuickTestConfiguration::createOutputReader(QtcProcess *app) const
{
auto qtSettings = static_cast<QtTestSettings *>(framework()->testSettings());
const QtTestOutputReader::OutputMode mode = qtSettings && qtSettings->useXMLOutput.value()
? QtTestOutputReader::XML
: QtTestOutputReader::PlainText;
- return new QtTestOutputReader(fi, app, buildDirectory(), projectFile(),
- mode, TestType::QuickTest);
+ return new QtTestOutputReader(app, buildDirectory(), projectFile(), mode, TestType::QuickTest);
}
QStringList QuickTestConfiguration::argumentsForTestRunner(QStringList *omitted) const
diff --git a/src/plugins/autotest/quick/quicktestconfiguration.h b/src/plugins/autotest/quick/quicktestconfiguration.h
index 84e374ebe8a..6739f848256 100644
--- a/src/plugins/autotest/quick/quicktestconfiguration.h
+++ b/src/plugins/autotest/quick/quicktestconfiguration.h
@@ -12,8 +12,7 @@ class QuickTestConfiguration : public DebuggableTestConfiguration
{
public:
explicit QuickTestConfiguration(ITestFramework *framework);
- TestOutputReader *createOutputReader(const QFutureInterface<TestResult> &fi,
- Utils::QtcProcess *app) const override;
+ TestOutputReader *createOutputReader(Utils::QtcProcess *app) const override;
QStringList argumentsForTestRunner(QStringList *omitted = nullptr) const override;
Utils::Environment filteredEnvironment(const Utils::Environment &original) const override;
};
diff --git a/src/plugins/autotest/quick/quicktestparser.cpp b/src/plugins/autotest/quick/quicktestparser.cpp
index 2c72cdf3f1a..de0003b2ffc 100644
--- a/src/plugins/autotest/quick/quicktestparser.cpp
+++ b/src/plugins/autotest/quick/quicktestparser.cpp
@@ -12,14 +12,19 @@
#include <cppeditor/cppmodelmanager.h>
#include <cppeditor/projectpart.h>
-#include <projectexplorer/session.h>
+
+#include <projectexplorer/projectmanager.h>
+
#include <qmljs/parser/qmljsast_p.h>
#include <qmljs/qmljsdialect.h>
#include <qmljstools/qmljsmodelmanager.h>
+
#include <utils/hostosinfo.h>
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
+#include <QPromise>
+
using namespace QmlJS;
using namespace Utils;
@@ -155,10 +160,9 @@ QList<Document::Ptr> QuickTestParser::scanDirectoryForQuickTestQmlFiles(const Fi
QStringList dirsStr({srcDir.toString()});
ModelManagerInterface *qmlJsMM = QmlJSTools::Internal::ModelManager::instance();
// make sure even files not listed in pro file are available inside the snapshot
- QFutureInterface<void> future;
PathsAndLanguages paths;
paths.maybeInsert(srcDir, Dialect::Qml);
- ModelManagerInterface::importScan(future, ModelManagerInterface::workingCopy(), paths, qmlJsMM,
+ ModelManagerInterface::importScan(ModelManagerInterface::workingCopy(), paths, qmlJsMM,
false /*emitDocumentChanges*/, false /*onlyTheLib*/, true /*forceRescan*/ );
const Snapshot snapshot = QmlJSTools::Internal::ModelManager::instance()->snapshot();
@@ -196,7 +200,7 @@ QList<Document::Ptr> QuickTestParser::scanDirectoryForQuickTestQmlFiles(const Fi
return foundDocs;
}
-static bool checkQmlDocumentForQuickTestCode(QFutureInterface<TestParseResultPtr> &futureInterface,
+static bool checkQmlDocumentForQuickTestCode(QPromise<TestParseResultPtr> &promise,
const Document::Ptr &qmlJSDoc,
ITestFramework *framework,
const FilePath &proFile = {},
@@ -240,12 +244,12 @@ static bool checkQmlDocumentForQuickTestCode(QFutureInterface<TestParseResultPtr
parseResult->children.append(funcResult);
}
- futureInterface.reportResult(TestParseResultPtr(parseResult));
+ promise.addResult(TestParseResultPtr(parseResult));
}
return true;
}
-bool QuickTestParser::handleQtQuickTest(QFutureInterface<TestParseResultPtr> &futureInterface,
+bool QuickTestParser::handleQtQuickTest(QPromise<TestParseResultPtr> &promise,
CPlusPlus::Document::Ptr document,
ITestFramework *framework)
{
@@ -263,17 +267,14 @@ bool QuickTestParser::handleQtQuickTest(QFutureInterface<TestParseResultPtr> &fu
if (srcDir.isEmpty())
return false;
- if (futureInterface.isCanceled())
+ if (promise.isCanceled())
return false;
const QList<Document::Ptr> qmlDocs = scanDirectoryForQuickTestQmlFiles(srcDir);
bool result = false;
for (const Document::Ptr &qmlJSDoc : qmlDocs) {
- if (futureInterface.isCanceled())
+ if (promise.isCanceled())
break;
- result |= checkQmlDocumentForQuickTestCode(futureInterface,
- qmlJSDoc,
- framework,
- proFile,
+ result |= checkQmlDocumentForQuickTestCode(promise, qmlJSDoc, framework, proFile,
m_checkForDerivedTests);
}
return result;
@@ -305,9 +306,8 @@ void QuickTestParser::handleDirectoryChanged(const QString &directory)
m_watchedFiles[directory] = filesAndDates;
PathsAndLanguages paths;
paths.maybeInsert(FilePath::fromString(directory), Dialect::Qml);
- QFutureInterface<void> future;
ModelManagerInterface *qmlJsMM = ModelManagerInterface::instance();
- ModelManagerInterface::importScan(future, ModelManagerInterface::workingCopy(), paths,
+ ModelManagerInterface::importScan(ModelManagerInterface::workingCopy(), paths,
qmlJsMM,
true /*emitDocumentChanges*/,
false /*onlyTheLib*/,
@@ -327,8 +327,8 @@ void QuickTestParser::doUpdateWatchPaths(const QStringList &directories)
QuickTestParser::QuickTestParser(ITestFramework *framework)
: CppParser(framework)
{
- connect(ProjectExplorer::SessionManager::instance(),
- &ProjectExplorer::SessionManager::startupProjectChanged, this, [this] {
+ connect(ProjectExplorer::ProjectManager::instance(),
+ &ProjectExplorer::ProjectManager::startupProjectChanged, this, [this] {
const QStringList &dirs = m_directoryWatcher.directories();
if (!dirs.isEmpty())
m_directoryWatcher.removePaths(dirs);
@@ -370,7 +370,7 @@ void QuickTestParser::release()
CppParser::release();
}
-bool QuickTestParser::processDocument(QFutureInterface<TestParseResultPtr> &futureInterface,
+bool QuickTestParser::processDocument(QPromise<TestParseResultPtr> &promise,
const FilePath &fileName)
{
if (fileName.endsWith(".qml")) {
@@ -378,7 +378,7 @@ bool QuickTestParser::processDocument(QFutureInterface<TestParseResultPtr> &futu
if (proFile.isEmpty())
return false;
Document::Ptr qmlJSDoc = m_qmlSnapshot.document(fileName);
- return checkQmlDocumentForQuickTestCode(futureInterface,
+ return checkQmlDocumentForQuickTestCode(promise,
qmlJSDoc,
framework(),
proFile,
@@ -389,7 +389,7 @@ bool QuickTestParser::processDocument(QFutureInterface<TestParseResultPtr> &futu
if (cppdoc.isNull() || !includesQtQuickTest(cppdoc, m_cppSnapshot))
return false;
- return handleQtQuickTest(futureInterface, cppdoc, framework());
+ return handleQtQuickTest(promise, cppdoc, framework());
}
FilePath QuickTestParser::projectFileForMainCppFile(const FilePath &fileName) const
diff --git a/src/plugins/autotest/quick/quicktestparser.h b/src/plugins/autotest/quick/quicktestparser.h
index c0fbc5a3e83..bf3727feca3 100644
--- a/src/plugins/autotest/quick/quicktestparser.h
+++ b/src/plugins/autotest/quick/quicktestparser.h
@@ -26,13 +26,13 @@ public:
explicit QuickTestParser(ITestFramework *framework);
void init(const Utils::FilePaths &filesToParse, bool fullParse) override;
void release() override;
- bool processDocument(QFutureInterface<TestParseResultPtr> &futureInterface,
+ bool processDocument(QPromise<TestParseResultPtr> &promise,
const Utils::FilePath &fileName) override;
Utils::FilePath projectFileForMainCppFile(const Utils::FilePath &fileName) const;
QStringList supportedExtensions() const override { return {"qml"}; };
private:
- bool handleQtQuickTest(QFutureInterface<TestParseResultPtr> &futureInterface,
+ bool handleQtQuickTest(QPromise<TestParseResultPtr> &promise,
CPlusPlus::Document::Ptr document,
ITestFramework *framework);
void handleDirectoryChanged(const QString &directory);
diff --git a/src/plugins/autotest/quick/quicktesttreeitem.cpp b/src/plugins/autotest/quick/quicktesttreeitem.cpp
index 49358f49ebd..a7d6e330ea4 100644
--- a/src/plugins/autotest/quick/quicktesttreeitem.cpp
+++ b/src/plugins/autotest/quick/quicktesttreeitem.cpp
@@ -9,7 +9,9 @@
#include "../itestframework.h"
#include <cppeditor/cppmodelmanager.h>
-#include <projectexplorer/session.h>
+
+#include <projectexplorer/projectmanager.h>
+
#include <utils/qtcassert.h>
using namespace Utils;
@@ -108,7 +110,7 @@ bool QuickTestTreeItem::canProvideDebugConfiguration() const
ITestConfiguration *QuickTestTreeItem::testConfiguration() const
{
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
QTC_ASSERT(project, return nullptr);
QuickTestConfiguration *config = nullptr;
@@ -147,7 +149,7 @@ static QList<ITestConfiguration *> testConfigurationsFor(
const TestTreeItem *rootNode, const std::function<bool(TestTreeItem *)> &predicate)
{
QTC_ASSERT(rootNode, return {});
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
if (!project || rootNode->type() != TestTreeItem::Root)
return {};
@@ -171,7 +173,7 @@ static QList<ITestConfiguration *> testConfigurationsFor(
if (it == configurationForProFiles.end()) {
auto tc = new QuickTestConfiguration(treeItem->framework());
tc->setProjectFile(treeItem->proFile());
- tc->setProject(ProjectExplorer::SessionManager::startupProject());
+ tc->setProject(ProjectExplorer::ProjectManager::startupProject());
tc->setInternalTargets(internalTargets(treeItem->proFile()));
it = configurationForProFiles.insert(treeItem->proFile(), tc);
}
@@ -206,7 +208,7 @@ QList<ITestConfiguration *> QuickTestTreeItem::getAllTestConfigurations() const
{
QList<ITestConfiguration *> result;
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
if (!project || type() != Root)
return result;
@@ -369,7 +371,7 @@ QSet<QString> internalTargets(const FilePath &proFile)
{
QSet<QString> result;
const auto cppMM = CppEditor::CppModelManager::instance();
- const auto projectInfo = cppMM->projectInfo(ProjectExplorer::SessionManager::startupProject());
+ const auto projectInfo = cppMM->projectInfo(ProjectExplorer::ProjectManager::startupProject());
if (!projectInfo)
return {};
for (const CppEditor::ProjectPart::ConstPtr &projectPart : projectInfo->projectParts()) {
diff --git a/src/plugins/autotest/testcodeparser.cpp b/src/plugins/autotest/testcodeparser.cpp
index edf126f8d42..55fc5ec9079 100644
--- a/src/plugins/autotest/testcodeparser.cpp
+++ b/src/plugins/autotest/testcodeparser.cpp
@@ -7,25 +7,21 @@
#include "autotesttr.h"
#include "testtreemodel.h"
-#include <coreplugin/editormanager/editormanager.h>
-#include <coreplugin/progressmanager/futureprogress.h>
#include <coreplugin/progressmanager/progressmanager.h>
+#include <coreplugin/progressmanager/taskprogress.h>
#include <cppeditor/cppeditorconstants.h>
#include <cppeditor/cppmodelmanager.h>
#include <projectexplorer/buildsystem.h>
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
-#include <qmljstools/qmljsmodelmanager.h>
+#include <projectexplorer/projectmanager.h>
#include <utils/algorithm.h>
-#include <utils/mapreduce.h>
+#include <utils/asynctask.h>
#include <utils/qtcassert.h>
-#include <utils/runextensions.h>
-#include <QFuture>
-#include <QFutureInterface>
#include <QLoggingCategory>
+using namespace Core;
using namespace Utils;
namespace Autotest {
@@ -37,7 +33,7 @@ using namespace ProjectExplorer;
static bool isProjectParsing()
{
- const BuildSystem *bs = SessionManager::startupBuildSystem();
+ const BuildSystem *bs = ProjectManager::startupBuildSystem();
return bs && bs->isParsing();
}
@@ -45,25 +41,21 @@ TestCodeParser::TestCodeParser()
: m_threadPool(new QThreadPool(this))
{
// connect to ProgressManager to postpone test parsing when CppModelManager is parsing
- auto progressManager = qobject_cast<Core::ProgressManager *>(Core::ProgressManager::instance());
- connect(progressManager, &Core::ProgressManager::taskStarted,
+ ProgressManager *progressManager = ProgressManager::instance();
+ connect(progressManager, &ProgressManager::taskStarted,
this, &TestCodeParser::onTaskStarted);
- connect(progressManager, &Core::ProgressManager::allTasksFinished,
+ connect(progressManager, &ProgressManager::allTasksFinished,
this, &TestCodeParser::onAllTasksFinished);
- connect(&m_futureWatcher, &QFutureWatcher<TestParseResultPtr>::started,
- this, &TestCodeParser::parsingStarted);
- connect(&m_futureWatcher, &QFutureWatcher<TestParseResultPtr>::finished,
- this, &TestCodeParser::onFinished);
- connect(&m_futureWatcher, &QFutureWatcher<TestParseResultPtr>::resultReadyAt,
- this, [this](int index) {
- emit testParseResultReady(m_futureWatcher.resultAt(index));
- });
connect(this, &TestCodeParser::parsingFinished, this, &TestCodeParser::releaseParserInternals);
m_reparseTimer.setSingleShot(true);
connect(&m_reparseTimer, &QTimer::timeout, this, &TestCodeParser::parsePostponedFiles);
m_threadPool->setMaxThreadCount(std::max(QThread::idealThreadCount()/4, 1));
+ m_threadPool->setThreadPriority(QThread::LowestPriority);
+ m_futureSynchronizer.setCancelOnWait(true);
}
+TestCodeParser::~TestCodeParser() = default;
+
void TestCodeParser::setState(State state)
{
if (m_parserState == Shutdown)
@@ -82,7 +74,7 @@ void TestCodeParser::setState(State state)
}
m_parserState = state;
- if (m_parserState == Idle && SessionManager::startupProject()) {
+ if (m_parserState == Idle && ProjectManager::startupProject()) {
if (m_postponedUpdateType == UpdateType::FullUpdate || m_dirty) {
emitUpdateTestTree();
} else if (m_postponedUpdateType == UpdateType::PartialUpdate) {
@@ -100,7 +92,7 @@ void TestCodeParser::syncTestFrameworks(const QList<ITestParser *> &parsers)
// there's a running parse
m_postponedUpdateType = UpdateType::NoUpdate;
m_postponedFiles.clear();
- Core::ProgressManager::cancelTasks(Constants::TASK_PARSE);
+ ProgressManager::cancelTasks(Constants::TASK_PARSE);
}
qCDebug(LOG) << "Setting" << parsers << "as current parsers";
m_testCodeParsers = parsers;
@@ -139,7 +131,7 @@ void TestCodeParser::updateTestTree(const QSet<ITestParser *> &parsers)
return;
}
- if (!SessionManager::startupProject())
+ if (!ProjectManager::startupProject())
return;
m_postponedUpdateType = UpdateType::NoUpdate;
@@ -158,7 +150,7 @@ void TestCodeParser::onDocumentUpdated(const FilePath &fileName, bool isQmlFile)
if (isProjectParsing() || m_codeModelParsing || m_postponedUpdateType == UpdateType::FullUpdate)
return;
- Project *project = SessionManager::startupProject();
+ Project *project = ProjectManager::startupProject();
if (!project)
return;
// Quick tests: qml files aren't necessarily listed inside project files
@@ -185,7 +177,7 @@ void TestCodeParser::onStartupProjectChanged(Project *project)
{
if (m_parserState == FullParse || m_parserState == PartialParse) {
qCDebug(LOG) << "Canceling scanForTest (startup project changed)";
- Core::ProgressManager::cancelTasks(Constants::TASK_PARSE);
+ ProgressManager::cancelTasks(Constants::TASK_PARSE);
}
emit aboutToPerformFullParse();
if (project)
@@ -194,7 +186,7 @@ void TestCodeParser::onStartupProjectChanged(Project *project)
void TestCodeParser::onProjectPartsUpdated(Project *project)
{
- if (project != SessionManager::startupProject())
+ if (project != ProjectManager::startupProject())
return;
if (isProjectParsing() || m_codeModelParsing)
m_postponedUpdateType = UpdateType::FullUpdate;
@@ -205,12 +197,9 @@ void TestCodeParser::onProjectPartsUpdated(Project *project)
void TestCodeParser::aboutToShutdown()
{
qCDebug(LOG) << "Disabling (immediately) - shutting down";
- State oldState = m_parserState;
m_parserState = Shutdown;
- if (oldState == PartialParse || oldState == FullParse) {
- m_futureWatcher.cancel();
- m_futureWatcher.waitForFinished();
- }
+ m_taskTree.reset();
+ m_futureSynchronizer.waitForFinished();
}
bool TestCodeParser::postponed(const FilePaths &fileList)
@@ -249,7 +238,7 @@ bool TestCodeParser::postponed(const FilePaths &fileList)
m_postponedFiles.clear();
m_postponedUpdateType = UpdateType::FullUpdate;
qCDebug(LOG) << "Canceling scanForTest (full parse triggered while running a scan)";
- Core::ProgressManager::cancelTasks(Constants::TASK_PARSE);
+ ProgressManager::cancelTasks(Constants::TASK_PARSE);
} else {
// partial parse triggered, but full parse is postponed already, ignoring this
if (m_postponedUpdateType == UpdateType::FullUpdate)
@@ -266,14 +255,13 @@ bool TestCodeParser::postponed(const FilePaths &fileList)
QTC_ASSERT(false, return false); // should not happen at all
}
-static void parseFileForTests(const QList<ITestParser *> &parsers,
- QFutureInterface<TestParseResultPtr> &futureInterface,
- const FilePath &fileName)
+static void parseFileForTests(QPromise<TestParseResultPtr> &promise,
+ const QList<ITestParser *> &parsers, const FilePath &fileName)
{
for (ITestParser *parser : parsers) {
- if (futureInterface.isCanceled())
+ if (promise.isCanceled())
return;
- if (parser->processDocument(futureInterface, fileName))
+ if (parser->processDocument(promise, fileName))
break;
}
}
@@ -290,7 +278,7 @@ void TestCodeParser::scanForTests(const FilePaths &fileList, const QList<ITestPa
m_reparseTimerTimedOut = false;
m_postponedFiles.clear();
bool isFullParse = fileList.isEmpty();
- Project *project = SessionManager::startupProject();
+ Project *project = ProjectManager::startupProject();
if (!project)
return;
FilePaths list;
@@ -339,7 +327,7 @@ void TestCodeParser::scanForTests(const FilePaths &fileList, const QList<ITestPa
emit requestRemoval(filePath);
}
- QTC_ASSERT(!(isFullParse && list.isEmpty()), onFinished(); return);
+ QTC_ASSERT(!(isFullParse && list.isEmpty()), onFinished(true); return);
// use only a single parser or all current active?
const QList<ITestParser *> codeParsers = parsers.isEmpty() ? m_testCodeParsers : parsers;
@@ -367,18 +355,34 @@ void TestCodeParser::scanForTests(const FilePaths &fileList, const QList<ITestPa
qCDebug(LOG) << "Starting scan of" << filteredList.size() << "(" << list.size() << ")"
<< "files with" << codeParsers.size() << "parsers";
- QFuture<TestParseResultPtr> future = Utils::map(filteredList,
- [codeParsers](QFutureInterface<TestParseResultPtr> &fi, const FilePath &file) {
- parseFileForTests(codeParsers, fi, file);
- },
- MapReduceOption::Unordered,
- m_threadPool,
- QThread::LowestPriority);
- m_futureWatcher.setFuture(future);
+ using namespace Tasking;
+
+ QList<TaskItem> tasks{parallel}; // TODO: use ParallelLimit(N) and add to settings?
+ for (const FilePath &file : filteredList) {
+ const auto setup = [this, codeParsers, file](AsyncTask<TestParseResultPtr> &async) {
+ async.setConcurrentCallData(parseFileForTests, codeParsers, file);
+ async.setThreadPool(m_threadPool);
+ async.setFutureSynchronizer(&m_futureSynchronizer);
+ };
+ const auto onDone = [this](const AsyncTask<TestParseResultPtr> &async) {
+ const QList<TestParseResultPtr> results = async.results();
+ for (const TestParseResultPtr &result : results)
+ emit testParseResultReady(result);
+ };
+ tasks.append(Async<TestParseResultPtr>(setup, onDone));
+ }
+ m_taskTree.reset(new TaskTree{tasks});
+ const auto onDone = [this] { m_taskTree.release()->deleteLater(); onFinished(true); };
+ const auto onError = [this] { m_taskTree.release()->deleteLater(); onFinished(false); };
+ connect(m_taskTree.get(), &TaskTree::started, this, &TestCodeParser::parsingStarted);
+ connect(m_taskTree.get(), &TaskTree::done, this, onDone);
+ connect(m_taskTree.get(), &TaskTree::errorOccurred, this, onError);
if (filteredList.size() > 5) {
- Core::ProgressManager::addTask(future, Tr::tr("Scanning for Tests"),
- Autotest::Constants::TASK_PARSE);
+ auto progress = new TaskProgress(m_taskTree.get());
+ progress->setDisplayName(Tr::tr("Scanning for Tests"));
+ progress->setId(Constants::TASK_PARSE);
}
+ m_taskTree->start();
}
void TestCodeParser::onTaskStarted(Id type)
@@ -390,7 +394,7 @@ void TestCodeParser::onTaskStarted(Id type)
? UpdateType::FullUpdate : UpdateType::PartialUpdate;
qCDebug(LOG) << "Canceling scan for test (CppModelParsing started)";
m_parsingHasFailed = true;
- Core::ProgressManager::cancelTasks(Constants::TASK_PARSE);
+ ProgressManager::cancelTasks(Constants::TASK_PARSE);
}
}
}
@@ -410,10 +414,9 @@ void TestCodeParser::onAllTasksFinished(Id type)
setState(Idle);
}
-void TestCodeParser::onFinished()
+void TestCodeParser::onFinished(bool success)
{
- if (m_futureWatcher.isCanceled())
- m_parsingHasFailed = true;
+ m_parsingHasFailed = !success;
switch (m_parserState) {
case PartialParse:
qCDebug(LOG) << "setting state to Idle (onFinished, PartialParse)";
diff --git a/src/plugins/autotest/testcodeparser.h b/src/plugins/autotest/testcodeparser.h
index efd0d602e4a..b9b8ef044b6 100644
--- a/src/plugins/autotest/testcodeparser.h
+++ b/src/plugins/autotest/testcodeparser.h
@@ -6,10 +6,10 @@
#include "itestparser.h"
#include <qmljs/qmljsdocument.h>
+
+#include <utils/futuresynchronizer.h>
#include <utils/id.h>
-#include <QFutureWatcher>
-#include <QMap>
#include <QObject>
#include <QTimer>
@@ -18,9 +18,9 @@ class QThreadPool;
QT_END_NAMESPACE
namespace ProjectExplorer { class Project; }
+namespace Utils { class TaskTree; }
namespace Autotest {
-
namespace Internal {
class TestCodeParser : public QObject
@@ -35,6 +35,7 @@ public:
};
TestCodeParser();
+ ~TestCodeParser();
void setState(State state);
State state() const { return m_parserState; }
@@ -48,7 +49,7 @@ public:
signals:
void aboutToPerformFullParse();
- void testParseResultReady(const TestParseResultPtr result);
+ void testParseResultReady(const TestParseResultPtr result); // TODO: pass list of results?
void parsingStarted();
void parsingFinished();
void parsingFailed();
@@ -73,7 +74,7 @@ private:
void onDocumentUpdated(const Utils::FilePath &fileName, bool isQmlFile = false);
void onTaskStarted(Utils::Id type);
void onAllTasksFinished(Utils::Id type);
- void onFinished();
+ void onFinished(bool success);
void onPartialParsingFinished();
void parsePostponedFiles();
void releaseParserInternals();
@@ -83,21 +84,19 @@ private:
bool m_parsingHasFailed = false;
bool m_codeModelParsing = false;
- enum class UpdateType {
- NoUpdate,
- PartialUpdate,
- FullUpdate
- } m_postponedUpdateType = UpdateType::NoUpdate;
+ enum class UpdateType { NoUpdate, PartialUpdate, FullUpdate };
+ UpdateType m_postponedUpdateType = UpdateType::NoUpdate;
bool m_dirty = false;
bool m_singleShotScheduled = false;
bool m_reparseTimerTimedOut = false;
QSet<Utils::FilePath> m_postponedFiles;
State m_parserState = Idle;
- QFutureWatcher<TestParseResultPtr> m_futureWatcher;
QList<ITestParser *> m_testCodeParsers; // ptrs are still owned by TestFrameworkManager
QTimer m_reparseTimer;
QSet<ITestParser *> m_updateParsers;
QThreadPool *m_threadPool = nullptr;
+ Utils::FutureSynchronizer m_futureSynchronizer;
+ std::unique_ptr<Utils::TaskTree> m_taskTree;
};
} // namespace Internal
diff --git a/src/plugins/autotest/testconfiguration.cpp b/src/plugins/autotest/testconfiguration.cpp
index 764ede697d1..c095e9e0f6a 100644
--- a/src/plugins/autotest/testconfiguration.cpp
+++ b/src/plugins/autotest/testconfiguration.cpp
@@ -4,22 +4,19 @@
#include "testconfiguration.h"
#include "itestframework.h"
-#include "testoutputreader.h"
#include "testrunconfiguration.h"
-#include <cppeditor/cppmodelmanager.h>
-#include <cppeditor/projectinfo.h>
-
#include <projectexplorer/buildconfiguration.h>
#include <projectexplorer/buildsystem.h>
#include <projectexplorer/buildtargetinfo.h>
#include <projectexplorer/deploymentdata.h>
-#include <projectexplorer/environmentaspect.h>
#include <projectexplorer/kitinformation.h>
#include <projectexplorer/runconfiguration.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
+#include <utils/algorithm.h>
+
#include <QLoggingCategory>
static Q_LOGGING_CATEGORY(LOG, "qtc.autotest.testconfiguration", QtWarningMsg)
@@ -29,7 +26,6 @@ using namespace Utils;
namespace Autotest {
-
ITestConfiguration::ITestConfiguration(ITestBase *testBase)
: m_testBase(testBase)
{
@@ -94,7 +90,7 @@ static FilePath ensureExeEnding(const FilePath &file)
return file.withExecutableSuffix();
}
-void TestConfiguration::completeTestInformation(ProjectExplorer::RunConfiguration *rc,
+void TestConfiguration::completeTestInformation(RunConfiguration *rc,
TestRunMode runMode)
{
QTC_ASSERT(rc, return);
@@ -104,7 +100,7 @@ void TestConfiguration::completeTestInformation(ProjectExplorer::RunConfiguratio
qCDebug(LOG) << "Executable has been set already - not completing configuration again.";
return;
}
- Project *startupProject = SessionManager::startupProject();
+ Project *startupProject = ProjectManager::startupProject();
if (!startupProject || startupProject != project())
return;
@@ -149,7 +145,7 @@ void TestConfiguration::completeTestInformation(TestRunMode runMode)
}
qCDebug(LOG) << "Failed to complete - using 'normal' way.";
}
- Project *startupProject = SessionManager::startupProject();
+ Project *startupProject = ProjectManager::startupProject();
if (!startupProject || startupProject != project()) {
setProject(nullptr);
return;
diff --git a/src/plugins/autotest/testconfiguration.h b/src/plugins/autotest/testconfiguration.h
index d57416d0164..492509605cf 100644
--- a/src/plugins/autotest/testconfiguration.h
+++ b/src/plugins/autotest/testconfiguration.h
@@ -9,7 +9,6 @@
#include <projectexplorer/runcontrol.h>
#include <utils/environment.h>
-#include <QFutureInterface>
#include <QPointer>
#include <QStringList>
@@ -40,8 +39,7 @@ public:
Utils::FilePath executableFilePath() const;
virtual Utils::FilePath testExecutable() const { return executableFilePath(); };
- virtual TestOutputReader *createOutputReader(const QFutureInterface<TestResult> &fi,
- Utils::QtcProcess *app) const = 0;
+ virtual TestOutputReader *createOutputReader(Utils::QtcProcess *app) const = 0;
virtual Utils::Environment filteredEnvironment(const Utils::Environment &original) const;
ITestBase *testBase() const { return m_testBase; }
diff --git a/src/plugins/autotest/testnavigationwidget.cpp b/src/plugins/autotest/testnavigationwidget.cpp
index 100eed89ed9..95defb21ec4 100644
--- a/src/plugins/autotest/testnavigationwidget.cpp
+++ b/src/plugins/autotest/testnavigationwidget.cpp
@@ -18,9 +18,11 @@
#include <coreplugin/coreconstants.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/find/itemviewfind.h>
+
#include <projectexplorer/buildmanager.h>
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
+
#include <utils/algorithm.h>
#include <utils/link.h>
#include <utils/progressindicator.h>
@@ -87,8 +89,8 @@ TestNavigationWidget::TestNavigationWidget(QWidget *parent) :
connect(m_model, &TestTreeModel::updatedActiveFrameworks, this, [this](int numberOfActive) {
m_missingFrameworksWidget->setVisible(numberOfActive == 0);
});
- ProjectExplorer::SessionManager *sm = ProjectExplorer::SessionManager::instance();
- connect(sm, &ProjectExplorer::SessionManager::startupProjectChanged,
+ ProjectExplorer::ProjectManager *sm = ProjectExplorer::ProjectManager::instance();
+ connect(sm, &ProjectExplorer::ProjectManager::startupProjectChanged,
this, [this](ProjectExplorer::Project * /*project*/) {
m_expandedStateCache.clear();
});
diff --git a/src/plugins/autotest/testoutputreader.cpp b/src/plugins/autotest/testoutputreader.cpp
index 2f1dc75e485..e05fe8fdddb 100644
--- a/src/plugins/autotest/testoutputreader.cpp
+++ b/src/plugins/autotest/testoutputreader.cpp
@@ -4,17 +4,12 @@
#include "testoutputreader.h"
#include "autotesttr.h"
-#include "testresult.h"
-#include "testresultspane.h"
#include "testtreeitem.h"
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
-#include <QDebug>
-#include <QDir>
-#include <QFileInfo>
-#include <QProcess>
+#include <QRegularExpression>
using namespace Utils;
@@ -26,11 +21,8 @@ FilePath TestOutputReader::constructSourceFilePath(const FilePath &path, const Q
return filePath.isReadableFile() ? filePath : FilePath();
}
-TestOutputReader::TestOutputReader(const QFutureInterface<TestResult> &futureInterface,
- QtcProcess *testApplication, const FilePath &buildDirectory)
- : m_futureInterface(futureInterface)
- , m_buildDir(buildDirectory)
- , m_id(testApplication ? testApplication->commandLine().executable().toUserOutput() : QString())
+TestOutputReader::TestOutputReader(QtcProcess *testApplication, const FilePath &buildDirectory)
+ : m_buildDir(buildDirectory)
{
auto chopLineBreak = [](QByteArray line) {
if (line.endsWith('\n'))
@@ -41,6 +33,9 @@ TestOutputReader::TestOutputReader(const QFutureInterface<TestResult> &futureInt
};
if (testApplication) {
+ connect(testApplication, &QtcProcess::started, this, [this, testApplication] {
+ m_id = testApplication->commandLine().executable().toUserOutput();
+ });
testApplication->setStdOutLineCallback([this, &chopLineBreak](const QString &line) {
processStdOutput(chopLineBreak(line.toUtf8()));
});
diff --git a/src/plugins/autotest/testoutputreader.h b/src/plugins/autotest/testoutputreader.h
index 55c645d87c4..7011070b44a 100644
--- a/src/plugins/autotest/testoutputreader.h
+++ b/src/plugins/autotest/testoutputreader.h
@@ -5,9 +5,7 @@
#include "testresult.h"
-#include <QFutureInterface>
#include <QObject>
-#include <QString>
namespace Utils { class QtcProcess; }
@@ -17,8 +15,7 @@ class TestOutputReader : public QObject
{
Q_OBJECT
public:
- TestOutputReader(const QFutureInterface<TestResult> &futureInterface,
- Utils::QtcProcess *testApplication, const Utils::FilePath &buildDirectory);
+ TestOutputReader(Utils::QtcProcess *testApplication, const Utils::FilePath &buildDirectory);
virtual ~TestOutputReader();
void processStdOutput(const QByteArray &outputLine);
virtual void processStdError(const QByteArray &outputLine);
@@ -46,7 +43,6 @@ protected:
void sendAndResetSanitizerResult();
void reportResult(const TestResult &result);
- QFutureInterface<TestResult> m_futureInterface;
Utils::FilePath m_buildDir;
QString m_id;
QHash<ResultType, int> m_summary;
diff --git a/src/plugins/autotest/testprojectsettings.cpp b/src/plugins/autotest/testprojectsettings.cpp
index 862fc8a24f4..5f745637db8 100644
--- a/src/plugins/autotest/testprojectsettings.cpp
+++ b/src/plugins/autotest/testprojectsettings.cpp
@@ -7,7 +7,7 @@
#include "testframeworkmanager.h"
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <utils/algorithm.h>
#include <QLoggingCategory>
diff --git a/src/plugins/autotest/testresultdelegate.h b/src/plugins/autotest/testresultdelegate.h
index c5a4919a71e..f7d6aa1ab27 100644
--- a/src/plugins/autotest/testresultdelegate.h
+++ b/src/plugins/autotest/testresultdelegate.h
@@ -38,7 +38,6 @@ private:
public:
LayoutPositions(QStyleOptionViewItem &options, const TestResultFilterModel *filterModel)
: m_top(options.rect.top()),
- m_bottom(options.rect.bottom()),
m_left(options.rect.left()),
m_right(options.rect.right())
{
@@ -57,20 +56,15 @@ private:
int top() const { return m_top + ITEM_MARGIN; }
int left() const { return m_left + ITEM_MARGIN; }
int right() const { return m_right - ITEM_MARGIN; }
- int bottom() const { return m_bottom; }
int minimumHeight() const { return ICON_SIZE + 2 * ITEM_MARGIN; }
int iconSize() const { return ICON_SIZE; }
- int fontHeight() const { return m_fontHeight; }
int typeAreaLeft() const { return left() + ICON_SIZE + ITEM_SPACING; }
- int typeAreaWidth() const { return m_typeAreaWidth; }
int textAreaLeft() const { return typeAreaLeft() + m_typeAreaWidth + ITEM_SPACING; }
int textAreaWidth() const { return fileAreaLeft() - ITEM_SPACING - textAreaLeft(); }
int fileAreaLeft() const { return lineAreaLeft() - ITEM_SPACING - m_realFileLength; }
int lineAreaLeft() const { return right() - m_maxLineLength; }
- QRect typeArea() const { return QRect(typeAreaLeft(), top(),
- typeAreaWidth(), m_fontHeight); }
QRect textArea() const { return QRect(textAreaLeft(), top(),
textAreaWidth(), m_fontHeight); }
QRect fileArea() const { return QRect(fileAreaLeft(), top(),
@@ -84,7 +78,6 @@ private:
int m_maxLineLength;
int m_realFileLength;
int m_top;
- int m_bottom;
int m_left;
int m_right;
int m_fontHeight;
diff --git a/src/plugins/autotest/testrunner.cpp b/src/plugins/autotest/testrunner.cpp
index cb01d0d9a2a..26ef727fd36 100644
--- a/src/plugins/autotest/testrunner.cpp
+++ b/src/plugins/autotest/testrunner.cpp
@@ -6,18 +6,15 @@
#include "autotestconstants.h"
#include "autotestplugin.h"
#include "autotesttr.h"
-#include "itestframework.h"
#include "testoutputreader.h"
#include "testprojectsettings.h"
#include "testresultspane.h"
#include "testrunconfiguration.h"
-#include "testsettings.h"
#include "testtreeitem.h"
#include "testtreemodel.h"
#include <coreplugin/icore.h>
-#include <coreplugin/progressmanager/futureprogress.h>
-#include <coreplugin/progressmanager/progressmanager.h>
+#include <coreplugin/progressmanager/taskprogress.h>
#include <debugger/debuggerkitinformation.h>
#include <debugger/debuggerruncontrol.h>
@@ -28,8 +25,8 @@
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectexplorersettings.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/runconfiguration.h>
-#include <projectexplorer/session.h>
#include <projectexplorer/target.h>
#include <utils/algorithm.h>
@@ -47,10 +44,9 @@
#include <QLabel>
#include <QLoggingCategory>
#include <QPointer>
-#include <QProcess>
#include <QPushButton>
-#include <QTimer>
+using namespace Core;
using namespace ProjectExplorer;
using namespace Utils;
@@ -72,14 +68,7 @@ TestRunner::TestRunner()
m_cancelTimer.setSingleShot(true);
connect(&m_cancelTimer, &QTimer::timeout, this, [this] { cancelCurrent(Timeout); });
- connect(&m_futureWatcher, &QFutureWatcher<TestResult>::finished,
- this, &TestRunner::onFinished);
- connect(this, &TestRunner::requestStopTestRun,
- &m_futureWatcher, &QFutureWatcher<TestResult>::cancel);
- connect(&m_futureWatcher, &QFutureWatcher<TestResult>::canceled, this, [this] {
- cancelCurrent(UserCanceled);
- reportResult(ResultType::MessageFatal, Tr::tr("Test run canceled by user."));
- });
+ connect(this, &TestRunner::requestStopTestRun, this, [this] { cancelCurrent(UserCanceled); });
connect(BuildManager::instance(), &BuildManager::buildQueueFinished,
this, &TestRunner::onBuildQueueFinished);
}
@@ -93,7 +82,7 @@ TestRunner::~TestRunner()
void TestRunner::runTest(TestRunMode mode, const ITestTreeItem *item)
{
- QTC_ASSERT(!m_executingTests, return);
+ QTC_ASSERT(!isTestRunning(), return);
ITestConfiguration *configuration = item->asConfiguration(mode);
if (configuration)
@@ -144,180 +133,21 @@ static QString constructOmittedVariablesDetailsString(const EnvironmentItems &di
+ '\n' + removedVars.join('\n');
}
-bool TestRunner::currentConfigValid()
-{
- const FilePath commandFilePath = m_currentConfig->testExecutable();
- if (!commandFilePath.isEmpty())
- return true;
-
- reportResult(ResultType::MessageFatal,
- Tr::tr("Executable path is empty. (%1)").arg(m_currentConfig->displayName()));
- delete m_currentConfig;
- m_currentConfig = nullptr;
- if (m_selectedTests.isEmpty()) {
- if (m_fakeFutureInterface)
- m_fakeFutureInterface->reportFinished();
- onFinished();
- } else {
- onProcessDone();
- }
- return false;
-}
-
-void TestRunner::setUpProcessEnv()
-{
- CommandLine command = m_currentProcess->commandLine();
- if (m_currentConfig->testBase()->type() == ITestBase::Framework) {
- TestConfiguration *current = static_cast<TestConfiguration *>(m_currentConfig);
-
- QStringList omitted;
- command.addArgs(current->argumentsForTestRunner(&omitted).join(' '), CommandLine::Raw);
- if (!omitted.isEmpty()) {
- const QString &details = constructOmittedDetailsString(omitted);
- reportResult(ResultType::MessageWarn, details.arg(current->displayName()));
- }
- } else {
- TestToolConfiguration *current = static_cast<TestToolConfiguration *>(m_currentConfig);
- command.setArguments(current->commandLine().arguments());
- }
- m_currentProcess->setCommand(command);
-
- m_currentProcess->setWorkingDirectory(m_currentConfig->workingDirectory());
- const Environment &original = m_currentConfig->environment();
- Environment environment = m_currentConfig->filteredEnvironment(original);
- const EnvironmentItems removedVariables = Utils::filtered(
- original.diff(environment), [](const EnvironmentItem &it) {
- return it.operation == EnvironmentItem::Unset;
- });
- if (!removedVariables.isEmpty()) {
- const QString &details = constructOmittedVariablesDetailsString(removedVariables)
- .arg(m_currentConfig->displayName());
- reportResult(ResultType::MessageWarn, details);
- }
- m_currentProcess->setEnvironment(environment);
-}
-
-void TestRunner::scheduleNext()
-{
- QTC_ASSERT(!m_selectedTests.isEmpty(), onFinished(); return);
- QTC_ASSERT(!m_currentConfig && !m_currentProcess, resetInternalPointers());
- QTC_ASSERT(m_fakeFutureInterface, onFinished(); return);
- QTC_ASSERT(!m_canceled, onFinished(); return);
-
- m_currentConfig = m_selectedTests.takeFirst();
-
- if (!currentConfigValid())
- return;
-
- if (!m_currentConfig->project())
- onProcessDone();
-
- m_currentProcess = new QtcProcess;
- m_currentProcess->setCommand({m_currentConfig->testExecutable(), {}});
-
- QTC_ASSERT(!m_currentOutputReader, delete m_currentOutputReader);
- m_currentOutputReader = m_currentConfig->createOutputReader(*m_fakeFutureInterface, m_currentProcess);
- QTC_ASSERT(m_currentOutputReader, onProcessDone(); return);
- connect(m_currentOutputReader, &TestOutputReader::newResult, this, &TestRunner::testResultReady);
- connect(m_currentOutputReader, &TestOutputReader::newOutputLineAvailable,
- TestResultsPane::instance(), &TestResultsPane::addOutputLine);
-
- setUpProcessEnv();
-
- connect(m_currentProcess, &QtcProcess::done, this, &TestRunner::onProcessDone);
- const int timeout = AutotestPlugin::settings()->timeout;
- m_cancelTimer.setInterval(timeout);
- m_cancelTimer.start();
-
- qCInfo(runnerLog) << "Command:" << m_currentProcess->commandLine().executable();
- qCInfo(runnerLog) << "Arguments:" << m_currentProcess->commandLine().arguments();
- qCInfo(runnerLog) << "Working directory:" << m_currentProcess->workingDirectory();
- qCDebug(runnerLog) << "Environment:" << m_currentProcess->environment().toStringList();
-
- m_currentProcess->start();
-}
-
void TestRunner::cancelCurrent(TestRunner::CancelReason reason)
{
- m_canceled = true;
-
- if (m_fakeFutureInterface)
- m_fakeFutureInterface->reportCanceled();
-
if (reason == KitChanged)
reportResult(ResultType::MessageWarn, Tr::tr("Current kit has changed. Canceling test run."));
else if (reason == Timeout)
reportResult(ResultType::MessageFatal, Tr::tr("Test case canceled due to timeout.\nMaybe raise the timeout?"));
-
- // if user or timeout cancels the current run ensure to kill the running process
- if (m_currentProcess && m_currentProcess->state() != QProcess::NotRunning) {
- m_currentProcess->kill();
- m_currentProcess->waitForFinished();
- }
-}
-
-void TestRunner::onProcessDone()
-{
- if (m_currentProcess->result() == ProcessResult::StartFailed) {
- reportResult(ResultType::MessageFatal,
- Tr::tr("Failed to start test for project \"%1\".").arg(m_currentConfig->displayName())
- + processInformation(m_currentProcess) + rcInfo(m_currentConfig));
- }
-
- if (m_executingTests && m_currentConfig) {
- QTC_CHECK(m_fakeFutureInterface);
- m_fakeFutureInterface->setProgressValue(m_fakeFutureInterface->progressValue()
- + m_currentConfig->testCaseCount());
- if (m_currentProcess && !m_fakeFutureInterface->isCanceled()) {
- if (m_currentProcess->exitStatus() == QProcess::CrashExit) {
- if (m_currentOutputReader)
- m_currentOutputReader->reportCrash();
- reportResult(ResultType::MessageFatal,
- Tr::tr("Test for project \"%1\" crashed.").arg(m_currentConfig->displayName())
- + processInformation(m_currentProcess) + rcInfo(m_currentConfig));
- } else if (m_currentOutputReader && !m_currentOutputReader->hadValidOutput()) {
- reportResult(ResultType::MessageFatal,
- Tr::tr("Test for project \"%1\" did not produce any expected output.")
- .arg(m_currentConfig->displayName()) + processInformation(m_currentProcess)
- + rcInfo(m_currentConfig));
- }
- }
- }
- if (m_currentOutputReader) {
- const int disabled = m_currentOutputReader->disabledTests();
- if (disabled > 0)
- emit hadDisabledTests(disabled);
- if (m_currentOutputReader->hasSummary())
- emit reportSummary(m_currentOutputReader->id(), m_currentOutputReader->summary());
-
- m_currentOutputReader->resetCommandlineColor();
- }
- resetInternalPointers();
-
- if (!m_fakeFutureInterface) {
- QTC_ASSERT(!m_executingTests, m_executingTests = false);
- return;
- }
- if (!m_selectedTests.isEmpty() && !m_fakeFutureInterface->isCanceled())
- scheduleNext();
- else
- m_fakeFutureInterface->reportFinished();
-}
-
-void TestRunner::resetInternalPointers()
-{
- delete m_currentOutputReader;
- if (m_currentProcess)
- m_currentProcess->deleteLater();
- delete m_currentConfig;
- m_currentOutputReader = nullptr;
- m_currentProcess = nullptr;
- m_currentConfig = nullptr;
+ else if (reason == UserCanceled)
+ reportResult(ResultType::MessageFatal, Tr::tr("Test run canceled by user."));
+ m_taskTree.reset();
+ onFinished();
}
void TestRunner::runTests(TestRunMode mode, const QList<ITestConfiguration *> &selectedTests)
{
- QTC_ASSERT(!m_executingTests, return);
+ QTC_ASSERT(!isTestRunning(), return);
qDeleteAll(m_selectedTests);
m_selectedTests = selectedTests;
@@ -332,8 +162,6 @@ void TestRunner::runTests(TestRunMode mode, const QList<ITestConfiguration *> &s
return;
}
- m_executingTests = true;
- m_canceled = false;
emit testRunStarted();
// clear old log and output pane
@@ -385,7 +213,7 @@ static QString firstNonEmptyTestCaseTarget(const TestConfiguration *config)
static RunConfiguration *getRunConfiguration(const QString &buildTargetKey)
{
- const Project *project = SessionManager::startupProject();
+ const Project *project = ProjectManager::startupProject();
if (!project)
return nullptr;
const Target *target = project->activeTarget();
@@ -411,7 +239,7 @@ static RunConfiguration *getRunConfiguration(const QString &buildTargetKey)
if (runConfigurations.size() == 1)
return runConfigurations.first();
- RunConfigurationSelectionDialog dialog(buildTargetKey, Core::ICore::dialogParent());
+ RunConfigurationSelectionDialog dialog(buildTargetKey, ICore::dialogParent());
if (dialog.exec() == QDialog::Accepted) {
const QString dName = dialog.displayName();
if (dName.isEmpty())
@@ -467,7 +295,7 @@ int TestRunner::precheckTestConfigurations()
void TestRunner::onBuildSystemUpdated()
{
- Target *target = SessionManager::startupTarget();
+ Target *target = ProjectManager::startupTarget();
if (QTC_GUARD(target))
disconnect(target, &Target::buildSystemUpdated, this, &TestRunner::onBuildSystemUpdated);
if (!m_skipTargetsCheck) {
@@ -482,7 +310,7 @@ void TestRunner::runTestsHelper()
bool projectChanged = false;
for (ITestConfiguration *itc : std::as_const(m_selectedTests)) {
if (itc->testBase()->type() == ITestBase::Tool) {
- if (itc->project() != SessionManager::startupProject()) {
+ if (itc->project() != ProjectManager::startupProject()) {
projectChanged = true;
toBeRemoved.append(itc);
}
@@ -513,19 +341,136 @@ void TestRunner::runTestsHelper()
return;
}
- int testCaseCount = precheckTestConfigurations();
+ const int testCaseCount = precheckTestConfigurations();
+ Q_UNUSED(testCaseCount) // TODO: may be useful for fine-grained progress reporting, when fixed
+
+ struct TestStorage {
+ std::unique_ptr<TestOutputReader> m_outputReader;
+ };
+
+ using namespace Tasking;
+ QList<TaskItem> tasks{optional};
+
+ for (ITestConfiguration *config : m_selectedTests) {
+ QTC_ASSERT(config, continue);
+ const TreeStorage<TestStorage> storage;
+
+ const auto onGroupSetup = [this, config] {
+ if (!config->project())
+ return TaskAction::StopWithDone;
+ if (config->testExecutable().isEmpty()) {
+ reportResult(ResultType::MessageFatal,
+ Tr::tr("Executable path is empty. (%1)").arg(config->displayName()));
+ return TaskAction::StopWithDone;
+ }
+ return TaskAction::Continue;
+ };
+ const auto onSetup = [this, config, storage](QtcProcess &process) {
+ TestStorage *testStorage = storage.activeStorage();
+ QTC_ASSERT(testStorage, return);
+ testStorage->m_outputReader.reset(config->createOutputReader(&process));
+ QTC_ASSERT(testStorage->m_outputReader, return);
+ connect(testStorage->m_outputReader.get(), &TestOutputReader::newResult,
+ this, &TestRunner::testResultReady);
+ connect(testStorage->m_outputReader.get(), &TestOutputReader::newOutputLineAvailable,
+ TestResultsPane::instance(), &TestResultsPane::addOutputLine);
+
+ CommandLine command{config->testExecutable(), {}};
+ if (config->testBase()->type() == ITestBase::Framework) {
+ TestConfiguration *current = static_cast<TestConfiguration *>(config);
+ QStringList omitted;
+ command.addArgs(current->argumentsForTestRunner(&omitted).join(' '), CommandLine::Raw);
+ if (!omitted.isEmpty()) {
+ const QString &details = constructOmittedDetailsString(omitted);
+ reportResult(ResultType::MessageWarn, details.arg(current->displayName()));
+ }
+ } else {
+ TestToolConfiguration *current = static_cast<TestToolConfiguration *>(config);
+ command.setArguments(current->commandLine().arguments());
+ }
+ process.setCommand(command);
+
+ process.setWorkingDirectory(config->workingDirectory());
+ const Environment &original = config->environment();
+ Environment environment = config->filteredEnvironment(original);
+ const EnvironmentItems removedVariables = Utils::filtered(
+ original.diff(environment), [](const EnvironmentItem &it) {
+ return it.operation == EnvironmentItem::Unset;
+ });
+ if (!removedVariables.isEmpty()) {
+ const QString &details = constructOmittedVariablesDetailsString(removedVariables)
+ .arg(config->displayName());
+ reportResult(ResultType::MessageWarn, details);
+ }
+ process.setEnvironment(environment);
+
+ m_cancelTimer.setInterval(AutotestPlugin::settings()->timeout);
+ m_cancelTimer.start();
+
+ qCInfo(runnerLog) << "Command:" << process.commandLine().executable();
+ qCInfo(runnerLog) << "Arguments:" << process.commandLine().arguments();
+ qCInfo(runnerLog) << "Working directory:" << process.workingDirectory();
+ qCDebug(runnerLog) << "Environment:" << process.environment().toStringList();
+ };
+ const auto onDone = [this, config, storage](const QtcProcess &process) {
+ TestStorage *testStorage = storage.activeStorage();
+ QTC_ASSERT(testStorage, return);
+ if (process.result() == ProcessResult::StartFailed) {
+ reportResult(ResultType::MessageFatal,
+ Tr::tr("Failed to start test for project \"%1\".").arg(config->displayName())
+ + processInformation(&process) + rcInfo(config));
+ }
- // Fake future interface - destruction will be handled by QFuture/QFutureWatcher
- m_fakeFutureInterface = new QFutureInterface<TestResult>(QFutureInterfaceBase::Running);
- QFuture<TestResult> future = m_fakeFutureInterface->future();
- m_fakeFutureInterface->setProgressRange(0, testCaseCount);
- m_fakeFutureInterface->setProgressValue(0);
- m_futureWatcher.setFuture(future);
+ if (process.exitStatus() == QProcess::CrashExit) {
+ if (testStorage->m_outputReader)
+ testStorage->m_outputReader->reportCrash();
+ reportResult(ResultType::MessageFatal,
+ Tr::tr("Test for project \"%1\" crashed.").arg(config->displayName())
+ + processInformation(&process) + rcInfo(config));
+ } else if (testStorage->m_outputReader && !testStorage->m_outputReader->hadValidOutput()) {
+ reportResult(ResultType::MessageFatal,
+ Tr::tr("Test for project \"%1\" did not produce any expected output.")
+ .arg(config->displayName()) + processInformation(&process)
+ + rcInfo(config));
+ }
+ if (testStorage->m_outputReader) {
+ const int disabled = testStorage->m_outputReader->disabledTests();
+ if (disabled > 0)
+ emit hadDisabledTests(disabled);
+ if (testStorage->m_outputReader->hasSummary())
+ emit reportSummary(testStorage->m_outputReader->id(), testStorage->m_outputReader->summary());
+
+ testStorage->m_outputReader->resetCommandlineColor();
+ }
+ };
+ const Group group {
+ optional,
+ Storage(storage),
+ OnGroupSetup(onGroupSetup),
+ Process(onSetup, onDone, onDone)
+ };
+ tasks.append(group);
+ }
+
+ m_taskTree.reset(new TaskTree(tasks));
+ connect(m_taskTree.get(), &TaskTree::done, this, &TestRunner::onFinished);
+ connect(m_taskTree.get(), &TaskTree::errorOccurred, this, &TestRunner::onFinished);
+
+ auto progress = new TaskProgress(m_taskTree.get());
+ progress->setDisplayName(tr("Running Tests"));
+ progress->setAutoStopOnCancel(false);
+ progress->setHalfLifeTimePerTask(10000); // 10 seconds
+ connect(progress, &TaskProgress::canceled, this, [this, progress] {
+ // progress was a child of task tree which is going to be deleted directly. Unwind properly.
+ progress->setParent(nullptr);
+ progress->deleteLater();
+ cancelCurrent(UserCanceled);
+ });
- Core::ProgressManager::addTask(future, Tr::tr("Running Tests"), Autotest::Constants::TASK_INDEX);
if (AutotestPlugin::settings()->popupOnStart)
AutotestPlugin::popupResultsPane();
- scheduleNext();
+
+ m_taskTree->start();
}
static void processOutput(TestOutputReader *outputreader, const QString &msg, OutputFormat format)
@@ -626,13 +571,8 @@ void TestRunner::debugTests()
}
}
- // We need a fake QFuture for the results. TODO: replace with QtConcurrent::run
- QFutureInterface<TestResult> *futureInterface
- = new QFutureInterface<TestResult>(QFutureInterfaceBase::Running);
- m_futureWatcher.setFuture(futureInterface->future());
-
if (useOutputProcessor) {
- TestOutputReader *outputreader = config->createOutputReader(*futureInterface, nullptr);
+ TestOutputReader *outputreader = config->createOutputReader(nullptr);
connect(outputreader, &TestOutputReader::newResult, this, &TestRunner::testResultReady);
outputreader->setId(inferior.command.executable().toString());
connect(outputreader, &TestOutputReader::newOutputLineAvailable,
@@ -641,9 +581,7 @@ void TestRunner::debugTests()
this, [outputreader](const QString &msg, OutputFormat format) {
processOutput(outputreader, msg, format);
});
-
- connect(runControl, &RunControl::stopped,
- outputreader, &QObject::deleteLater);
+ connect(runControl, &RunControl::stopped, outputreader, &QObject::deleteLater);
}
m_stopDebugConnect = connect(this, &TestRunner::requestStopTestRun,
@@ -658,7 +596,7 @@ void TestRunner::debugTests()
static bool executablesEmpty()
{
- Target *target = SessionManager::startupTarget();
+ Target *target = ProjectManager::startupTarget();
const QList<RunConfiguration *> configs = target->runConfigurations();
QTC_ASSERT(!configs.isEmpty(), return false);
if (auto execAspect = configs.first()->aspect<ExecutableAspect>())
@@ -671,7 +609,7 @@ void TestRunner::runOrDebugTests()
if (!m_skipTargetsCheck) {
if (executablesEmpty()) {
m_skipTargetsCheck = true;
- Target * target = SessionManager::startupTarget();
+ Target *target = ProjectManager::startupTarget();
QTimer::singleShot(5000, this, [this, target = QPointer<Target>(target)] {
if (target) {
disconnect(target, &Target::buildSystemUpdated,
@@ -706,8 +644,7 @@ void TestRunner::buildProject(Project *project)
BuildManager *buildManager = BuildManager::instance();
m_buildConnect = connect(this, &TestRunner::requestStopTestRun,
buildManager, &BuildManager::cancel);
- connect(buildManager, &BuildManager::buildQueueFinished,
- this, &TestRunner::buildFinished);
+ connect(buildManager, &BuildManager::buildQueueFinished, this, &TestRunner::buildFinished);
BuildManager::buildProjectWithDependencies(project);
if (!BuildManager::isBuilding())
buildFinished(false);
@@ -717,23 +654,19 @@ void TestRunner::buildFinished(bool success)
{
disconnect(m_buildConnect);
BuildManager *buildManager = BuildManager::instance();
- disconnect(buildManager, &BuildManager::buildQueueFinished,
- this, &TestRunner::buildFinished);
+ disconnect(buildManager, &BuildManager::buildQueueFinished, this, &TestRunner::buildFinished);
if (success) {
- if (!m_canceled)
- runOrDebugTests();
- else if (m_executingTests)
- onFinished();
- } else {
- reportResult(ResultType::MessageFatal, Tr::tr("Build failed. Canceling test run."));
- onFinished();
+ runOrDebugTests();
+ return;
}
+ reportResult(ResultType::MessageFatal, Tr::tr("Build failed. Canceling test run."));
+ onFinished();
}
static RunAfterBuildMode runAfterBuild()
{
- Project *project = SessionManager::startupProject();
+ Project *project = ProjectManager::startupProject();
if (!project)
return RunAfterBuildMode::None;
@@ -747,7 +680,7 @@ static RunAfterBuildMode runAfterBuild()
void TestRunner::onBuildQueueFinished(bool success)
{
- if (m_executingTests || !m_selectedTests.isEmpty()) // paranoia!
+ if (isTestRunning() || !m_selectedTests.isEmpty()) // paranoia!
return;
if (!success || m_runMode != TestRunMode::None)
@@ -768,17 +701,15 @@ void TestRunner::onBuildQueueFinished(bool success)
void TestRunner::onFinished()
{
- m_cancelTimer.stop();
- // if we've been canceled and we still have test configurations queued just throw them away
- qDeleteAll(m_selectedTests);
- m_selectedTests.clear();
-
+ if (m_taskTree)
+ m_taskTree.release()->deleteLater();
disconnect(m_stopDebugConnect);
disconnect(m_finishDebugConnect);
disconnect(m_targetConnect);
- m_fakeFutureInterface = nullptr;
+ qDeleteAll(m_selectedTests);
+ m_selectedTests.clear();
+ m_cancelTimer.stop();
m_runMode = TestRunMode::None;
- m_executingTests = false;
emit testRunFinished();
}
@@ -855,7 +786,7 @@ void RunConfigurationSelectionDialog::populate()
{
m_rcCombo->addItem({}, QStringList{{}, {}, {}}); // empty default
- if (auto project = SessionManager::startupProject()) {
+ if (auto project = ProjectManager::startupProject()) {
if (auto target = project->activeTarget()) {
for (RunConfiguration *rc : target->runConfigurations()) {
auto runnable = rc->runnable();
diff --git a/src/plugins/autotest/testrunner.h b/src/plugins/autotest/testrunner.h
index c96af56075f..f361d7297a8 100644
--- a/src/plugins/autotest/testrunner.h
+++ b/src/plugins/autotest/testrunner.h
@@ -4,12 +4,11 @@
#pragma once
#include "autotest_global.h"
-#include "testresult.h"
+
+#include "autotestconstants.h"
#include <QDialog>
-#include <QFutureWatcher>
#include <QList>
-#include <QObject>
#include <QTimer>
QT_BEGIN_NAMESPACE
@@ -20,13 +19,14 @@ class QLabel;
QT_END_NAMESPACE
namespace ProjectExplorer { class Project; }
-namespace Utils { class QtcProcess; }
+namespace Utils { class TaskTree; }
namespace Autotest {
-enum class TestRunMode;
class ITestConfiguration;
-class TestOutputReader;
+class ITestTreeItem;
+class TestResult;
+enum class ResultType;
namespace Internal {
@@ -44,7 +44,7 @@ public:
void runTests(TestRunMode mode, const QList<ITestConfiguration *> &selectedTests);
void runTest(TestRunMode mode, const ITestTreeItem *item);
- bool isTestRunning() const { return m_executingTests; }
+ bool isTestRunning() const { return m_buildConnect || m_stopDebugConnect || m_taskTree.get(); }
signals:
void testRunStarted();
@@ -61,12 +61,7 @@ private:
void onFinished();
int precheckTestConfigurations();
- bool currentConfigValid();
- void setUpProcessEnv();
- void scheduleNext();
void cancelCurrent(CancelReason reason);
- void onProcessDone();
- void resetInternalPointers();
void runTestsHelper();
void debugTests();
@@ -75,14 +70,9 @@ private:
bool postponeTestRunWithEmptyExecutable(ProjectExplorer::Project *project);
void onBuildSystemUpdated();
- QFutureWatcher<TestResult> m_futureWatcher;
- QFutureInterface<TestResult> *m_fakeFutureInterface = nullptr;
+ std::unique_ptr<Utils::TaskTree> m_taskTree;
+
QList<ITestConfiguration *> m_selectedTests;
- bool m_executingTests = false;
- bool m_canceled = false;
- ITestConfiguration *m_currentConfig = nullptr;
- Utils::QtcProcess *m_currentProcess = nullptr;
- TestOutputReader *m_currentOutputReader = nullptr;
TestRunMode m_runMode = TestRunMode::None;
// temporarily used if building before running is necessary
diff --git a/src/plugins/autotest/testtreemodel.cpp b/src/plugins/autotest/testtreemodel.cpp
index 8842734f0c5..4e3d010ad45 100644
--- a/src/plugins/autotest/testtreemodel.cpp
+++ b/src/plugins/autotest/testtreemodel.cpp
@@ -10,12 +10,16 @@
#include "testprojectsettings.h"
#include <cppeditor/cppmodelmanager.h>
+
#include <projectexplorer/buildsystem.h>
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
+
#include <qmljs/qmljsmodelmanagerinterface.h>
+
#include <texteditor/texteditor.h>
+
#include <utils/algorithm.h>
#include <utils/fileutils.h>
#include <utils/qtcassert.h>
@@ -38,7 +42,7 @@ TestTreeModel::TestTreeModel(TestCodeParser *parser) :
connect(m_parser, &TestCodeParser::aboutToPerformFullParse, this,
&TestTreeModel::removeAllTestItems, Qt::QueuedConnection);
connect(m_parser, &TestCodeParser::testParseResultReady,
- this, &TestTreeModel::onParseResultReady, Qt::QueuedConnection);
+ this, &TestTreeModel::onParseResultReady);
connect(m_parser, &TestCodeParser::parsingFinished,
this, &TestTreeModel::sweep, Qt::QueuedConnection);
connect(m_parser, &TestCodeParser::parsingFailed,
@@ -70,8 +74,8 @@ void TestTreeModel::setupParsingConnections()
m_parser->setDirty();
m_parser->setState(TestCodeParser::Idle);
- SessionManager *sm = SessionManager::instance();
- connect(sm, &SessionManager::startupProjectChanged, this, [this, sm](Project *project) {
+ ProjectManager *sm = ProjectManager::instance();
+ connect(sm, &ProjectManager::startupProjectChanged, this, [this, sm](Project *project) {
synchronizeTestFrameworks(); // we might have project settings
m_parser->onStartupProjectChanged(project);
removeAllTestToolItems();
@@ -226,7 +230,7 @@ static QList<ITestTreeItem *> testItemsByName(TestTreeItem *root, const QString
void TestTreeModel::onTargetChanged(Target *target)
{
if (target && target->buildSystem()) {
- const Target *topLevelTarget = SessionManager::startupProject()->targets().first();
+ const Target *topLevelTarget = ProjectManager::startupProject()->targets().first();
connect(topLevelTarget->buildSystem(), &BuildSystem::testInformationUpdated,
this, &TestTreeModel::onBuildSystemTestsUpdated, Qt::UniqueConnection);
disconnect(target->project(), &Project::activeTargetChanged,
@@ -236,7 +240,7 @@ void TestTreeModel::onTargetChanged(Target *target)
void TestTreeModel::onBuildSystemTestsUpdated()
{
- const BuildSystem *bs = SessionManager::startupBuildSystem();
+ const BuildSystem *bs = ProjectManager::startupBuildSystem();
if (!bs || !bs->project())
return;
@@ -333,7 +337,7 @@ void TestTreeModel::synchronizeTestFrameworks()
void TestTreeModel::synchronizeTestTools()
{
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
TestTools tools;
if (!project || AutotestPlugin::projectSettings(project)->useGlobalSettings()) {
tools = Utils::filtered(TestFrameworkManager::registeredTestTools(),
diff --git a/src/plugins/autotoolsprojectmanager/autotoolsbuildsystem.cpp b/src/plugins/autotoolsprojectmanager/autotoolsbuildsystem.cpp
index 0375ce9ac61..113212be5fb 100644
--- a/src/plugins/autotoolsprojectmanager/autotoolsbuildsystem.cpp
+++ b/src/plugins/autotoolsprojectmanager/autotoolsbuildsystem.cpp
@@ -14,6 +14,7 @@
#include <utils/qtcassert.h>
using namespace ProjectExplorer;
+using namespace Utils;
namespace AutotoolsProjectManager::Internal {
@@ -140,7 +141,7 @@ void AutotoolsBuildSystem::updateCppCodeModel()
if (cxxflags.isEmpty())
cxxflags = cflags;
- const QString includeFileBaseDir = projectDirectory().toString();
+ const FilePath includeFileBaseDir = projectDirectory();
rpp.setFlagsForC({kitInfo.cToolChain, cflags, includeFileBaseDir});
rpp.setFlagsForCxx({kitInfo.cxxToolChain, cxxflags, includeFileBaseDir});
diff --git a/src/plugins/autotoolsprojectmanager/makefileparser.cpp b/src/plugins/autotoolsprojectmanager/makefileparser.cpp
index 09fbd666479..9bbccdb86ad 100644
--- a/src/plugins/autotoolsprojectmanager/makefileparser.cpp
+++ b/src/plugins/autotoolsprojectmanager/makefileparser.cpp
@@ -13,6 +13,8 @@
#include <QFile>
#include <QFileInfo>
+using namespace Utils;
+
namespace AutotoolsProjectManager::Internal {
MakefileParser::MakefileParser(const QString &makefile) : m_makefile(makefile)
@@ -423,7 +425,7 @@ QStringList MakefileParser::parseTermsAfterAssign(const QString &line)
if (assignPos <= 0 || assignPos >= line.size())
return QStringList();
- const QStringList parts = Utils::ProcessArgs::splitArgs(line.mid(assignPos));
+ const QStringList parts = ProcessArgs::splitArgs(line.mid(assignPos), HostOsInfo::hostOs());
QStringList result;
for (int i = 0; i < parts.count(); ++i) {
const QString cur = parts.at(i);
diff --git a/src/plugins/baremetal/baremetaldebugsupport.cpp b/src/plugins/baremetal/baremetaldebugsupport.cpp
index db0c376a5ad..c236ab32f24 100644
--- a/src/plugins/baremetal/baremetaldebugsupport.cpp
+++ b/src/plugins/baremetal/baremetaldebugsupport.cpp
@@ -11,6 +11,7 @@
#include "idebugserverprovider.h"
#include <debugger/debuggerkitinformation.h>
+#include <debugger/debuggerruncontrol.h>
#include <projectexplorer/buildconfiguration.h>
#include <projectexplorer/buildsteplist.h>
diff --git a/src/plugins/baremetal/baremetaldebugsupport.h b/src/plugins/baremetal/baremetaldebugsupport.h
index f0addb4729b..ca16c1ab7ec 100644
--- a/src/plugins/baremetal/baremetaldebugsupport.h
+++ b/src/plugins/baremetal/baremetaldebugsupport.h
@@ -3,12 +3,11 @@
#pragma once
-#include <debugger/debuggerruncontrol.h>
+#include <projectexplorer/runcontrol.h>
namespace BareMetal::Internal {
-class BareMetalDebugSupportFactory final
- : public ProjectExplorer::RunWorkerFactory
+class BareMetalDebugSupportFactory final : public ProjectExplorer::RunWorkerFactory
{
public:
BareMetalDebugSupportFactory();
diff --git a/src/plugins/baremetal/baremetaldevice.cpp b/src/plugins/baremetal/baremetaldevice.cpp
index 4c8d7ea8c66..d7754ebc9a0 100644
--- a/src/plugins/baremetal/baremetaldevice.cpp
+++ b/src/plugins/baremetal/baremetaldevice.cpp
@@ -24,7 +24,6 @@ const char debugServerProviderIdKeyC[] = "IDebugServerProviderId";
BareMetalDevice::BareMetalDevice()
{
setDisplayType(Tr::tr("Bare Metal"));
- setDefaultDisplayName(defaultDisplayName());
setOsType(Utils::OsTypeOther);
}
diff --git a/src/plugins/baremetal/debugservers/gdb/gdbserverprovider.cpp b/src/plugins/baremetal/debugservers/gdb/gdbserverprovider.cpp
index 336b606d798..222d22ade98 100644
--- a/src/plugins/baremetal/debugservers/gdb/gdbserverprovider.cpp
+++ b/src/plugins/baremetal/debugservers/gdb/gdbserverprovider.cpp
@@ -8,6 +8,8 @@
#include <baremetal/baremetaltr.h>
#include <baremetal/debugserverprovidermanager.h>
+#include <debugger/debuggerruncontrol.h>
+
#include <projectexplorer/runconfigurationaspects.h>
#include <utils/environment.h>
diff --git a/src/plugins/baremetal/debugservers/gdb/openocdgdbserverprovider.cpp b/src/plugins/baremetal/debugservers/gdb/openocdgdbserverprovider.cpp
index 334f31132cb..03933cff50e 100644
--- a/src/plugins/baremetal/debugservers/gdb/openocdgdbserverprovider.cpp
+++ b/src/plugins/baremetal/debugservers/gdb/openocdgdbserverprovider.cpp
@@ -64,7 +64,7 @@ QString OpenOcdGdbServerProvider::channelString() const
// otherwise running will be stuck.
CommandLine cmd = command();
QStringList args = {"|", cmd.executable().toString()};
- for (const QString &a : ProcessArgs::splitArgs(cmd.arguments())) {
+ for (const QString &a : ProcessArgs::splitArgs(cmd.arguments(), HostOsInfo::hostOs())) {
if (a.startsWith('\"') && a.endsWith('\"'))
args << a;
else
diff --git a/src/plugins/baremetal/debugservers/uvsc/uvproject.cpp b/src/plugins/baremetal/debugservers/uvsc/uvproject.cpp
index 773ec410666..faa32fea086 100644
--- a/src/plugins/baremetal/debugservers/uvsc/uvproject.cpp
+++ b/src/plugins/baremetal/debugservers/uvsc/uvproject.cpp
@@ -9,7 +9,7 @@
#include <debugger/debuggerkitinformation.h>
#include <debugger/debuggerruncontrol.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <QFileInfo>
diff --git a/src/plugins/baremetal/debugservers/uvsc/uvscserverprovider.cpp b/src/plugins/baremetal/debugservers/uvsc/uvscserverprovider.cpp
index b95c333600d..590c1158866 100644
--- a/src/plugins/baremetal/debugservers/uvsc/uvscserverprovider.cpp
+++ b/src/plugins/baremetal/debugservers/uvsc/uvscserverprovider.cpp
@@ -14,6 +14,7 @@
#include <baremetal/debugserverprovidermanager.h>
#include <debugger/debuggerkitinformation.h>
+#include <debugger/debuggerruncontrol.h>
#include <projectexplorer/project.h>
#include <projectexplorer/runconfigurationaspects.h>
diff --git a/src/plugins/bookmarks/bookmarkfilter.cpp b/src/plugins/bookmarks/bookmarkfilter.cpp
index 7cc9bf2a759..d659885a548 100644
--- a/src/plugins/bookmarks/bookmarkfilter.cpp
+++ b/src/plugins/bookmarks/bookmarkfilter.cpp
@@ -110,10 +110,8 @@ void BookmarkFilter::accept(const LocatorFilterEntry &selection, QString *newTex
Q_UNUSED(newText)
Q_UNUSED(selectionStart)
Q_UNUSED(selectionLength)
- if (const Bookmark *bookmark = m_manager->bookmarkForIndex(
- selection.internalData.toModelIndex())) {
+ if (Bookmark *bookmark = m_manager->bookmarkForIndex(selection.internalData.toModelIndex()))
m_manager->gotoBookmark(bookmark);
- }
}
} // Bookmarks::Internal
diff --git a/src/plugins/bookmarks/bookmarkmanager.cpp b/src/plugins/bookmarks/bookmarkmanager.cpp
index e7c5a4e388e..4e98648147a 100644
--- a/src/plugins/bookmarks/bookmarkmanager.cpp
+++ b/src/plugins/bookmarks/bookmarkmanager.cpp
@@ -12,8 +12,10 @@
#include <coreplugin/idocument.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/command.h>
+
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/session.h>
+
#include <texteditor/texteditor.h>
#include <utils/algorithm.h>
#include <utils/icon.h>
@@ -29,7 +31,6 @@
#include <QDialog>
#include <QDialogButtonBox>
#include <QDir>
-#include <QFileInfo>
#include <QFormLayout>
#include <QLineEdit>
#include <QMenu>
diff --git a/src/plugins/boot2qt/qdbdeployconfigurationfactory.cpp b/src/plugins/boot2qt/qdbdeployconfigurationfactory.cpp
index f1156857948..3abb700df1d 100644
--- a/src/plugins/boot2qt/qdbdeployconfigurationfactory.cpp
+++ b/src/plugins/boot2qt/qdbdeployconfigurationfactory.cpp
@@ -7,7 +7,7 @@
#include "qdbtr.h"
#include <projectexplorer/deploymentdataview.h>
-#include "projectexplorer/devicesupport/idevice.h"
+#include <projectexplorer/devicesupport/idevice.h>
#include <projectexplorer/kitinformation.h>
#include <projectexplorer/project.h>
#include <projectexplorer/target.h>
diff --git a/src/plugins/boot2qt/qdbdevice.cpp b/src/plugins/boot2qt/qdbdevice.cpp
index 90568f42641..c2c25b65372 100644
--- a/src/plugins/boot2qt/qdbdevice.cpp
+++ b/src/plugins/boot2qt/qdbdevice.cpp
@@ -30,11 +30,10 @@ using namespace Utils;
namespace Qdb::Internal {
-class QdbProcessImpl : public LinuxProcessInterface
+class QdbProcessImpl : public SshProcessInterface
{
public:
- QdbProcessImpl(const LinuxDevice *linuxDevice)
- : LinuxProcessInterface(linuxDevice) {}
+ QdbProcessImpl(const IDevice::ConstPtr &device) : SshProcessInterface(device) {}
~QdbProcessImpl() { killIfRunning(); }
private:
@@ -105,6 +104,7 @@ private:
QdbDevice::QdbDevice()
{
setDisplayType(Tr::tr("Boot2Qt Device"));
+ setType(Constants::QdbLinuxOsType);
addDeviceAction({Tr::tr("Reboot Device"), [](const IDevice::Ptr &device, QWidget *) {
(void) new DeviceApplicationObserver(device, {device->filePath("reboot"), {}});
@@ -124,7 +124,7 @@ ProjectExplorer::IDeviceWidget *QdbDevice::createWidget()
ProcessInterface *QdbDevice::createProcessInterface() const
{
- return new QdbProcessImpl(this);
+ return new QdbProcessImpl(sharedFromThis());
}
void QdbDevice::setSerialNumber(const QString &serial)
@@ -248,6 +248,7 @@ QdbLinuxDeviceFactory::QdbLinuxDeviceFactory()
{
setDisplayName(Tr::tr("Boot2Qt Device"));
setCombinedIcon(":/qdb/images/qdbdevicesmall.png", ":/qdb/images/qdbdevice.png");
+ setQuickCreationAllowed(true);
setConstructionFunction(&QdbDevice::create);
setCreator([] {
QdbDeviceWizard wizard(Core::ICore::dialogParent());
diff --git a/src/plugins/boot2qt/qdbmakedefaultappstep.cpp b/src/plugins/boot2qt/qdbmakedefaultappstep.cpp
index 70c1c40acba..ffc1c4c487f 100644
--- a/src/plugins/boot2qt/qdbmakedefaultappstep.cpp
+++ b/src/plugins/boot2qt/qdbmakedefaultappstep.cpp
@@ -22,16 +22,24 @@ using namespace Utils::Tasking;
namespace Qdb::Internal {
-// QdbMakeDefaultAppService
-
-class QdbMakeDefaultAppService : public RemoteLinux::AbstractRemoteLinuxDeployService
+class QdbMakeDefaultAppStep final : public RemoteLinux::AbstractRemoteLinuxDeployStep
{
public:
- void setMakeDefault(bool makeDefault) { m_makeDefault = makeDefault; }
+ QdbMakeDefaultAppStep(BuildStepList *bsl, Id id)
+ : AbstractRemoteLinuxDeployStep(bsl, id)
+ {
+ auto selection = addAspect<SelectionAspect>();
+ selection->setSettingsKey("QdbMakeDefaultDeployStep.MakeDefault");
+ selection->addOption(Tr::tr("Set this application to start by default"));
+ selection->addOption(Tr::tr("Reset default application"));
-private:
- bool isDeploymentNecessary() const final { return true; }
+ setInternalInitializer([this, selection] {
+ m_makeDefault = selection->value() == 0;
+ return isDeploymentPossible();
+ });
+ }
+private:
Group deployRecipe() final
{
const auto setupHandler = [this](QtcProcess &process) {
@@ -48,17 +56,17 @@ private:
process.setCommand(cmd);
QtcProcess *proc = &process;
connect(proc, &QtcProcess::readyReadStandardError, this, [this, proc] {
- emit stdErrData(proc->readAllStandardError());
+ handleStdErrData(proc->readAllStandardError());
});
};
const auto doneHandler = [this](const QtcProcess &) {
if (m_makeDefault)
- emit progressMessage(Tr::tr("Application set as the default one."));
+ addProgressMessage(Tr::tr("Application set as the default one."));
else
- emit progressMessage(Tr::tr("Reset the default application."));
+ addProgressMessage(Tr::tr("Reset the default application."));
};
const auto errorHandler = [this](const QtcProcess &process) {
- emit errorMessage(Tr::tr("Remote process failed: %1").arg(process.errorString()));
+ addErrorMessage(Tr::tr("Remote process failed: %1").arg(process.errorString()));
};
return Group { Process(setupHandler, doneHandler, errorHandler) };
}
@@ -66,30 +74,6 @@ private:
bool m_makeDefault = true;
};
-// QdbMakeDefaultAppStep
-
-class QdbMakeDefaultAppStep final : public RemoteLinux::AbstractRemoteLinuxDeployStep
-{
-public:
- QdbMakeDefaultAppStep(BuildStepList *bsl, Id id)
- : AbstractRemoteLinuxDeployStep(bsl, id)
- {
- auto service = new QdbMakeDefaultAppService;
- setDeployService(service);
-
- auto selection = addAspect<SelectionAspect>();
- selection->setSettingsKey("QdbMakeDefaultDeployStep.MakeDefault");
- selection->addOption(Tr::tr("Set this application to start by default"));
- selection->addOption(Tr::tr("Reset default application"));
-
- setInternalInitializer([service, selection] {
- service->setMakeDefault(selection->value() == 0);
- return service->isDeploymentPossible();
- });
- }
-};
-
-
// QdbMakeDefaultAppStepFactory
QdbMakeDefaultAppStepFactory::QdbMakeDefaultAppStepFactory()
diff --git a/src/plugins/boot2qt/qdbplugin.cpp b/src/plugins/boot2qt/qdbplugin.cpp
index a73646b6c82..0d995ee0402 100644
--- a/src/plugins/boot2qt/qdbplugin.cpp
+++ b/src/plugins/boot2qt/qdbplugin.cpp
@@ -100,16 +100,14 @@ void registerFlashAction(QObject *parentForAction)
toolsContainer->addAction(flashCommand, flashActionId);
}
-template <class Step>
-class QdbDeployStepFactory : public ProjectExplorer::BuildStepFactory
+template <class Factory>
+class QdbDeployStepFactory : public Factory
{
public:
- explicit QdbDeployStepFactory(Id id)
+ QdbDeployStepFactory()
{
- registerStep<Step>(id);
- setDisplayName(Step::displayName());
- setSupportedConfiguration(Constants::QdbDeployConfigurationId);
- setSupportedStepList(ProjectExplorer::Constants::BUILDSTEPS_DEPLOY);
+ Factory::setSupportedConfiguration(Constants::QdbDeployConfigurationId);
+ Factory::setSupportedStepList(ProjectExplorer::Constants::BUILDSTEPS_DEPLOY);
}
};
@@ -125,12 +123,9 @@ public:
QdbStopApplicationStepFactory m_stopApplicationStepFactory;
QdbMakeDefaultAppStepFactory m_makeDefaultAppStepFactory;
- QdbDeployStepFactory<RemoteLinux::GenericDirectUploadStep>
- m_directUploadStepFactory{RemoteLinux::Constants::DirectUploadStepId};
- QdbDeployStepFactory<RemoteLinux::RsyncDeployStep>
- m_rsyncDeployStepFactory{RemoteLinux::Constants::RsyncDeployStepId};
- QdbDeployStepFactory<RemoteLinux::MakeInstallStep>
- m_makeInstallStepFactory{RemoteLinux::Constants::MakeInstallStepId};
+ QdbDeployStepFactory<RemoteLinux::GenericDirectUploadStepFactory> m_directUploadStepFactory;
+ QdbDeployStepFactory<RemoteLinux::RsyncDeployStepFactory> m_rsyncDeployStepFactory;
+ QdbDeployStepFactory<RemoteLinux::MakeInstallStepFactory> m_makeInstallStepFactory;
const QList<Id> supportedRunConfigs {
m_runConfigFactory.runConfigurationId(),
diff --git a/src/plugins/boot2qt/qdbstopapplicationstep.cpp b/src/plugins/boot2qt/qdbstopapplicationstep.cpp
index 60d891beafb..ac67385a7be 100644
--- a/src/plugins/boot2qt/qdbstopapplicationstep.cpp
+++ b/src/plugins/boot2qt/qdbstopapplicationstep.cpp
@@ -21,21 +21,28 @@ using namespace Utils::Tasking;
namespace Qdb::Internal {
-// QdbStopApplicationService
+// QdbStopApplicationStep
-class QdbStopApplicationService : public RemoteLinux::AbstractRemoteLinuxDeployService
+class QdbStopApplicationStep final : public RemoteLinux::AbstractRemoteLinuxDeployStep
{
-private:
- bool isDeploymentNecessary() const final { return true; }
+public:
+ QdbStopApplicationStep(BuildStepList *bsl, Id id)
+ : AbstractRemoteLinuxDeployStep(bsl, id)
+ {
+ setWidgetExpandedByDefault(false);
+
+ setInternalInitializer([this] { return isDeploymentPossible(); });
+ }
+
Group deployRecipe() final;
};
-Group QdbStopApplicationService::deployRecipe()
+Group QdbStopApplicationStep::deployRecipe()
{
const auto setupHandler = [this](QtcProcess &process) {
const auto device = DeviceKitAspect::device(target()->kit());
if (!device) {
- emit errorMessage(Tr::tr("No device to stop the application on."));
+ addErrorMessage(Tr::tr("No device to stop the application on."));
return TaskAction::StopWithError;
}
QTC_CHECK(device);
@@ -43,48 +50,30 @@ Group QdbStopApplicationService::deployRecipe()
process.setWorkingDirectory("/usr/bin");
QtcProcess *proc = &process;
connect(proc, &QtcProcess::readyReadStandardOutput, this, [this, proc] {
- emit stdOutData(proc->readAllStandardOutput());
+ handleStdOutData(proc->readAllStandardOutput());
});
return TaskAction::Continue;
};
const auto doneHandler = [this](const QtcProcess &) {
- emit progressMessage(Tr::tr("Stopped the running application."));
+ addProgressMessage(Tr::tr("Stopped the running application."));
};
const auto errorHandler = [this](const QtcProcess &process) {
const QString errorOutput = process.cleanedStdErr();
const QString failureMessage = Tr::tr("Could not check and possibly stop running application.");
if (process.exitStatus() == QProcess::CrashExit) {
- emit errorMessage(failureMessage);
+ addErrorMessage(failureMessage);
} else if (process.result() != ProcessResult::FinishedWithSuccess) {
- emit stdErrData(process.errorString());
+ handleStdErrData(process.errorString());
} else if (errorOutput.contains("Could not connect: Connection refused")) {
- emit progressMessage(Tr::tr("Checked that there is no running application."));
+ addProgressMessage(Tr::tr("Checked that there is no running application."));
} else if (!errorOutput.isEmpty()) {
- emit stdErrData(errorOutput);
- emit errorMessage(failureMessage);
+ handleStdErrData(errorOutput);
+ addErrorMessage(failureMessage);
}
};
return Group { Process(setupHandler, doneHandler, errorHandler) };
}
-// QdbStopApplicationStep
-
-class QdbStopApplicationStep final : public RemoteLinux::AbstractRemoteLinuxDeployStep
-{
-public:
- QdbStopApplicationStep(BuildStepList *bsl, Id id)
- : AbstractRemoteLinuxDeployStep(bsl, id)
- {
- auto service = new QdbStopApplicationService;
- setDeployService(service);
-
- setWidgetExpandedByDefault(false);
-
- setInternalInitializer([service] { return service->isDeploymentPossible(); });
- }
-};
-
-
// QdbStopApplicationStepFactory
QdbStopApplicationStepFactory::QdbStopApplicationStepFactory()
diff --git a/src/plugins/clangcodemodel/CMakeLists.txt b/src/plugins/clangcodemodel/CMakeLists.txt
index 50007413e15..dbfba36ee06 100644
--- a/src/plugins/clangcodemodel/CMakeLists.txt
+++ b/src/plugins/clangcodemodel/CMakeLists.txt
@@ -50,7 +50,6 @@ extend_qtc_plugin(ClangCodeModel
CONDITION WITH_TESTS
SOURCES
test/activationsequenceprocessortest.cpp test/activationsequenceprocessortest.h
- test/clangbatchfileprocessor.cpp test/clangbatchfileprocessor.h
test/clangdtests.cpp test/clangdtests.h
test/clangfixittest.cpp test/clangfixittest.h
test/data/clangtestdata.qrc
diff --git a/src/plugins/clangcodemodel/clangcodemodel.qbs b/src/plugins/clangcodemodel/clangcodemodel.qbs
index 45a6abc347d..903a81147a9 100644
--- a/src/plugins/clangcodemodel/clangcodemodel.qbs
+++ b/src/plugins/clangcodemodel/clangcodemodel.qbs
@@ -94,15 +94,11 @@ QtcPlugin {
}
}
- Group {
- name: "Tests"
- condition: qtc.testsEnabled
+ QtcTestFiles {
prefix: "test/"
files: [
"activationsequenceprocessortest.cpp",
"activationsequenceprocessortest.h",
- "clangbatchfileprocessor.cpp",
- "clangbatchfileprocessor.h",
"clangdtests.cpp",
"clangdtests.h",
"clangfixittest.cpp",
diff --git a/src/plugins/clangcodemodel/clangcodemodelplugin.cpp b/src/plugins/clangcodemodel/clangcodemodelplugin.cpp
index 2a3772afb26..c4f083592d1 100644
--- a/src/plugins/clangcodemodel/clangcodemodelplugin.cpp
+++ b/src/plugins/clangcodemodel/clangcodemodelplugin.cpp
@@ -10,7 +10,6 @@
#ifdef WITH_TESTS
# include "test/activationsequenceprocessortest.h"
-# include "test/clangbatchfileprocessor.h"
# include "test/clangdtests.h"
# include "test/clangfixittest.h"
#endif
@@ -28,15 +27,16 @@
#include <projectexplorer/projectpanelfactory.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectexplorerconstants.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <projectexplorer/taskhub.h>
#include <texteditor/textmark.h>
+#include <utils/asynctask.h>
#include <utils/environment.h>
#include <utils/qtcassert.h>
-#include <utils/runextensions.h>
#include <utils/temporarydirectory.h>
using namespace Core;
@@ -49,7 +49,7 @@ void ClangCodeModelPlugin::generateCompilationDB()
{
using namespace CppEditor;
- Target *target = SessionManager::startupTarget();
+ Target *target = ProjectManager::startupTarget();
if (!target)
return;
@@ -61,7 +61,7 @@ void ClangCodeModelPlugin::generateCompilationDB()
baseDir = TemporaryDirectory::masterDirectoryFilePath();
QFuture<GenerateCompilationDbResult> task
- = Utils::runAsync(&Internal::generateCompilationDB, ProjectInfoList{projectInfo},
+ = Utils::asyncRun(&Internal::generateCompilationDB, ProjectInfoList{projectInfo},
baseDir, CompilationDbPurpose::Project,
warningsConfigForProject(target->project()),
globalClangOptions(),
@@ -78,15 +78,8 @@ ClangCodeModelPlugin::~ClangCodeModelPlugin()
void ClangCodeModelPlugin::initialize()
{
TaskHub::addCategory(Constants::TASK_CATEGORY_DIAGNOSTICS, Tr::tr("Clang Code Model"));
-
- connect(ProjectExplorerPlugin::instance(),
- &ProjectExplorerPlugin::finishedInitialization,
- this,
- &ClangCodeModelPlugin::maybeHandleBatchFileAndExit);
-
CppEditor::CppModelManager::instance()->activateClangCodeModel(
- std::make_unique<ClangModelManagerSupport>());
-
+ std::make_unique<ClangModelManagerSupport>());
createCompilationDBAction();
#ifdef WITH_TESTS
@@ -109,7 +102,7 @@ void ClangCodeModelPlugin::createCompilationDBAction()
Tr::tr("Generate Compilation Database"),
Tr::tr("Generate Compilation Database for \"%1\""),
ParameterAction::AlwaysEnabled, this);
- Project *startupProject = SessionManager::startupProject();
+ Project *startupProject = ProjectManager::startupProject();
if (startupProject)
m_generateCompilationDBAction->setParameter(startupProject->displayName());
Command *command = ActionManager::registerAction(m_generateCompilationDBAction,
@@ -136,7 +129,7 @@ void ClangCodeModelPlugin::createCompilationDBAction()
"Generator is already running.");
return;
}
- Project * const project = SessionManager::startupProject();
+ Project * const project = ProjectManager::startupProject();
if (!project) {
MessageManager::writeDisrupting("Cannot generate compilation database: "
"No active project.");
@@ -154,21 +147,21 @@ void ClangCodeModelPlugin::createCompilationDBAction()
});
connect(CppEditor::CppModelManager::instance(), &CppEditor::CppModelManager::projectPartsUpdated,
this, [this](Project *project) {
- if (project != SessionManager::startupProject())
+ if (project != ProjectManager::startupProject())
return;
m_generateCompilationDBAction->setParameter(project->displayName());
});
- connect(SessionManager::instance(), &SessionManager::startupProjectChanged,
+ connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged,
this, [this](Project *project) {
m_generateCompilationDBAction->setParameter(project ? project->displayName() : "");
});
- connect(SessionManager::instance(), &SessionManager::projectDisplayNameChanged,
+ connect(ProjectManager::instance(), &ProjectManager::projectDisplayNameChanged,
this, [this](Project *project) {
- if (project != SessionManager::startupProject())
+ if (project != ProjectManager::startupProject())
return;
m_generateCompilationDBAction->setParameter(project->displayName());
});
- connect(SessionManager::instance(), &SessionManager::projectAdded,
+ connect(ProjectManager::instance(), &ProjectManager::projectAdded,
this, [this](Project *project) {
project->registerGenerator(Constants::GENERATE_COMPILATION_DB,
m_generateCompilationDBAction->text(),
@@ -176,16 +169,4 @@ void ClangCodeModelPlugin::createCompilationDBAction()
});
}
-// For e.g. creation of profile-guided optimization builds.
-void ClangCodeModelPlugin::maybeHandleBatchFileAndExit() const
-{
-#ifdef WITH_TESTS
- const QString batchFilePath = qtcEnvironmentVariable("QTC_CLANG_BATCH");
- if (!batchFilePath.isEmpty() && QTC_GUARD(QFileInfo::exists(batchFilePath))) {
- const bool runSucceeded = runClangBatchFile(batchFilePath);
- QCoreApplication::exit(!runSucceeded);
- }
-#endif
-}
-
-} // ClangCodeModel::Internal
+} // namespace ClangCodeModel::Internal
diff --git a/src/plugins/clangcodemodel/clangcodemodelplugin.h b/src/plugins/clangcodemodel/clangcodemodelplugin.h
index 664cf113aa0..d0456257f2d 100644
--- a/src/plugins/clangcodemodel/clangcodemodelplugin.h
+++ b/src/plugins/clangcodemodel/clangcodemodelplugin.h
@@ -23,8 +23,6 @@ public:
void initialize() override;
private:
- void maybeHandleBatchFileAndExit() const;
-
void generateCompilationDB();
void createCompilationDBAction();
diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp
index 1ae8adc616c..91a850f1b18 100644
--- a/src/plugins/clangcodemodel/clangdclient.cpp
+++ b/src/plugins/clangcodemodel/clangdclient.cpp
@@ -9,12 +9,10 @@
#include "clangdcompletion.h"
#include "clangdfindreferences.h"
#include "clangdfollowsymbol.h"
-#include "clangdlocatorfilters.h"
#include "clangdmemoryusagewidget.h"
#include "clangdquickfixes.h"
#include "clangdsemantichighlighting.h"
#include "clangdswitchdecldef.h"
-#include "clangmodelmanagersupport.h"
#include "clangtextmark.h"
#include "clangutils.h"
#include "tasktimers.h"
@@ -38,6 +36,7 @@
#include <languageclient/languageclienthoverhandler.h>
#include <languageclient/languageclientinterface.h>
#include <languageclient/languageclientmanager.h>
+#include <languageclient/languageclientoutline.h>
#include <languageclient/languageclientsymbolsupport.h>
#include <languageclient/languageclientutils.h>
#include <languageclient/progressmanager.h>
@@ -48,7 +47,7 @@
#include <projectexplorer/kitinformation.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projecttree.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <projectexplorer/taskhub.h>
#include <texteditor/codeassist/assistinterface.h>
@@ -57,10 +56,11 @@
#include <texteditor/codeassist/textdocumentmanipulatorinterface.h>
#include <texteditor/texteditor.h>
#include <utils/algorithm.h>
+#include <utils/asynctask.h>
#include <utils/environment.h>
#include <utils/fileutils.h>
#include <utils/itemviews.h>
-#include <utils/runextensions.h>
+#include <utils/theme/theme.h>
#include <utils/utilsicons.h>
#include <QAction>
@@ -128,6 +128,27 @@ public:
: Request("textDocument/symbolInfo", params) {}
};
+class ClangdOutlineItem : public LanguageClientOutlineItem
+{
+ using LanguageClientOutlineItem::LanguageClientOutlineItem;
+private:
+ QVariant data(int column, int role) const override
+ {
+ switch (role) {
+ case Qt::DisplayRole:
+ return ClangdClient::displayNameFromDocumentSymbol(
+ static_cast<SymbolKind>(type()), name(), detail());
+ case Qt::ForegroundRole:
+ if ((detail().endsWith("class") || detail().endsWith("struct"))
+ && range().end() == selectionRange().end()) {
+ return creatorTheme()->color(Theme::TextColorDisabled);
+ }
+ break;
+ }
+ return LanguageClientOutlineItem::data(column, role);
+ }
+};
+
void setupClangdConfigFile()
{
const Utils::FilePath targetConfigFile = CppEditor::ClangdSettings::clangdUserConfigFilePath();
@@ -427,7 +448,6 @@ ClangdClient::ClangdClient(Project *project, const Utils::FilePath &jsonDbDir, c
});
setCurrentProject(project);
setDocumentChangeUpdateThreshold(d->settings.documentUpdateThreshold);
- setSymbolStringifier(displayNameFromDocumentSymbol);
setSemanticTokensHandler([this](TextDocument *doc, const QList<ExpandedSemanticToken> &tokens,
int version, bool force) {
d->handleSemanticTokens(doc, tokens, version, force);
@@ -450,12 +470,7 @@ ClangdClient::ClangdClient(Project *project, const Utils::FilePath &jsonDbDir, c
}
});
- connect(this, &Client::initialized, this, [this] {
- auto currentDocumentFilter = static_cast<ClangdCurrentDocumentFilter *>(
- CppEditor::CppModelManager::instance()->currentDocumentFilter());
- currentDocumentFilter->updateCurrentClient();
- d->openedExtraFiles.clear();
- });
+ connect(this, &Client::initialized, this, [this] { d->openedExtraFiles.clear(); });
start();
}
@@ -661,6 +676,12 @@ DiagnosticManager *ClangdClient::createDiagnosticManager()
return diagnosticManager;
}
+LanguageClientOutlineItem *ClangdClient::createOutlineItem(
+ const LanguageServerProtocol::DocumentSymbol &symbol)
+{
+ return new ClangdOutlineItem(this, symbol);
+}
+
bool ClangdClient::referencesShadowFile(const TextEditor::TextDocument *doc,
const Utils::FilePath &candidate)
{
@@ -673,7 +694,7 @@ bool ClangdClient::fileBelongsToProject(const Utils::FilePath &filePath) const
{
if (CppEditor::ClangdSettings::instance().granularity()
== CppEditor::ClangdSettings::Granularity::Session) {
- return SessionManager::projectForFile(filePath);
+ return ProjectManager::projectForFile(filePath);
}
return Client::fileBelongsToProject(filePath);
}
@@ -1441,7 +1462,7 @@ void ClangdClient::Private::handleSemanticTokens(TextDocument *doc,
clangdVersion = q->versionNumber(),
this] {
try {
- return Utils::runAsync(doSemanticHighlighting, filePath, tokens, text, ast, doc,
+ return Utils::asyncRun(doSemanticHighlighting, filePath, tokens, text, ast, doc,
rev, clangdVersion, highlightingTimer);
} catch (const std::exception &e) {
qWarning() << "caught" << e.what() << "in main highlighting thread";
diff --git a/src/plugins/clangcodemodel/clangdclient.h b/src/plugins/clangcodemodel/clangdclient.h
index 772920b2776..3c9e603b843 100644
--- a/src/plugins/clangcodemodel/clangdclient.h
+++ b/src/plugins/clangcodemodel/clangdclient.h
@@ -137,6 +137,8 @@ private:
const CustomInspectorTabs createCustomInspectorTabs() override;
TextEditor::RefactoringChangesData *createRefactoringChangesBackend() const override;
LanguageClient::DiagnosticManager *createDiagnosticManager() override;
+ LanguageClient::LanguageClientOutlineItem *createOutlineItem(
+ const LanguageServerProtocol::DocumentSymbol &symbol) override;
bool referencesShadowFile(const TextEditor::TextDocument *doc,
const Utils::FilePath &candidate) override;
bool fileBelongsToProject(const Utils::FilePath &filePath) const override;
diff --git a/src/plugins/clangcodemodel/clangdfindreferences.cpp b/src/plugins/clangcodemodel/clangdfindreferences.cpp
index 8a2d3faaeff..2130587a2d5 100644
--- a/src/plugins/clangcodemodel/clangdfindreferences.cpp
+++ b/src/plugins/clangcodemodel/clangdfindreferences.cpp
@@ -21,9 +21,10 @@
#include <languageserverprotocol/lsptypes.h>
#include <projectexplorer/projectexplorer.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/projectnodes.h>
#include <projectexplorer/projecttree.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <texteditor/basefilefind.h>
@@ -451,7 +452,7 @@ void ClangdFindReferences::Private::addSearchResultsForFile(const FilePath &file
item.setContainingFunctionName(getContainingFunction(astPath, range).detail());
if (search->supportsReplace()) {
- const bool fileInSession = SessionManager::projectForFile(file);
+ const bool fileInSession = ProjectManager::projectForFile(file);
item.setSelectForReplacement(fileInSession);
if (fileInSession && file.baseName().compare(replacementData->oldSymbolName,
Qt::CaseInsensitive) == 0) {
diff --git a/src/plugins/clangcodemodel/clangdlocatorfilters.cpp b/src/plugins/clangcodemodel/clangdlocatorfilters.cpp
index 725d961591d..76a19f684f2 100644
--- a/src/plugins/clangcodemodel/clangdlocatorfilters.cpp
+++ b/src/plugins/clangcodemodel/clangdlocatorfilters.cpp
@@ -6,21 +6,25 @@
#include "clangdclient.h"
#include "clangmodelmanagersupport.h"
+#include <coreplugin/editormanager/editormanager.h>
#include <cppeditor/cppeditorconstants.h>
#include <cppeditor/cppeditortr.h>
#include <cppeditor/cpplocatorfilter.h>
#include <cppeditor/cppmodelmanager.h>
#include <cppeditor/indexitem.h>
+#include <languageclient/languageclientmanager.h>
#include <languageclient/languageclientutils.h>
#include <languageclient/locatorfilter.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <utils/link.h>
+#include <utils/algorithm.h>
-#include <set>
-#include <tuple>
+#include <QHash>
+using namespace Core;
using namespace LanguageClient;
using namespace LanguageServerProtocol;
+using namespace ProjectExplorer;
using namespace Utils;
namespace ClangCodeModel {
@@ -28,6 +32,17 @@ namespace Internal {
const int MaxResultCount = 10000;
+static QList<Client *> clientsForOpenProjects()
+{
+ QSet<Client *> clients;
+ const QList<Project *> projects = ProjectManager::projects();
+ for (Project *project : projects) {
+ if (Client *client = ClangModelManagerSupport::clientForProject(project))
+ clients << client;
+ }
+ return clients.values();
+}
+
class CppLocatorFilter : public CppEditor::CppLocatorFilter
{
public:
@@ -54,6 +69,10 @@ public:
setHidden(true);
setMaxResultCount(MaxResultCount);
}
+ void prepareSearch(const QString &entry) override
+ {
+ prepareSearchForClients(entry, clientsForOpenProjects());
+ }
};
@@ -82,6 +101,10 @@ public:
setHidden(true);
setMaxResultCount(MaxResultCount);
}
+ void prepareSearch(const QString &entry) override
+ {
+ prepareSearchForClients(entry, clientsForOpenProjects());
+ }
};
class CppFunctionsFilter : public CppEditor::CppFunctionsFilter
@@ -110,6 +133,10 @@ public:
setHidden(true);
setMaxResultCount(MaxResultCount);
}
+ void prepareSearch(const QString &entry) override
+ {
+ prepareSearchForClients(entry, clientsForOpenProjects());
+ }
};
@@ -119,7 +146,7 @@ ClangGlobalSymbolFilter::ClangGlobalSymbolFilter()
}
ClangGlobalSymbolFilter::ClangGlobalSymbolFilter(ILocatorFilter *cppFilter,
- WorkspaceLocatorFilter *lspFilter)
+ ILocatorFilter *lspFilter)
: m_cppFilter(cppFilter), m_lspFilter(lspFilter)
{
setId(CppEditor::Constants::LOCATOR_FILTER_ID);
@@ -137,51 +164,15 @@ ClangGlobalSymbolFilter::~ClangGlobalSymbolFilter()
void ClangGlobalSymbolFilter::prepareSearch(const QString &entry)
{
m_cppFilter->prepareSearch(entry);
- QList<Client *> clients;
- for (ProjectExplorer::Project * const project : ProjectExplorer::SessionManager::projects()) {
- if (Client * const client = ClangModelManagerSupport::clientForProject(project))
- clients << client;
- }
- if (!clients.isEmpty())
- m_lspFilter->prepareSearch(entry, clients);
-}
-
-QList<Core::LocatorFilterEntry> ClangGlobalSymbolFilter::matchesFor(
- QFutureInterface<Core::LocatorFilterEntry> &future, const QString &entry)
-{
- QList<Core::LocatorFilterEntry> matches = m_cppFilter->matchesFor(future, entry);
- const QList<Core::LocatorFilterEntry> lspMatches = m_lspFilter->matchesFor(future, entry);
- if (!lspMatches.isEmpty()) {
- std::set<std::tuple<FilePath, int, int>> locations;
- for (const auto &entry : std::as_const(matches)) {
- const CppEditor::IndexItem::Ptr item
- = qvariant_cast<CppEditor::IndexItem::Ptr>(entry.internalData);
- locations.insert(std::make_tuple(item->filePath(), item->line(), item->column()));
- }
- for (const auto &entry : lspMatches) {
- if (!entry.internalData.canConvert<Link>())
- continue;
- const auto link = qvariant_cast<Link>(entry.internalData);
- if (locations.find(std::make_tuple(link.targetFilePath, link.targetLine,
- link.targetColumn)) == locations.cend()) {
- matches << entry; // TODO: Insert sorted?
- }
- }
- }
-
- return matches;
+ m_lspFilter->prepareSearch(entry);
}
-void ClangGlobalSymbolFilter::accept(const Core::LocatorFilterEntry &selection, QString *newText,
- int *selectionStart, int *selectionLength) const
+QList<LocatorFilterEntry> ClangGlobalSymbolFilter::matchesFor(
+ QFutureInterface<LocatorFilterEntry> &future, const QString &entry)
{
- if (qvariant_cast<CppEditor::IndexItem::Ptr>(selection.internalData))
- m_cppFilter->accept(selection, newText, selectionStart, selectionLength);
- else
- m_lspFilter->accept(selection, newText, selectionStart, selectionLength);
+ return m_cppFilter->matchesFor(future, entry) + m_lspFilter->matchesFor(future, entry);
}
-
ClangClassesFilter::ClangClassesFilter()
: ClangGlobalSymbolFilter(new CppClassesFilter, new LspClassesFilter)
{
@@ -204,6 +195,7 @@ class LspCurrentDocumentFilter : public DocumentLocatorFilter
{
public:
LspCurrentDocumentFilter()
+ : DocumentLocatorFilter(LanguageClientManager::instance())
{
setId({});
setDisplayName({});
@@ -214,16 +206,23 @@ public:
}
private:
- Core::LocatorFilterEntry generateLocatorEntry(const DocumentSymbol &info,
- const Core::LocatorFilterEntry &parent) override
+ void prepareSearch(const QString &entry) override
+ {
+ DocumentLocatorFilter::prepareSearch(entry);
+ m_content = TextEditor::TextDocument::currentTextDocument()->plainText();
+ }
+
+ LocatorFilterEntry generateLocatorEntry(const DocumentSymbol &info,
+ const LocatorFilterEntry &parent) override
{
- Core::LocatorFilterEntry entry;
+ LocatorFilterEntry entry;
entry.filter = this;
entry.displayName = ClangdClient::displayNameFromDocumentSymbol(
static_cast<SymbolKind>(info.kind()), info.name(),
info.detail().value_or(QString()));
- const Position &pos = info.range().start();
- entry.internalData = QVariant::fromValue(LineColumn(pos.line(), pos.character()));
+ entry.internalData = QVariant::fromValue(info);
+ const Position pos = info.range().start();
+ entry.linkForEditor = {m_currentFilePath, pos.line() + 1, pos.character()};
entry.extraInfo = parent.extraInfo;
if (!entry.extraInfo.isEmpty())
entry.extraInfo.append("::");
@@ -234,6 +233,60 @@ private:
return entry;
}
+
+ // Filter out declarations for which a definition is also present.
+ QList<LocatorFilterEntry> matchesFor(QFutureInterface<LocatorFilterEntry> &future,
+ const QString &entry) override
+ {
+ QList<LocatorFilterEntry> allMatches
+ = DocumentLocatorFilter::matchesFor(future, entry);
+ QHash<QString, QList<LocatorFilterEntry>> possibleDuplicates;
+ for (const LocatorFilterEntry &e : std::as_const(allMatches))
+ possibleDuplicates[e.displayName + e.extraInfo] << e;
+ const QTextDocument doc(m_content);
+ for (auto it = possibleDuplicates.cbegin(); it != possibleDuplicates.cend(); ++it) {
+ const QList<LocatorFilterEntry> &duplicates = it.value();
+ if (duplicates.size() == 1)
+ continue;
+ QList<LocatorFilterEntry> declarations;
+ QList<LocatorFilterEntry> definitions;
+ for (const LocatorFilterEntry &candidate : duplicates) {
+ const auto symbol = qvariant_cast<DocumentSymbol>(candidate.internalData);
+ const SymbolKind kind = static_cast<SymbolKind>(symbol.kind());
+ if (kind != SymbolKind::Class && kind != SymbolKind::Function)
+ break;
+ const Range range = symbol.range();
+ const Range selectionRange = symbol.selectionRange();
+ if (kind == SymbolKind::Class) {
+ if (range.end() == selectionRange.end())
+ declarations << candidate;
+ else
+ definitions << candidate;
+ continue;
+ }
+ const int startPos = selectionRange.end().toPositionInDocument(&doc);
+ const int endPos = range.end().toPositionInDocument(&doc);
+ const QString functionBody = m_content.mid(startPos, endPos - startPos);
+
+ // Hacky, but I don't see anything better.
+ if (functionBody.contains('{') && functionBody.contains('}'))
+ definitions << candidate;
+ else
+ declarations << candidate;
+ }
+ if (definitions.size() == 1
+ && declarations.size() + definitions.size() == duplicates.size()) {
+ for (const LocatorFilterEntry &decl : std::as_const(declarations)) {
+ Utils::erase(allMatches, [&decl](const LocatorFilterEntry &e) {
+ return e.internalData == decl.internalData;
+ });
+ }
+ }
+ }
+ return allMatches;
+ }
+
+ QString m_content;
};
class ClangdCurrentDocumentFilter::Private
@@ -241,10 +294,10 @@ class ClangdCurrentDocumentFilter::Private
public:
~Private() { delete cppFilter; }
- Core::ILocatorFilter * const cppFilter
+ ILocatorFilter * const cppFilter
= CppEditor::CppModelManager::createAuxiliaryCurrentDocumentFilter();
LspCurrentDocumentFilter lspFilter;
- Core::ILocatorFilter *activeFilter = nullptr;
+ ILocatorFilter *activeFilter = nullptr;
};
@@ -256,17 +309,12 @@ ClangdCurrentDocumentFilter::ClangdCurrentDocumentFilter() : d(new Private)
setPriority(High);
setDefaultIncludedByDefault(false);
setEnabled(false);
- connect(Core::EditorManager::instance(), &Core::EditorManager::currentEditorChanged,
- this, [this](const Core::IEditor *editor) { setEnabled(editor); });
+ connect(EditorManager::instance(), &EditorManager::currentEditorChanged,
+ this, [this](const IEditor *editor) { setEnabled(editor); });
}
ClangdCurrentDocumentFilter::~ClangdCurrentDocumentFilter() { delete d; }
-void ClangdCurrentDocumentFilter::updateCurrentClient()
-{
- d->lspFilter.updateCurrentClient();
-}
-
void ClangdCurrentDocumentFilter::prepareSearch(const QString &entry)
{
const auto doc = TextEditor::TextDocument::currentTextDocument();
@@ -280,19 +328,12 @@ void ClangdCurrentDocumentFilter::prepareSearch(const QString &entry)
d->activeFilter->prepareSearch(entry);
}
-QList<Core::LocatorFilterEntry> ClangdCurrentDocumentFilter::matchesFor(
- QFutureInterface<Core::LocatorFilterEntry> &future, const QString &entry)
+QList<LocatorFilterEntry> ClangdCurrentDocumentFilter::matchesFor(
+ QFutureInterface<LocatorFilterEntry> &future, const QString &entry)
{
QTC_ASSERT(d->activeFilter, return {});
return d->activeFilter->matchesFor(future, entry);
}
-void ClangdCurrentDocumentFilter::accept(const Core::LocatorFilterEntry &selection, QString *newText,
- int *selectionStart, int *selectionLength) const
-{
- QTC_ASSERT(d->activeFilter, return);
- d->activeFilter->accept(selection, newText, selectionStart, selectionLength);
-}
-
} // namespace Internal
} // namespace ClangCodeModel
diff --git a/src/plugins/clangcodemodel/clangdlocatorfilters.h b/src/plugins/clangcodemodel/clangdlocatorfilters.h
index f7deacc7605..70afd4cce2e 100644
--- a/src/plugins/clangcodemodel/clangdlocatorfilters.h
+++ b/src/plugins/clangcodemodel/clangdlocatorfilters.h
@@ -15,18 +15,15 @@ class ClangGlobalSymbolFilter : public Core::ILocatorFilter
public:
ClangGlobalSymbolFilter();
ClangGlobalSymbolFilter(Core::ILocatorFilter *cppFilter,
- LanguageClient::WorkspaceLocatorFilter *lspFilter);
+ Core::ILocatorFilter *lspFilter);
~ClangGlobalSymbolFilter() override;
private:
void prepareSearch(const QString &entry) override;
QList<Core::LocatorFilterEntry> matchesFor(QFutureInterface<Core::LocatorFilterEntry> &future,
const QString &entry) override;
- void accept(const Core::LocatorFilterEntry &selection, QString *newText,
- int *selectionStart, int *selectionLength) const override;
-
Core::ILocatorFilter * const m_cppFilter;
- LanguageClient::WorkspaceLocatorFilter * const m_lspFilter;
+ Core::ILocatorFilter * const m_lspFilter;
};
class ClangClassesFilter : public ClangGlobalSymbolFilter
@@ -47,15 +44,10 @@ public:
ClangdCurrentDocumentFilter();
~ClangdCurrentDocumentFilter() override;
- void updateCurrentClient();
-
private:
void prepareSearch(const QString &entry) override;
QList<Core::LocatorFilterEntry> matchesFor(QFutureInterface<Core::LocatorFilterEntry> &future,
const QString &entry) override;
- void accept(const Core::LocatorFilterEntry &selection, QString *newText,
- int *selectionStart, int *selectionLength) const override;
-
class Private;
Private * const d;
};
diff --git a/src/plugins/clangcodemodel/clangdsemantichighlighting.cpp b/src/plugins/clangcodemodel/clangdsemantichighlighting.cpp
index cff8253de62..30ffaa780e4 100644
--- a/src/plugins/clangcodemodel/clangdsemantichighlighting.cpp
+++ b/src/plugins/clangcodemodel/clangdsemantichighlighting.cpp
@@ -112,7 +112,7 @@ static QList<BlockRange> cleanupDisabledCode(HighlightingResults &results, const
class ExtraHighlightingResultsCollector
{
public:
- ExtraHighlightingResultsCollector(QFutureInterface<HighlightingResult> &future,
+ ExtraHighlightingResultsCollector(QPromise<HighlightingResult> &promise,
HighlightingResults &results,
const Utils::FilePath &filePath, const ClangdAstNode &ast,
const QTextDocument *doc, const QString &docContent,
@@ -131,7 +131,7 @@ private:
void collectFromNode(const ClangdAstNode &node);
void visitNode(const ClangdAstNode&node);
- QFutureInterface<HighlightingResult> &m_future;
+ QPromise<HighlightingResult> &m_promise;
HighlightingResults &m_results;
const Utils::FilePath m_filePath;
const ClangdAstNode &m_ast;
@@ -142,7 +142,7 @@ private:
};
void doSemanticHighlighting(
- QFutureInterface<HighlightingResult> &future,
+ QPromise<HighlightingResult> &promise,
const Utils::FilePath &filePath,
const QList<ExpandedSemanticToken> &tokens,
const QString &docContents,
@@ -153,10 +153,8 @@ void doSemanticHighlighting(
const TaskTimer &taskTimer)
{
ThreadedSubtaskTimer t("highlighting", taskTimer);
- if (future.isCanceled()) {
- future.reportFinished();
+ if (promise.isCanceled())
return;
- }
const QTextDocument doc(docContents);
const auto tokenRange = [&doc](const ExpandedSemanticToken &token) {
@@ -399,13 +397,13 @@ void doSemanticHighlighting(
};
auto results = QtConcurrent::blockingMapped<HighlightingResults>(tokens, safeToResult);
const QList<BlockRange> ifdefedOutBlocks = cleanupDisabledCode(results, &doc, docContents);
- ExtraHighlightingResultsCollector(future, results, filePath, ast, &doc, docContents,
+ ExtraHighlightingResultsCollector(promise, results, filePath, ast, &doc, docContents,
clangdVersion).collect();
Utils::erase(results, [](const HighlightingResult &res) {
// QTCREATORBUG-28639
return res.textStyles.mainStyle == C_TEXT && res.textStyles.mixinStyles.empty();
});
- if (!future.isCanceled()) {
+ if (!promise.isCanceled()) {
qCInfo(clangdLogHighlight) << "reporting" << results.size() << "highlighting results";
QMetaObject::invokeMethod(textDocument, [textDocument, ifdefedOutBlocks, docRevision] {
if (textDocument && textDocument->document()->revision() == docRevision)
@@ -423,16 +421,16 @@ void doSemanticHighlighting(
if (ClangdClient * const client = ClangModelManagerSupport::clientForFile(filePath))
client->setVirtualRanges(filePath, virtualRanges, docRevision);
}, Qt::QueuedConnection);
- future.reportResults(QVector<HighlightingResult>(results.cbegin(), results.cend()));
+ for (const HighlightingResult &r : results)
+ promise.addResult(r);
}
- future.reportFinished();
}
ExtraHighlightingResultsCollector::ExtraHighlightingResultsCollector(
- QFutureInterface<HighlightingResult> &future, HighlightingResults &results,
+ QPromise<HighlightingResult> &promise, HighlightingResults &results,
const Utils::FilePath &filePath, const ClangdAstNode &ast, const QTextDocument *doc,
const QString &docContent, const QVersionNumber &clangdVersion)
- : m_future(future), m_results(results), m_filePath(filePath), m_ast(ast), m_doc(doc),
+ : m_promise(promise), m_results(results), m_filePath(filePath), m_ast(ast), m_doc(doc),
m_docContent(docContent), m_clangdVersion(clangdVersion.majorVersion())
{
}
@@ -916,7 +914,7 @@ void ExtraHighlightingResultsCollector::collectFromNode(const ClangdAstNode &nod
void ExtraHighlightingResultsCollector::visitNode(const ClangdAstNode &node)
{
- if (m_future.isCanceled())
+ if (m_promise.isCanceled())
return;
const ClangdAstNode::FileStatus prevFileStatus = m_currentFileStatus;
m_currentFileStatus = node.fileStatus(m_filePath);
diff --git a/src/plugins/clangcodemodel/clangdsemantichighlighting.h b/src/plugins/clangcodemodel/clangdsemantichighlighting.h
index 10bc4b8d091..a7f667d459f 100644
--- a/src/plugins/clangcodemodel/clangdsemantichighlighting.h
+++ b/src/plugins/clangcodemodel/clangdsemantichighlighting.h
@@ -3,11 +3,15 @@
#pragma once
-#include <QFutureInterface>
#include <QLoggingCategory>
#include <QPointer>
#include <QVersionNumber>
+QT_BEGIN_NAMESPACE
+template <typename T>
+class QPromise;
+QT_END_NAMESPACE
+
namespace LanguageClient { class ExpandedSemanticToken; }
namespace TextEditor {
class HighlightingResult;
@@ -21,7 +25,7 @@ class TaskTimer;
Q_DECLARE_LOGGING_CATEGORY(clangdLogHighlight);
void doSemanticHighlighting(
- QFutureInterface<TextEditor::HighlightingResult> &future,
+ QPromise<TextEditor::HighlightingResult> &promise,
const Utils::FilePath &filePath,
const QList<LanguageClient::ExpandedSemanticToken> &tokens,
const QString &docContents,
diff --git a/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp b/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp
index 5cc123ac911..79f2760a7c1 100644
--- a/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp
+++ b/src/plugins/clangcodemodel/clangmodelmanagersupport.cpp
@@ -33,6 +33,7 @@
#include <projectexplorer/buildconfiguration.h>
#include <projectexplorer/buildsystem.h>
#include <projectexplorer/project.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/projectnodes.h>
#include <projectexplorer/projecttree.h>
#include <projectexplorer/session.h>
@@ -40,9 +41,9 @@
#include <projectexplorer/taskhub.h>
#include <utils/algorithm.h>
+#include <utils/asynctask.h>
#include <utils/infobar.h>
#include <utils/qtcassert.h>
-#include <utils/runextensions.h>
#include <QApplication>
#include <QLabel>
@@ -53,6 +54,7 @@
using namespace CppEditor;
using namespace LanguageClient;
+using namespace ProjectExplorer;
using namespace Utils;
namespace ClangCodeModel::Internal {
@@ -66,7 +68,7 @@ static ProjectExplorer::Project *fallbackProject()
{
if (ProjectExplorer::Project * const p = ProjectExplorer::ProjectTree::currentProject())
return p;
- return ProjectExplorer::SessionManager::startupProject();
+ return ProjectExplorer::ProjectManager::startupProject();
}
static bool sessionModeEnabled()
@@ -87,7 +89,7 @@ static const QList<ProjectExplorer::Project *> projectsForClient(const Client *c
{
QList<ProjectExplorer::Project *> projects;
if (sessionModeEnabled()) {
- for (ProjectExplorer::Project * const p : ProjectExplorer::SessionManager::projects()) {
+ for (ProjectExplorer::Project * const p : ProjectExplorer::ProjectManager::projects()) {
if (ClangdProjectSettings(p).settings().useClangd)
projects << p;
}
@@ -228,14 +230,13 @@ ClangModelManagerSupport::ClangModelManagerSupport()
}
});
- auto *sessionManager = ProjectExplorer::SessionManager::instance();
- connect(sessionManager, &ProjectExplorer::SessionManager::projectRemoved,
+ auto projectManager = ProjectExplorer::ProjectManager::instance();
+ connect(projectManager, &ProjectExplorer::ProjectManager::projectRemoved,
this, [this] {
if (!sessionModeEnabled())
claimNonProjectSources(clientForProject(fallbackProject()));
});
- connect(sessionManager, &ProjectExplorer::SessionManager::sessionLoaded,
- this, [this] {
+ connect(SessionManager::instance(), &SessionManager::sessionLoaded, this, [this] {
if (sessionModeEnabled())
onClangdSettingsChanged();
});
@@ -351,7 +352,7 @@ void ClangModelManagerSupport::checkUnused(const Link &link, Core::SearchResult
const LinkHandler &callback)
{
if (const ProjectExplorer::Project * const project
- = ProjectExplorer::SessionManager::projectForFile(link.targetFilePath)) {
+ = ProjectExplorer::ProjectManager::projectForFile(link.targetFilePath)) {
if (ClangdClient * const client = clientWithProject(project);
client && client->isFullyIndexed()) {
client->checkUnused(link, search, callback);
@@ -429,7 +430,7 @@ static bool isProjectDataUpToDate(
ProjectExplorer::Project *project, ProjectInfoList projectInfo,
const FilePath &jsonDbDir)
{
- if (project && !ProjectExplorer::SessionManager::hasProject(project))
+ if (project && !ProjectExplorer::ProjectManager::hasProject(project))
return false;
const ClangdSettings settings(ClangdProjectSettings(project).settings());
if (!settings.useClangd())
@@ -525,7 +526,7 @@ void ClangModelManagerSupport::updateLanguageClient(ProjectExplorer::Project *pr
hasDocuments = true;
continue;
}
- const Project * const docProject = SessionManager::projectForFile(doc->filePath());
+ const Project * const docProject = ProjectManager::projectForFile(doc->filePath());
if (currentClient && currentClient->project()
&& currentClient->project() != project
&& currentClient->project() == docProject) {
@@ -565,8 +566,8 @@ void ClangModelManagerSupport::updateLanguageClient(ProjectExplorer::Project *pr
ProjectNode *rootNode = nullptr;
if (project)
rootNode = project->rootProjectNode();
- else if (SessionManager::startupProject())
- rootNode = SessionManager::startupProject()->rootProjectNode();
+ else if (ProjectManager::startupProject())
+ rootNode = ProjectManager::startupProject()->rootProjectNode();
if (!rootNode)
return;
const Node * const cxxNode = rootNode->findNode([](Node *n) {
@@ -584,7 +585,7 @@ void ClangModelManagerSupport::updateLanguageClient(ProjectExplorer::Project *pr
});
const FilePath includeDir = settings.clangdIncludePath();
- auto future = Utils::runAsync(&Internal::generateCompilationDB, projectInfo,
+ auto future = Utils::asyncRun(&Internal::generateCompilationDB, projectInfo,
jsonDbDir, CompilationDbPurpose::CodeModel,
warningsConfigForProject(project),
globalClangOptions(), includeDir);
@@ -641,7 +642,7 @@ void ClangModelManagerSupport::claimNonProjectSources(ClangdClient *client)
}
if (!ClangdSettings::instance().sizeIsOkay(doc->filePath()))
continue;
- if (ProjectExplorer::SessionManager::projectForFile(doc->filePath()))
+ if (ProjectExplorer::ProjectManager::projectForFile(doc->filePath()))
continue;
if (client->project() && !ProjectFile::isHeader(doc->filePath()))
continue;
@@ -666,7 +667,7 @@ void ClangModelManagerSupport::watchForExternalChanges()
if (!ProjectFile::isSource(kind) && !ProjectFile::isHeader(kind))
continue;
ProjectExplorer::Project * const project
- = ProjectExplorer::SessionManager::projectForFile(file);
+ = ProjectExplorer::ProjectManager::projectForFile(file);
if (!project)
continue;
@@ -692,7 +693,7 @@ void ClangModelManagerSupport::watchForInternalChanges()
if (!ProjectFile::isSource(kind) && !ProjectFile::isHeader(kind))
continue;
ProjectExplorer::Project * const project
- = ProjectExplorer::SessionManager::projectForFile(fp);
+ = ProjectExplorer::ProjectManager::projectForFile(fp);
if (!project)
continue;
if (ClangdClient * const client = clientForProject(project);
@@ -729,7 +730,7 @@ void ClangModelManagerSupport::onEditorOpened(Core::IEditor *editor)
connectToWidgetsMarkContextMenuRequested(editor->widget());
ProjectExplorer::Project * project
- = ProjectExplorer::SessionManager::projectForFile(document->filePath());
+ = ProjectExplorer::ProjectManager::projectForFile(document->filePath());
const ClangdSettings settings(ClangdProjectSettings(project).settings());
if (!settings.sizeIsOkay(textDocument->filePath()))
return;
@@ -849,7 +850,7 @@ void ClangModelManagerSupport::onClangdSettingsChanged()
{
const bool sessionMode = sessionModeEnabled();
- for (ProjectExplorer::Project * const project : ProjectExplorer::SessionManager::projects()) {
+ for (ProjectExplorer::Project * const project : ProjectExplorer::ProjectManager::projects()) {
const CppEditor::ClangdSettings settings(
CppEditor::ClangdProjectSettings(project).settings());
ClangdClient * const client = clientWithProject(project);
diff --git a/src/plugins/clangcodemodel/test/clangbatchfileprocessor.cpp b/src/plugins/clangcodemodel/test/clangbatchfileprocessor.cpp
deleted file mode 100644
index 1f0219df997..00000000000
--- a/src/plugins/clangcodemodel/test/clangbatchfileprocessor.cpp
+++ /dev/null
@@ -1,729 +0,0 @@
-// Copyright (C) 2017 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#include "clangbatchfileprocessor.h"
-
-#include <clangcodemodel/clangeditordocumentprocessor.h>
-
-#include <coreplugin/editormanager/editormanager.h>
-#include <coreplugin/editormanager/ieditor.h>
-#include <coreplugin/icore.h>
-#include <cppeditor/cpptoolsreuse.h>
-#include <cppeditor/cpptoolstestcase.h>
-#include <cppeditor/modelmanagertesthelper.h>
-#include <cppeditor/projectinfo.h>
-#include <projectexplorer/projectexplorer.h>
-#include <texteditor/codeassist/assistinterface.h>
-#include <texteditor/codeassist/assistproposalitem.h>
-#include <texteditor/codeassist/completionassistprovider.h>
-#include <texteditor/codeassist/genericproposalmodel.h>
-#include <texteditor/codeassist/iassistprocessor.h>
-#include <texteditor/codeassist/iassistproposal.h>
-#include <texteditor/textdocument.h>
-#include <texteditor/texteditor.h>
-
-#include <utils/environment.h>
-#include <utils/executeondestruction.h>
-#include <utils/qtcassert.h>
-
-#include <QDebug>
-#include <QElapsedTimer>
-#include <QFileInfo>
-#include <QLoggingCategory>
-#include <QSharedPointer>
-#include <QString>
-#include <QThread>
-
-using namespace ProjectExplorer;
-
-namespace ClangCodeModel {
-namespace Internal {
-
-static Q_LOGGING_CATEGORY(debug, "qtc.clangcodemodel.batch", QtWarningMsg);
-
-static int timeOutFromEnvironmentVariable()
-{
- bool isConversionOk = false;
- const int intervalAsInt = Utils::qtcEnvironmentVariableIntValue("QTC_CLANG_BATCH_TIMEOUT",
- &isConversionOk);
- if (!isConversionOk) {
- qCDebug(debug, "Environment variable QTC_CLANG_BATCH_TIMEOUT is not set, assuming 30000.");
- return 30000;
- }
-
- return intervalAsInt;
-}
-
-int timeOutInMs()
-{
- static int timeOut = timeOutFromEnvironmentVariable();
- return timeOut;
-}
-
-namespace {
-
-class BatchFileLineTokenizer
-{
-public:
- BatchFileLineTokenizer(const QString &line);
-
- QString nextToken();
-
-private:
- const QChar *advanceToTokenBegin();
- const QChar *advanceToTokenEnd();
-
- bool atEnd() const;
- bool atWhiteSpace() const;
- bool atQuotationMark() const;
-
-private:
- bool m_isWithinQuotation = false;
- QString m_line;
- const QChar *m_currentChar;
-};
-
-BatchFileLineTokenizer::BatchFileLineTokenizer(const QString &line)
- : m_line(line)
- , m_currentChar(m_line.unicode())
-{
-}
-
-QString BatchFileLineTokenizer::nextToken()
-{
- if (const QChar *tokenBegin = advanceToTokenBegin()) {
- if (const QChar *tokenEnd = advanceToTokenEnd()) {
- const int length = tokenEnd - tokenBegin;
- return QString(tokenBegin, length);
- }
- }
-
- return QString();
-}
-
-const QChar *BatchFileLineTokenizer::advanceToTokenBegin()
-{
- m_isWithinQuotation = false;
-
- forever {
- if (atEnd())
- return nullptr;
-
- if (atQuotationMark()) {
- m_isWithinQuotation = true;
- ++m_currentChar;
- return m_currentChar;
- }
-
- if (!atWhiteSpace())
- return m_currentChar;
-
- ++m_currentChar;
- }
-}
-
-const QChar *BatchFileLineTokenizer::advanceToTokenEnd()
-{
- forever {
- if (m_isWithinQuotation) {
- if (atEnd()) {
- qWarning("ClangBatchFileProcessor: error: unfinished quotation.");
- return nullptr;
- }
-
- if (atQuotationMark())
- return m_currentChar++;
-
- } else if (atWhiteSpace() || atEnd()) {
- return m_currentChar;
- }
-
- ++m_currentChar;
- }
-}
-
-bool BatchFileLineTokenizer::atEnd() const
-{
- return *m_currentChar == QLatin1Char('\0');
-}
-
-bool BatchFileLineTokenizer::atWhiteSpace() const
-{
- return *m_currentChar == ' '
- || *m_currentChar == '\t'
- || *m_currentChar == '\n';
-}
-
-bool BatchFileLineTokenizer::atQuotationMark() const
-{
- return *m_currentChar == '"';
-}
-
-struct CommandContext {
- QString filePath;
- int lineNumber = -1;
-};
-
-class Command
-{
-public:
- using Ptr = QSharedPointer<Command>;
-
-public:
- Command(const CommandContext &context) : m_commandContext(context) {}
- virtual ~Command() = default;
-
- const CommandContext &context() const { return m_commandContext; }
- virtual bool run() { return true; }
-
-private:
- const CommandContext m_commandContext;
-};
-
-class OpenProjectCommand : public Command
-{
-public:
- OpenProjectCommand(const CommandContext &context,
- const QString &projectFilePath);
-
- bool run() override;
-
- static Command::Ptr parse(BatchFileLineTokenizer &arguments,
- const CommandContext &context);
-
-private:
- QString m_projectFilePath;
-};
-
-OpenProjectCommand::OpenProjectCommand(const CommandContext &context,
- const QString &projectFilePath)
- : Command(context)
- , m_projectFilePath(projectFilePath)
-{
-}
-
-bool OpenProjectCommand::run()
-{
- qCDebug(debug) << "line" << context().lineNumber << "OpenProjectCommand" << m_projectFilePath;
-
- const ProjectExplorerPlugin::OpenProjectResult openProjectSucceeded
- = ProjectExplorerPlugin::openProject(Utils::FilePath::fromString(m_projectFilePath));
- QTC_ASSERT(openProjectSucceeded, return false);
-
- Project *project = openProjectSucceeded.project();
- project->configureAsExampleProject(nullptr);
-
- return CppEditor::Tests::TestCase::waitUntilProjectIsFullyOpened(project, timeOutInMs());
-}
-
-Command::Ptr OpenProjectCommand::parse(BatchFileLineTokenizer &arguments,
- const CommandContext &context)
-{
- const QString projectFilePath = arguments.nextToken();
- if (projectFilePath.isEmpty()) {
- qWarning("%s:%d: error: No project file path given.",
- qPrintable(context.filePath),
- context.lineNumber);
- return Command::Ptr();
- }
-
- const QString absoluteProjectFilePath = QFileInfo(projectFilePath).absoluteFilePath();
-
- return Command::Ptr(new OpenProjectCommand(context, absoluteProjectFilePath));
-}
-
-class OpenDocumentCommand : public Command
-{
-public:
- OpenDocumentCommand(const CommandContext &context,
- const QString &documentFilePath);
-
- bool run() override;
-
- static Command::Ptr parse(BatchFileLineTokenizer &arguments, const CommandContext &context);
-
-private:
- Utils::FilePath m_documentFilePath;
-};
-
-OpenDocumentCommand::OpenDocumentCommand(const CommandContext &context,
- const QString &documentFilePath)
- : Command(context)
- , m_documentFilePath(Utils::FilePath::fromString(documentFilePath))
-{
-}
-
-class WaitForUpdatedCodeWarnings : public QObject
-{
-public:
- WaitForUpdatedCodeWarnings(ClangEditorDocumentProcessor *processor);
-
- bool wait(int timeOutInMs) const;
-
-private:
- void onCodeWarningsUpdated() { m_gotResults = true; }
-
-private:
-
- bool m_gotResults = false;
-};
-
-WaitForUpdatedCodeWarnings::WaitForUpdatedCodeWarnings(ClangEditorDocumentProcessor *processor)
-{
- connect(processor,
- &ClangEditorDocumentProcessor::codeWarningsUpdated,
- this, &WaitForUpdatedCodeWarnings::onCodeWarningsUpdated);
-}
-
-bool WaitForUpdatedCodeWarnings::wait(int timeOutInMs) const
-{
- QElapsedTimer time;
- time.start();
-
- forever {
- if (time.elapsed() > timeOutInMs) {
- qWarning("WaitForUpdatedCodeWarnings: timeout of %d ms reached.", timeOutInMs);
- return false;
- }
-
- if (m_gotResults)
- return true;
-
- QCoreApplication::processEvents();
- QThread::msleep(20);
- }
-}
-
-bool OpenDocumentCommand::run()
-{
- qCDebug(debug) << "line" << context().lineNumber << "OpenDocumentCommand" << m_documentFilePath;
-
- const bool openEditorSucceeded = Core::EditorManager::openEditor(m_documentFilePath);
- QTC_ASSERT(openEditorSucceeded, return false);
-
- auto *processor = ClangEditorDocumentProcessor::get(m_documentFilePath);
- QTC_ASSERT(processor, return false);
-
- WaitForUpdatedCodeWarnings waiter(processor);
- return waiter.wait(timeOutInMs());
-}
-
-Command::Ptr OpenDocumentCommand::parse(BatchFileLineTokenizer &arguments,
- const CommandContext &context)
-{
- const QString documentFilePath = arguments.nextToken();
- if (documentFilePath.isEmpty()) {
- qWarning("%s:%d: error: No document file path given.",
- qPrintable(context.filePath),
- context.lineNumber);
- return Command::Ptr();
- }
-
- const QString absoluteDocumentFilePath = QFileInfo(documentFilePath).absoluteFilePath();
-
- return Command::Ptr(new OpenDocumentCommand(context, absoluteDocumentFilePath));
-}
-
-class CloseAllDocuments : public Command
-{
-public:
- CloseAllDocuments(const CommandContext &context);
-
- bool run() override;
-
- static Command::Ptr parse(BatchFileLineTokenizer &arguments, const CommandContext &context);
-};
-
-CloseAllDocuments::CloseAllDocuments(const CommandContext &context)
- : Command(context)
-{
-}
-
-bool CloseAllDocuments::run()
-{
- qCDebug(debug) << "line" << context().lineNumber << "CloseAllDocuments";
-
- return Core::EditorManager::closeAllEditors(/*askAboutModifiedEditors=*/ false);
-}
-
-Command::Ptr CloseAllDocuments::parse(BatchFileLineTokenizer &arguments,
- const CommandContext &context)
-{
- const QString argument = arguments.nextToken();
- if (!argument.isEmpty()) {
- qWarning("%s:%d: error: Unexpected argument.",
- qPrintable(context.filePath),
- context.lineNumber);
- return Command::Ptr();
- }
-
- return Command::Ptr(new CloseAllDocuments(context));
-}
-
-class InsertTextCommand : public Command
-{
-public:
- // line and column are 1-based
- InsertTextCommand(const CommandContext &context, const QString &text);
-
- bool run() override;
-
- static Command::Ptr parse(BatchFileLineTokenizer &arguments,
- const CommandContext &context);
-
-private:
- const QString m_textToInsert;
-};
-
-InsertTextCommand::InsertTextCommand(const CommandContext &context, const QString &text)
- : Command(context)
- , m_textToInsert(text)
-{
-}
-
-TextEditor::BaseTextEditor *currentTextEditor()
-{
- return qobject_cast<TextEditor::BaseTextEditor*>(Core::EditorManager::currentEditor());
-}
-
-bool InsertTextCommand::run()
-{
- qCDebug(debug) << "line" << context().lineNumber << "InsertTextCommand" << m_textToInsert;
-
- TextEditor::BaseTextEditor *editor = currentTextEditor();
- QTC_ASSERT(editor, return false);
- const Utils::FilePath documentFilePath = editor->document()->filePath();
- auto processor = ClangEditorDocumentProcessor::get(documentFilePath);
- QTC_ASSERT(processor, return false);
-
- editor->insert(m_textToInsert);
-
- WaitForUpdatedCodeWarnings waiter(processor);
- return waiter.wait(timeOutInMs());
-}
-
-Command::Ptr InsertTextCommand::parse(BatchFileLineTokenizer &arguments,
- const CommandContext &context)
-{
- const QString textToInsert = arguments.nextToken();
- if (textToInsert.isEmpty()) {
- qWarning("%s:%d: error: No text to insert given.",
- qPrintable(context.filePath),
- context.lineNumber);
- return Command::Ptr();
- }
-
- return Command::Ptr(new InsertTextCommand(context, textToInsert));
-}
-
-class SetCursorCommand : public Command
-{
-public:
- // line and column are 1-based
- SetCursorCommand(const CommandContext &context, int line, int column);
-
- bool run() override;
-
- static Command::Ptr parse(BatchFileLineTokenizer &arguments,
- const CommandContext &context);
-
-private:
- int m_line;
- int m_column;
-};
-
-SetCursorCommand::SetCursorCommand(const CommandContext &context, int line, int column)
- : Command(context)
- , m_line(line)
- , m_column(column)
-{
-}
-
-bool SetCursorCommand::run()
-{
- qCDebug(debug) << "line" << context().lineNumber << "SetCursorCommand" << m_line << m_column;
-
- TextEditor::BaseTextEditor *editor = currentTextEditor();
- QTC_ASSERT(editor, return false);
-
- editor->gotoLine(m_line, m_column - 1);
-
- return true;
-}
-
-Command::Ptr SetCursorCommand::parse(BatchFileLineTokenizer &arguments,
- const CommandContext &context)
-{
- // Process line
- const QString line = arguments.nextToken();
- if (line.isEmpty()) {
- qWarning("%s:%d: error: No line number given.",
- qPrintable(context.filePath),
- context.lineNumber);
- return Command::Ptr();
- }
- bool converted = false;
- const int lineNumber = line.toInt(&converted);
- if (!converted) {
- qWarning("%s:%d: error: Invalid line number.",
- qPrintable(context.filePath),
- context.lineNumber);
- return Command::Ptr();
- }
-
- // Process column
- const QString column = arguments.nextToken();
- if (column.isEmpty()) {
- qWarning("%s:%d: error: No column number given.",
- qPrintable(context.filePath),
- context.lineNumber);
- return Command::Ptr();
- }
- converted = false;
- const int columnNumber = column.toInt(&converted);
- if (!converted) {
- qWarning("%s:%d: error: Invalid column number.",
- qPrintable(context.filePath),
- context.lineNumber);
- return Command::Ptr();
- }
-
- return Command::Ptr(new SetCursorCommand(context, lineNumber, columnNumber));
-}
-
-class ProcessEventsCommand : public Command
-{
-public:
- ProcessEventsCommand(const CommandContext &context, int durationInMs);
-
- bool run() override;
-
- static Command::Ptr parse(BatchFileLineTokenizer &arguments,
- const CommandContext &context);
-
-private:
- int m_durationInMs;
-};
-
-ProcessEventsCommand::ProcessEventsCommand(const CommandContext &context,
- int durationInMs)
- : Command(context)
- , m_durationInMs(durationInMs)
-{
-}
-
-bool ProcessEventsCommand::run()
-{
- qCDebug(debug) << "line" << context().lineNumber << "ProcessEventsCommand" << m_durationInMs;
-
- QElapsedTimer time;
- time.start();
-
- forever {
- if (time.elapsed() > m_durationInMs)
- return true;
-
- QCoreApplication::processEvents();
- QThread::msleep(20);
- }
-}
-
-Command::Ptr ProcessEventsCommand::parse(BatchFileLineTokenizer &arguments,
- const CommandContext &context)
-{
- const QString durationInMsText = arguments.nextToken();
- if (durationInMsText.isEmpty()) {
- qWarning("%s:%d: error: No duration given.",
- qPrintable(context.filePath),
- context.lineNumber);
- return Command::Ptr();
- }
-
- bool converted = false;
- const int durationInMs = durationInMsText.toInt(&converted);
- if (!converted) {
- qWarning("%s:%d: error: Invalid duration given.",
- qPrintable(context.filePath),
- context.lineNumber);
- return Command::Ptr();
- }
-
- return Command::Ptr(new ProcessEventsCommand(context, durationInMs));
-}
-
-class BatchFileReader
-{
-public:
- BatchFileReader(const QString &filePath);
-
- bool isFilePathValid() const;
-
- QString read() const;
-
-private:
- const QString m_batchFilePath;
-};
-
-BatchFileReader::BatchFileReader(const QString &filePath)
- : m_batchFilePath(filePath)
-{
-}
-
-bool BatchFileReader::isFilePathValid() const
-{
- QFileInfo fileInfo(m_batchFilePath);
-
- return !m_batchFilePath.isEmpty()
- && fileInfo.isFile()
- && fileInfo.isReadable();
-}
-
-QString BatchFileReader::read() const
-{
- QFile file(m_batchFilePath);
- QTC_CHECK(file.open(QFile::ReadOnly | QFile::Text));
-
- return QString::fromLocal8Bit(file.readAll());
-}
-
-class BatchFileParser
-{
-public:
- BatchFileParser(const QString &filePath,
- const QString &commands);
-
- bool parse();
- QVector<Command::Ptr> commands() const;
-
-private:
- bool advanceLine();
- QString currentLine() const;
- bool parseLine(const QString &line);
-
-private:
- using ParseFunction = Command::Ptr (*)(BatchFileLineTokenizer &, const CommandContext &);
- using CommandToParseFunction = QHash<QString, ParseFunction>;
- CommandToParseFunction m_commandParsers;
-
- int m_currentLineIndex = -1;
- CommandContext m_context;
- QStringList m_lines;
- QVector<Command::Ptr> m_commands;
-};
-
-BatchFileParser::BatchFileParser(const QString &filePath,
- const QString &commands)
- : m_lines(commands.split('\n'))
-{
- m_context.filePath = filePath;
-
- m_commandParsers.insert("openProject", &OpenProjectCommand::parse);
- m_commandParsers.insert("openDocument", &OpenDocumentCommand::parse);
- m_commandParsers.insert("closeAllDocuments", &CloseAllDocuments::parse);
- m_commandParsers.insert("setCursor", &SetCursorCommand::parse);
- m_commandParsers.insert("insertText", &InsertTextCommand::parse);
- m_commandParsers.insert("processEvents", &ProcessEventsCommand::parse);
-}
-
-bool BatchFileParser::parse()
-{
- while (advanceLine()) {
- const QString line = currentLine().trimmed();
- if (line.isEmpty() || line.startsWith('#'))
- continue;
-
- if (!parseLine(line))
- return false;
- }
-
- return true;
-}
-
-QVector<Command::Ptr> BatchFileParser::commands() const
-{
- return m_commands;
-}
-
-bool BatchFileParser::advanceLine()
-{
- ++m_currentLineIndex;
- m_context.lineNumber = m_currentLineIndex + 1;
- return m_currentLineIndex < m_lines.size();
-}
-
-QString BatchFileParser::currentLine() const
-{
- return m_lines[m_currentLineIndex];
-}
-
-bool BatchFileParser::parseLine(const QString &line)
-{
- BatchFileLineTokenizer tokenizer(line);
- QString command = tokenizer.nextToken();
- QTC_CHECK(!command.isEmpty());
-
- if (const ParseFunction parseFunction = m_commandParsers.value(command)) {
- if (Command::Ptr cmd = parseFunction(tokenizer, m_context)) {
- m_commands.append(cmd);
- return true;
- }
-
- return false;
- }
-
- qWarning("%s:%d: error: Unknown command \"%s\".",
- qPrintable(m_context.filePath),
- m_context.lineNumber,
- qPrintable(command));
-
- return false;
-}
-
-} // anonymous namespace
-
-static QString applySubstitutions(const QString &filePath, const QString &text)
-{
- const QString dirPath = QFileInfo(filePath).absolutePath();
-
- QString result = text;
- result.replace("${PWD}", dirPath);
-
- return result;
-}
-
-bool runClangBatchFile(const QString &filePath)
-{
- qWarning("ClangBatchFileProcessor: Running \"%s\".", qPrintable(filePath));
-
- BatchFileReader reader(filePath);
- QTC_ASSERT(reader.isFilePathValid(), return false);
- const QString fileContent = reader.read();
- const QString fileContentWithSubstitutionsApplied = applySubstitutions(filePath, fileContent);
-
- BatchFileParser parser(filePath, fileContentWithSubstitutionsApplied);
- QTC_ASSERT(parser.parse(), return false);
- const QVector<Command::Ptr> commands = parser.commands();
-
- Utils::ExecuteOnDestruction closeAllEditors([] {
- qWarning("ClangBatchFileProcessor: Finished, closing all documents.");
- QTC_CHECK(Core::EditorManager::closeAllEditors(/*askAboutModifiedEditors=*/ false));
- });
-
- for (const Command::Ptr &command : commands) {
- const bool runSucceeded = command->run();
- QCoreApplication::processEvents(); // Update GUI
-
- if (!runSucceeded) {
- const CommandContext context = command->context();
- qWarning("%s:%d: Failed to run.",
- qPrintable(context.filePath),
- context.lineNumber);
- return false;
- }
- }
-
- return true;
-}
-
-} // namespace Internal
-} // namespace ClangCodeModel
diff --git a/src/plugins/clangcodemodel/test/clangbatchfileprocessor.h b/src/plugins/clangcodemodel/test/clangbatchfileprocessor.h
deleted file mode 100644
index 942269b3648..00000000000
--- a/src/plugins/clangcodemodel/test/clangbatchfileprocessor.h
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright (C) 2017 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#pragma once
-
-#include <QString>
-
-namespace ClangCodeModel {
-namespace Internal {
-
-int timeOutInMs();
-
-bool runClangBatchFile(const QString &filePath);
-
-} // namespace Internal
-} // namespace ClangCodeModel
diff --git a/src/plugins/clangcodemodel/test/clangdtests.cpp b/src/plugins/clangcodemodel/test/clangdtests.cpp
index b5103d3552d..36b6d43e51c 100644
--- a/src/plugins/clangcodemodel/test/clangdtests.cpp
+++ b/src/plugins/clangcodemodel/test/clangdtests.cpp
@@ -3,7 +3,6 @@
#include "clangdtests.h"
-#include "clangbatchfileprocessor.h"
#include "../clangdclient.h"
#include "../clangmodelmanagersupport.h"
@@ -69,6 +68,27 @@ static QString qrcPath(const QString &relativeFilePath)
return ":/unittests/ClangCodeModel/" + relativeFilePath;
}
+static Q_LOGGING_CATEGORY(debug, "qtc.clangcodemodel.batch", QtWarningMsg);
+
+static int timeOutFromEnvironmentVariable()
+{
+ bool isConversionOk = false;
+ const int intervalAsInt = Utils::qtcEnvironmentVariableIntValue("QTC_CLANG_BATCH_TIMEOUT",
+ &isConversionOk);
+ if (!isConversionOk) {
+ qCDebug(debug, "Environment variable QTC_CLANG_BATCH_TIMEOUT is not set, assuming 30000.");
+ return 30000;
+ }
+
+ return intervalAsInt;
+}
+
+int timeOutInMs()
+{
+ static int timeOut = timeOutFromEnvironmentVariable();
+ return timeOut;
+}
+
ClangdTest::~ClangdTest()
{
EditorManager::closeAllEditors(false);
diff --git a/src/plugins/clangformat/clangformat.qbs b/src/plugins/clangformat/clangformat.qbs
index bd1b9677632..057035cd1a6 100644
--- a/src/plugins/clangformat/clangformat.qbs
+++ b/src/plugins/clangformat/clangformat.qbs
@@ -54,10 +54,8 @@ QtcPlugin {
"clangformatutils.cpp",
]
- Group {
- name: "Tests"
+ QtcTestFiles {
prefix: "tests/"
- condition: qtc.testsEnabled
cpp.defines: outer.concat('TESTDATA_DIR="' + sourceDirectory + "/tests/data" + '"')
files: [
"clangformat-test.cpp",
diff --git a/src/plugins/clangformat/clangformatbaseindenter.cpp b/src/plugins/clangformat/clangformatbaseindenter.cpp
index affabfec671..9ab44472304 100644
--- a/src/plugins/clangformat/clangformatbaseindenter.cpp
+++ b/src/plugins/clangformat/clangformatbaseindenter.cpp
@@ -9,7 +9,7 @@
#include <projectexplorer/editorconfiguration.h>
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <texteditor/icodestylepreferences.h>
#include <texteditor/texteditorsettings.h>
@@ -744,7 +744,7 @@ void ClangFormatBaseIndenter::autoIndent(const QTextCursor &cursor,
clang::format::FormatStyle overrideStyle(const Utils::FilePath &fileName)
{
const ProjectExplorer::Project *projectForFile
- = ProjectExplorer::SessionManager::projectForFile(fileName);
+ = ProjectExplorer::ProjectManager::projectForFile(fileName);
const TextEditor::ICodeStylePreferences *preferences
= projectForFile
diff --git a/src/plugins/clangformat/clangformatfile.h b/src/plugins/clangformat/clangformatfile.h
index 6e2267befc9..dcd0e0d6c10 100644
--- a/src/plugins/clangformat/clangformatfile.h
+++ b/src/plugins/clangformat/clangformatfile.h
@@ -3,7 +3,8 @@
#pragma once
-#include "utils/filepath.h"
+#include <utils/filepath.h>
+
#include <clang/Format/Format.h>
namespace CppEditor { class CppCodeStyleSettings; }
diff --git a/src/plugins/clangformat/clangformatindenter.cpp b/src/plugins/clangformat/clangformatindenter.cpp
index 94e0fafd8e0..67d5171b010 100644
--- a/src/plugins/clangformat/clangformatindenter.cpp
+++ b/src/plugins/clangformat/clangformatindenter.cpp
@@ -15,7 +15,7 @@
#include <utils/genericconstants.h>
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <texteditor/tabsettings.h>
#include <texteditor/textdocumentlayout.h>
diff --git a/src/plugins/clangformat/clangformatutils.cpp b/src/plugins/clangformat/clangformatutils.cpp
index d1d6bee684b..a7a766768eb 100644
--- a/src/plugins/clangformat/clangformatutils.cpp
+++ b/src/plugins/clangformat/clangformatutils.cpp
@@ -15,7 +15,7 @@
#include <projectexplorer/editorconfiguration.h>
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <utils/qtcassert.h>
@@ -211,7 +211,7 @@ bool getProjectOverriddenSettings(const ProjectExplorer::Project *project)
bool getCurrentOverriddenSettings(const Utils::FilePath &filePath)
{
- const ProjectExplorer::Project *project = ProjectExplorer::SessionManager::projectForFile(
+ const ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::projectForFile(
filePath);
return getProjectUseGlobalSettings(project)
@@ -233,7 +233,7 @@ ClangFormatSettings::Mode getProjectIndentationOrFormattingSettings(
ClangFormatSettings::Mode getCurrentIndentationOrFormattingSettings(const Utils::FilePath &filePath)
{
- const ProjectExplorer::Project *project = ProjectExplorer::SessionManager::projectForFile(
+ const ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::projectForFile(
filePath);
return getProjectUseGlobalSettings(project)
@@ -264,7 +264,7 @@ Utils::FilePath configForFile(const Utils::FilePath &fileName)
return findConfig(fileName);
const ProjectExplorer::Project *projectForFile
- = ProjectExplorer::SessionManager::projectForFile(fileName);
+ = ProjectExplorer::ProjectManager::projectForFile(fileName);
const TextEditor::ICodeStylePreferences *preferences
= projectForFile
diff --git a/src/plugins/clangtools/clangtool.cpp b/src/plugins/clangtools/clangtool.cpp
index afe60be7dc8..bf13622b590 100644
--- a/src/plugins/clangtools/clangtool.cpp
+++ b/src/plugins/clangtools/clangtool.cpp
@@ -34,7 +34,7 @@
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/projectexplorericons.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <projectexplorer/taskhub.h>
@@ -342,7 +342,7 @@ static FileInfos sortedFileInfos(const QVector<CppEditor::ProjectPart::ConstPtr>
static RunSettings runSettings()
{
- if (Project *project = SessionManager::startupProject()) {
+ if (Project *project = ProjectManager::startupProject()) {
const auto projectSettings = ClangToolsProjectSettings::getSettings(project);
if (!projectSettings->useGlobalSettings())
return projectSettings->runSettings();
@@ -611,7 +611,7 @@ void ClangTool::startTool(ClangTool::FileSelection fileSelection,
const RunSettings &runSettings,
const CppEditor::ClangDiagnosticConfig &diagnosticConfig)
{
- Project *project = SessionManager::startupProject();
+ Project *project = ProjectManager::startupProject();
QTC_ASSERT(project, return);
QTC_ASSERT(project->activeTarget(), return);
@@ -870,7 +870,7 @@ static CheckResult canAnalyze()
Tr::tr("Set a valid Clazy-Standalone executable.")};
}
- if (Project *project = SessionManager::startupProject()) {
+ if (Project *project = ProjectManager::startupProject()) {
if (!canAnalyzeProject(project)) {
return {CheckResult::ProjectNotSuitable,
Tr::tr("Project \"%1\" is not a C/C++ project.")
diff --git a/src/plugins/clangtools/clangtoolrunner.cpp b/src/plugins/clangtools/clangtoolrunner.cpp
index f5c955c50e7..90bfe51ca30 100644
--- a/src/plugins/clangtools/clangtoolrunner.cpp
+++ b/src/plugins/clangtools/clangtoolrunner.cpp
@@ -111,11 +111,10 @@ TaskItem clangToolTask(const AnalyzeInputData &input,
};
const TreeStorage<ClangToolStorage> storage;
- const auto mainToolArguments = [=](const ClangToolStorage *data)
- {
+ const auto mainToolArguments = [=](const ClangToolStorage &data) {
QStringList result;
- result << "-export-fixes=" + data->outputFilePath.nativePath();
- if (!input.overlayFilePath.isEmpty() && isVFSOverlaySupported(data->executable))
+ result << "-export-fixes=" + data.outputFilePath.nativePath();
+ if (!input.overlayFilePath.isEmpty() && isVFSOverlaySupported(data.executable))
result << "--vfsoverlay=" + input.overlayFilePath;
result << input.unit.file.nativePath();
return result;
@@ -147,13 +146,13 @@ TaskItem clangToolTask(const AnalyzeInputData &input,
process.setLowPriority();
process.setWorkingDirectory(input.outputDirPath); // Current clang-cl puts log file into working dir.
- const ClangToolStorage *data = storage.activeStorage();
+ const ClangToolStorage &data = *storage;
const QStringList args = checksArguments(input.tool, input.config)
+ mainToolArguments(data)
+ QStringList{"--"}
+ clangArguments(input.config, input.unit.arguments);
- const CommandLine commandLine = {data->executable, args};
+ const CommandLine commandLine = {data.executable, args};
qCDebug(LOG).noquote() << "Starting" << commandLine.toUserOutput();
process.setCommand(commandLine);
@@ -162,8 +161,7 @@ TaskItem clangToolTask(const AnalyzeInputData &input,
qCDebug(LOG).noquote() << "Output:\n" << process.cleanedStdOut();
if (!outputHandler)
return;
- const ClangToolStorage *data = storage.activeStorage();
- outputHandler({true, input.unit.file, data->outputFilePath, input.tool});
+ outputHandler({true, input.unit.file, storage->outputFilePath, input.tool});
};
const auto onProcessError = [=](const QtcProcess &process) {
if (!outputHandler)
@@ -172,15 +170,15 @@ TaskItem clangToolTask(const AnalyzeInputData &input,
.arg(process.commandLine().toUserOutput())
.arg(process.error())
.arg(process.cleanedStdOut());
- const ClangToolStorage *data = storage.activeStorage();
+ const ClangToolStorage &data = *storage;
QString message;
if (process.result() == ProcessResult::StartFailed)
- message = Tr::tr("An error occurred with the %1 process.").arg(data->name);
+ message = Tr::tr("An error occurred with the %1 process.").arg(data.name);
else if (process.result() == ProcessResult::FinishedWithError)
- message = Tr::tr("%1 finished with exit code: %2.").arg(data->name).arg(process.exitCode());
+ message = Tr::tr("%1 finished with exit code: %2.").arg(data.name).arg(process.exitCode());
else
- message = Tr::tr("%1 crashed.").arg(data->name);
- outputHandler({false, input.unit.file, data->outputFilePath, input.tool, message, details});
+ message = Tr::tr("%1 crashed.").arg(data.name);
+ outputHandler({false, input.unit.file, data.outputFilePath, input.tool, message, details});
};
const Group group {
diff --git a/src/plugins/clangtools/clangtools.qbs b/src/plugins/clangtools/clangtools.qbs
index 9c86d4bb113..ad8543bf611 100644
--- a/src/plugins/clangtools/clangtools.qbs
+++ b/src/plugins/clangtools/clangtools.qbs
@@ -74,9 +74,7 @@ QtcPlugin {
"virtualfilesystemoverlay.h",
]
- Group {
- name: "Unit tests"
- condition: qtc.testsEnabled
+ QtcTestFiles {
files: [
"clangtoolspreconfiguredsessiontests.cpp",
"clangtoolspreconfiguredsessiontests.h",
diff --git a/src/plugins/clangtools/clangtoolsdiagnosticmodel.cpp b/src/plugins/clangtools/clangtoolsdiagnosticmodel.cpp
index 39329c79f14..e0b797a1e0a 100644
--- a/src/plugins/clangtools/clangtoolsdiagnosticmodel.cpp
+++ b/src/plugins/clangtools/clangtoolsdiagnosticmodel.cpp
@@ -10,14 +10,14 @@
#include "diagnosticmark.h"
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
+
#include <texteditor/textmark.h>
#include <utils/fsengine/fileiconprovider.h>
#include <utils/qtcassert.h>
#include <utils/utilsicons.h>
-#include <QFileInfo>
#include <QLoggingCategory>
#include <tuple>
@@ -484,8 +484,8 @@ DiagnosticFilterModel::DiagnosticFilterModel(QObject *parent)
{
// So that when a user closes and re-opens a project and *then* clicks "Suppress",
// we enter that information into the project settings.
- connect(ProjectExplorer::SessionManager::instance(),
- &ProjectExplorer::SessionManager::projectAdded, this,
+ connect(ProjectExplorer::ProjectManager::instance(),
+ &ProjectExplorer::ProjectManager::projectAdded, this,
[this](ProjectExplorer::Project *project) {
if (!m_project && project->projectDirectory() == m_lastProjectDirectory)
setProject(project);
diff --git a/src/plugins/clangtools/clangtoolspreconfiguredsessiontests.cpp b/src/plugins/clangtools/clangtoolspreconfiguredsessiontests.cpp
index 5041dc4dacc..4d4b34c1ef8 100644
--- a/src/plugins/clangtools/clangtoolspreconfiguredsessiontests.cpp
+++ b/src/plugins/clangtools/clangtoolspreconfiguredsessiontests.cpp
@@ -5,15 +5,17 @@
#include "clangtool.h"
#include "clangtoolsdiagnostic.h"
-#include "clangtoolsutils.h"
#include <coreplugin/icore.h>
+
#include <cppeditor/compileroptionsbuilder.h>
#include <cppeditor/projectinfo.h>
+
#include <projectexplorer/kitinformation.h>
#include <projectexplorer/kitmanager.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorer.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/session.h>
#include <projectexplorer/target.h>
#include <projectexplorer/toolchain.h>
@@ -55,8 +57,8 @@ public:
WaitForParsedProjects(const FilePaths &projects)
: m_projectsToWaitFor(projects)
{
- connect(SessionManager::instance(),
- &ProjectExplorer::SessionManager::projectFinishedParsing,
+ connect(ProjectManager::instance(),
+ &ProjectExplorer::ProjectManager::projectFinishedParsing,
this, &WaitForParsedProjects::onProjectFinishedParsing);
}
@@ -87,9 +89,9 @@ void PreconfiguredSessionTests::initTestCase()
QSKIP("Session must not be already active.");
// Load session
- const FilePaths projects = SessionManager::projectsForSessionName(preconfiguredSessionName);
+ const FilePaths projects = ProjectManager::projectsForSessionName(preconfiguredSessionName);
WaitForParsedProjects waitForParsedProjects(projects);
- QVERIFY(SessionManager::loadSession(preconfiguredSessionName));
+ QVERIFY(ProjectManager::loadSession(preconfiguredSessionName));
QVERIFY(waitForParsedProjects.wait());
}
@@ -102,7 +104,7 @@ void PreconfiguredSessionTests::testPreconfiguredSession()
for (ClangTool * const tool : {ClangTidyTool::instance(), ClazyTool::instance()}) {
tool->startTool(ClangTool::FileSelectionType::AllFiles);
- QSignalSpy waitUntilAnalyzerFinished(tool, SIGNAL(finished(bool)));
+ QSignalSpy waitUntilAnalyzerFinished(tool, &ClangTool::finished);
QVERIFY(waitUntilAnalyzerFinished.wait(30000));
const QList<QVariant> arguments = waitUntilAnalyzerFinished.takeFirst();
const bool analyzerFinishedSuccessfully = arguments.first().toBool();
@@ -175,7 +177,7 @@ void PreconfiguredSessionTests::testPreconfiguredSession_data()
bool hasAddedTestData = false;
- for (Project *project : validProjects(SessionManager::projects())) {
+ for (Project *project : validProjects(ProjectManager::projects())) {
for (Target *target : validTargets(project)) {
hasAddedTestData = true;
QTest::newRow(dataTagName(project, target)) << project << target;
@@ -189,17 +191,17 @@ void PreconfiguredSessionTests::testPreconfiguredSession_data()
bool PreconfiguredSessionTests::switchToProjectAndTarget(Project *project,
Target *target)
{
- Project * const activeProject = SessionManager::startupProject();
+ Project * const activeProject = ProjectManager::startupProject();
if (project == activeProject && target == activeProject->activeTarget())
return true; // OK, desired project/target already active.
if (project != activeProject)
- SessionManager::setStartupProject(project);
+ ProjectManager::setStartupProject(project);
if (target != project->activeTarget()) {
- QSignalSpy spyFinishedParsing(ProjectExplorer::SessionManager::instance(),
- &ProjectExplorer::SessionManager::projectFinishedParsing);
- SessionManager::setActiveTarget(project, target, ProjectExplorer::SetActive::NoCascade);
+ QSignalSpy spyFinishedParsing(ProjectExplorer::ProjectManager::instance(),
+ &ProjectExplorer::ProjectManager::projectFinishedParsing);
+ project->setActiveTarget(target, ProjectExplorer::SetActive::NoCascade);
QTC_ASSERT(spyFinishedParsing.wait(30000), return false);
const QVariant projectArgument = spyFinishedParsing.takeFirst().constFirst();
diff --git a/src/plugins/clangtools/clangtoolsprojectsettings.cpp b/src/plugins/clangtools/clangtoolsprojectsettings.cpp
index cde1731acbd..581a8ca57b6 100644
--- a/src/plugins/clangtools/clangtoolsprojectsettings.cpp
+++ b/src/plugins/clangtools/clangtoolsprojectsettings.cpp
@@ -4,7 +4,7 @@
#include "clangtoolsprojectsettings.h"
#include "clangtoolsdiagnostic.h"
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
diff --git a/src/plugins/clangtools/clangtoolsutils.cpp b/src/plugins/clangtools/clangtoolsutils.cpp
index 01dcb614b5a..acfd768797b 100644
--- a/src/plugins/clangtools/clangtoolsutils.cpp
+++ b/src/plugins/clangtools/clangtoolsutils.cpp
@@ -284,7 +284,7 @@ static QStringList extraOptions(const QString &envVar)
if (!qtcEnvironmentVariableIsSet(envVar))
return QStringList();
QString arguments = qtcEnvironmentVariable(envVar);
- return Utils::ProcessArgs::splitArgs(arguments);
+ return ProcessArgs::splitArgs(arguments, HostOsInfo::hostOs());
}
QStringList extraClangToolsPrependOptions()
diff --git a/src/plugins/clangtools/diagnosticconfigswidget.cpp b/src/plugins/clangtools/diagnosticconfigswidget.cpp
index 1d160befa22..a2b55be1b4b 100644
--- a/src/plugins/clangtools/diagnosticconfigswidget.cpp
+++ b/src/plugins/clangtools/diagnosticconfigswidget.cpp
@@ -14,8 +14,8 @@
#include <cppeditor/cppeditorconstants.h>
#include <cppeditor/cpptoolsreuse.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/selectablefilesmodel.h>
-#include <projectexplorer/session.h>
#include <utils/algorithm.h>
#include <utils/fancylineedit.h>
@@ -1294,7 +1294,7 @@ void disableChecks(const QList<Diagnostic> &diagnostics)
Utils::Id activeConfigId = settings->runSettings().diagnosticConfigId();
ClangToolsProjectSettings::ClangToolsProjectSettingsPtr projectSettings;
- if (ProjectExplorer::Project *project = ProjectExplorer::SessionManager::projectForFile(
+ if (ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::projectForFile(
diagnostics.first().location.filePath)) {
projectSettings = ClangToolsProjectSettings::getSettings(project);
if (!projectSettings->useGlobalSettings())
diff --git a/src/plugins/clangtools/documentclangtoolrunner.cpp b/src/plugins/clangtools/documentclangtoolrunner.cpp
index 81a7e8f5444..6a08b579f18 100644
--- a/src/plugins/clangtools/documentclangtoolrunner.cpp
+++ b/src/plugins/clangtools/documentclangtoolrunner.cpp
@@ -18,7 +18,7 @@
#include <projectexplorer/buildconfiguration.h>
#include <projectexplorer/buildtargettype.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <texteditor/textdocument.h>
@@ -97,8 +97,8 @@ void DocumentClangToolRunner::scheduleRun()
static Project *findProject(const FilePath &file)
{
- Project *project = SessionManager::projectForFile(file);
- return project ? project : SessionManager::startupProject();
+ Project *project = ProjectManager::projectForFile(file);
+ return project ? project : ProjectManager::startupProject();
}
static VirtualFileSystemOverlay &vfso()
diff --git a/src/plugins/clangtools/unit-tests/exported-diagnostics/CMakeLists.txt b/src/plugins/clangtools/unit-tests/exported-diagnostics/CMakeLists.txt
index 30efc996911..98f9833492d 100644
--- a/src/plugins/clangtools/unit-tests/exported-diagnostics/CMakeLists.txt
+++ b/src/plugins/clangtools/unit-tests/exported-diagnostics/CMakeLists.txt
@@ -11,7 +11,7 @@ set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
-find_package(Qt5 COMPONENTS Widgets REQUIRED)
+find_package(Qt6 COMPONENTS Widgets REQUIRED)
add_executable(clangtools
main.cpp
diff --git a/src/plugins/classview/classviewmanager.cpp b/src/plugins/classview/classviewmanager.cpp
index a98cec70442..7f4eeb955c6 100644
--- a/src/plugins/classview/classviewmanager.cpp
+++ b/src/plugins/classview/classviewmanager.cpp
@@ -8,8 +8,11 @@
#include <cppeditor/cppeditorconstants.h>
#include <cppeditor/cppmodelmanager.h>
+
#include <coreplugin/progressmanager/progressmanager.h>
-#include <projectexplorer/session.h>
+
+#include <projectexplorer/projectmanager.h>
+
#include <texteditor/texteditor.h>
#include <QThread>
@@ -90,7 +93,7 @@ void ManagerPrivate::resetParser()
cancelScheduledUpdate();
QHash<FilePath, QPair<QString, FilePaths>> projectData;
- for (const Project *project : SessionManager::projects()) {
+ for (const Project *project : ProjectManager::projects()) {
projectData.insert(project->projectFilePath(),
{project->displayName(), project->files(Project::SourceFiles)});
}
@@ -201,8 +204,8 @@ void Manager::initialize()
d->m_timer.setSingleShot(true);
// connections to enable/disable navi widget factory
- SessionManager *sessionManager = SessionManager::instance();
- connect(sessionManager, &SessionManager::projectAdded,
+ ProjectManager *sessionManager = ProjectManager::instance();
+ connect(sessionManager, &ProjectManager::projectAdded,
this, [this](Project *project) {
const FilePath projectPath = project->projectFilePath();
const QString projectName = project->displayName();
@@ -211,7 +214,7 @@ void Manager::initialize()
d->m_parser->addProject(projectPath, projectName, projectFiles);
}, Qt::QueuedConnection);
});
- connect(sessionManager, &SessionManager::projectRemoved,
+ connect(sessionManager, &ProjectManager::projectRemoved,
this, [this](Project *project) {
const FilePath projectPath = project->projectFilePath();
QMetaObject::invokeMethod(d->m_parser, [this, projectPath]() {
diff --git a/src/plugins/classview/classviewparsertreeitem.cpp b/src/plugins/classview/classviewparsertreeitem.cpp
index 1d97d0c013b..787b63b3c84 100644
--- a/src/plugins/classview/classviewparsertreeitem.cpp
+++ b/src/plugins/classview/classviewparsertreeitem.cpp
@@ -7,9 +7,11 @@
#include <cplusplus/Icons.h>
#include <cplusplus/Overview.h>
+
#include <projectexplorer/project.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/projectnodes.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <QDebug>
#include <QHash>
@@ -261,7 +263,7 @@ bool ParserTreeItem::canFetchMore(QStandardItem *item) const
*/
void ParserTreeItem::fetchMore(QStandardItem *item) const
{
- using ProjectExplorer::SessionManager;
+ using ProjectExplorer::ProjectManager;
if (!item)
return;
@@ -283,7 +285,7 @@ void ParserTreeItem::fetchMore(QStandardItem *item) const
// icon
const Utils::FilePath &filePath = ptr->projectFilePath();
if (!filePath.isEmpty()) {
- ProjectExplorer::Project *project = SessionManager::projectForFile(filePath);
+ ProjectExplorer::Project *project = ProjectManager::projectForFile(filePath);
if (project)
add->setIcon(project->containerNode()->icon());
}
diff --git a/src/plugins/clearcase/clearcaseplugin.cpp b/src/plugins/clearcase/clearcaseplugin.cpp
index de884452be6..4c810544b3f 100644
--- a/src/plugins/clearcase/clearcaseplugin.cpp
+++ b/src/plugins/clearcase/clearcaseplugin.cpp
@@ -27,16 +27,16 @@
#include <texteditor/textdocument.h>
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <utils/algorithm.h>
+#include <utils/asynctask.h>
#include <utils/hostosinfo.h>
#include <utils/infobar.h>
#include <utils/layoutbuilder.h>
#include <utils/parameteraction.h>
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
-#include <utils/runextensions.h>
#include <utils/temporarydirectory.h>
#include <vcsbase/basevcseditorfactory.h>
@@ -259,7 +259,7 @@ private:
CommandResult runCleartool(const FilePath &workingDir, const QStringList &arguments,
VcsBase::RunFlags flags = VcsBase::RunFlags::None,
QTextCodec *codec = nullptr, int timeoutMultiplier = 1) const;
- static void sync(QFutureInterface<void> &future, QStringList files);
+ static void sync(QPromise<void> &promise, QStringList files);
void history(const FilePath &workingDir,
const QStringList &file = {},
@@ -488,7 +488,7 @@ FileStatus::Status ClearCasePluginPrivate::getFileStatus(const QString &fileName
/// "cleartool pwv" returns the values for "set view" and "working directory view", also for
/// snapshot views.
///
-/// \returns The ClearCase topLevel/VOB directory for this directory
+/// Returns the ClearCase topLevel/VOB directory for this directory.
QString ClearCasePluginPrivate::ccManagesDirectory(const FilePath &directory) const
{
const CommandResult result = runCleartoolProc(directory, {"pwv"});
@@ -595,7 +595,7 @@ ClearCasePluginPrivate::ClearCasePluginPrivate()
m_settings.fromSettings(ICore::settings());
// update view name when changing active project
- connect(SessionManager::instance(), &SessionManager::startupProjectChanged,
+ connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged,
this, &ClearCasePluginPrivate::projectChanged);
const QString description = QLatin1String("ClearCase");
@@ -1657,7 +1657,7 @@ bool ClearCasePluginPrivate::vcsOpen(const FilePath &workingDir, const QString &
if (!m_settings.disableIndexer &&
(fi.isWritable() || vcsStatus(absPath).status == FileStatus::Unknown))
- runAsync(sync, QStringList(absPath)).waitForFinished();
+ Utils::asyncRun(sync, QStringList(absPath)).waitForFinished();
if (vcsStatus(absPath).status == FileStatus::CheckedOut) {
QMessageBox::information(ICore::dialogParent(), Tr::tr("ClearCase Checkout"),
Tr::tr("File is already checked out."));
@@ -2124,12 +2124,13 @@ void ClearCasePluginPrivate::updateIndex()
{
QTC_ASSERT(currentState().hasTopLevel(), return);
ProgressManager::cancelTasks(ClearCase::Constants::TASK_INDEX);
- Project *project = SessionManager::startupProject();
+ Project *project = ProjectManager::startupProject();
if (!project)
return;
m_checkInAllAction->setEnabled(false);
m_statusMap->clear();
- QFuture<void> result = runAsync(sync, transform(project->files(Project::SourceFiles), &FilePath::toString));
+ QFuture<void> result = Utils::asyncRun(sync, transform(project->files(Project::SourceFiles),
+ &FilePath::toString));
if (!m_settings.disableIndexer)
ProgressManager::addTask(result, Tr::tr("Updating ClearCase Index"), ClearCase::Constants::TASK_INDEX);
}
@@ -2261,7 +2262,7 @@ void ClearCasePluginPrivate::syncSlot()
FilePath topLevel = state.topLevel();
if (topLevel != state.currentProjectTopLevel())
return;
- runAsync(sync, QStringList());
+ Utils::asyncRun(sync, QStringList()); // TODO: make use of returned QFuture
}
void ClearCasePluginPrivate::closing()
@@ -2271,12 +2272,12 @@ void ClearCasePluginPrivate::closing()
disconnect(qApp, &QApplication::applicationStateChanged, nullptr, nullptr);
}
-void ClearCasePluginPrivate::sync(QFutureInterface<void> &future, QStringList files)
+void ClearCasePluginPrivate::sync(QPromise<void> &promise, QStringList files)
{
ClearCasePluginPrivate *plugin = ClearCasePluginPrivate::instance();
ClearCaseSync ccSync(plugin->m_statusMap);
connect(&ccSync, &ClearCaseSync::updateStreamAndView, plugin, &ClearCasePluginPrivate::updateStreamAndView);
- ccSync.run(future, files);
+ ccSync.run(promise, files);
}
QString ClearCasePluginPrivate::displayName() const
diff --git a/src/plugins/clearcase/clearcasesync.cpp b/src/plugins/clearcase/clearcasesync.cpp
index 388ce1a8c18..90dad2b988c 100644
--- a/src/plugins/clearcase/clearcasesync.cpp
+++ b/src/plugins/clearcase/clearcasesync.cpp
@@ -13,6 +13,8 @@
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
+#include <QPromise>
+
#ifdef WITH_TESTS
#include <QTest>
#include <utils/fileutils.h>
@@ -22,8 +24,7 @@ using namespace Utils;
namespace ClearCase::Internal {
-static void runProcess(QFutureInterface<void> &future,
- const ClearCaseSettings &settings,
+static void runProcess(QPromise<void> &promise, const ClearCaseSettings &settings,
const QStringList &args,
std::function<void(const QString &buffer, int processed)> processLine)
{
@@ -37,7 +38,7 @@ static void runProcess(QFutureInterface<void> &future,
int processed = 0;
QString buffer;
- while (process.waitForReadyRead() && !future.isCanceled()) {
+ while (process.waitForReadyRead() && !promise.isCanceled()) {
buffer += QString::fromLocal8Bit(process.readAllRawStandardOutput());
int index = buffer.indexOf('\n');
while (index != -1) {
@@ -135,7 +136,7 @@ void ClearCaseSync::updateStatusForNotManagedFiles(const QStringList &files)
}
}
-void ClearCaseSync::syncSnapshotView(QFutureInterface<void> &future, QStringList &files,
+void ClearCaseSync::syncSnapshotView(QPromise<void> &promise, QStringList &files,
const ClearCaseSettings &settings)
{
const QString view = ClearCasePlugin::viewData().name;
@@ -167,18 +168,18 @@ void ClearCaseSync::syncSnapshotView(QFutureInterface<void> &future, QStringList
// adding 1 for initial sync in which total is not accurate, to prevent finishing
// (we don't want it to become green)
- future.setProgressRange(0, totalFileCount + 1);
+ promise.setProgressRange(0, totalFileCount + 1);
int totalProcessed = 0;
- runProcess(future, settings, args, [&](const QString &buffer, int processed) {
+ runProcess(promise, settings, args, [&](const QString &buffer, int processed) {
processCleartoolLsLine(viewRootDir, buffer);
- future.setProgressValue(qMin(totalFileCount, processed));
+ promise.setProgressValue(qMin(totalFileCount, processed));
totalProcessed = processed;
});
- if (!future.isCanceled()) {
+ if (!promise.isCanceled()) {
updateStatusForNotManagedFiles(files);
- future.setProgressValue(totalFileCount + 1);
+ promise.setProgressValue(totalFileCount + 1);
if (!hot)
updateTotalFilesCount(view, settings, totalProcessed);
}
@@ -193,21 +194,20 @@ void ClearCaseSync::processCleartoolLscheckoutLine(const QString &buffer)
///
/// Update the file status for dynamic views.
///
-void ClearCaseSync::syncDynamicView(QFutureInterface<void> &future,
- const ClearCaseSettings& settings)
+void ClearCaseSync::syncDynamicView(QPromise<void> &promise, const ClearCaseSettings& settings)
{
// Always invalidate status for all files
invalidateStatusAllFiles();
const QStringList args({"lscheckout", "-avobs", "-me", "-cview", "-s"});
- runProcess(future, settings, args, [&](const QString &buffer, int processed) {
+ runProcess(promise, settings, args, [&](const QString &buffer, int processed) {
processCleartoolLscheckoutLine(buffer);
- future.setProgressValue(processed);
+ promise.setProgressValue(processed);
});
}
-void ClearCaseSync::run(QFutureInterface<void> &future, QStringList &files)
+void ClearCaseSync::run(QPromise<void> &promise, QStringList &files)
{
ClearCaseSettings settings = ClearCasePlugin::settings();
if (settings.disableIndexer)
@@ -225,9 +225,9 @@ void ClearCaseSync::run(QFutureInterface<void> &future, QStringList &files)
emit updateStreamAndView();
if (ClearCasePlugin::viewData().isDynamic)
- syncDynamicView(future, settings);
+ syncDynamicView(promise, settings);
else
- syncSnapshotView(future, files, settings);
+ syncSnapshotView(promise, files, settings);
}
#ifdef WITH_TESTS
diff --git a/src/plugins/clearcase/clearcasesync.h b/src/plugins/clearcase/clearcasesync.h
index a008dabb600..c2862d9d404 100644
--- a/src/plugins/clearcase/clearcasesync.h
+++ b/src/plugins/clearcase/clearcasesync.h
@@ -9,6 +9,8 @@ QT_BEGIN_NAMESPACE
class QDir;
template <typename T>
class QFutureInterface;
+template <typename T>
+class QPromise;
QT_END_NAMESPACE
namespace ClearCase::Internal {
@@ -18,7 +20,7 @@ class ClearCaseSync : public QObject
Q_OBJECT
public:
explicit ClearCaseSync(QSharedPointer<StatusMap> statusMap);
- void run(QFutureInterface<void> &future, QStringList &files);
+ void run(QPromise<void> &promise, QStringList &files);
QStringList updateStatusHotFiles(const QString &viewRoot, int &total);
void invalidateStatus(const QDir &viewRootDir, const QStringList &files);
@@ -28,9 +30,8 @@ public:
const int processed);
void updateStatusForNotManagedFiles(const QStringList &files);
- void syncDynamicView(QFutureInterface<void> &future,
- const ClearCaseSettings &settings);
- void syncSnapshotView(QFutureInterface<void> &future, QStringList &files,
+ void syncDynamicView(QPromise<void> &promise, const ClearCaseSettings &settings);
+ void syncSnapshotView(QPromise<void> &promise, QStringList &files,
const ClearCaseSettings &settings);
void processCleartoolLscheckoutLine(const QString &buffer);
diff --git a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp
index fb3a098e54f..7cf47dc62d0 100644
--- a/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp
+++ b/src/plugins/cmakeprojectmanager/cmakebuildconfiguration.cpp
@@ -41,7 +41,7 @@
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/projectexplorertr.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <projectexplorer/taskhub.h>
@@ -643,7 +643,7 @@ void CMakeBuildSettingsWidget::updateInitialCMakeArguments()
// As the user would expect to have e.g. "--preset" from "Initial Configuration"
// to "Current Configuration" as additional parameters
m_buildSystem->setAdditionalCMakeArguments(ProcessArgs::splitArgs(
- bc->aspect<InitialCMakeArgumentsAspect>()->value()));
+ bc->aspect<InitialCMakeArgumentsAspect>()->value(), HostOsInfo::hostOs()));
}
void CMakeBuildSettingsWidget::kitCMakeConfiguration()
@@ -826,9 +826,10 @@ void CMakeBuildSettingsWidget::updateFromKit()
// Then the additional parameters
const QStringList additionalKitCMake = ProcessArgs::splitArgs(
- CMakeConfigurationKitAspect::additionalConfiguration(k));
+ CMakeConfigurationKitAspect::additionalConfiguration(k), HostOsInfo::hostOs());
const QStringList additionalInitialCMake = ProcessArgs::splitArgs(
- m_buildSystem->buildConfiguration()->aspect<InitialCMakeArgumentsAspect>()->value());
+ m_buildSystem->buildConfiguration()->aspect<InitialCMakeArgumentsAspect>()->value(),
+ HostOsInfo::hostOs());
QStringList mergedArgumentList;
std::set_union(additionalInitialCMake.begin(),
@@ -1734,7 +1735,8 @@ void CMakeBuildSystem::setInitialCMakeArguments(const QStringList &args)
QStringList CMakeBuildSystem::additionalCMakeArguments() const
{
- return ProcessArgs::splitArgs(buildConfiguration()->aspect<AdditionalCMakeOptionsAspect>()->value());
+ return ProcessArgs::splitArgs(buildConfiguration()->aspect<AdditionalCMakeOptionsAspect>()->value(),
+ HostOsInfo::hostOs());
}
void CMakeBuildSystem::setAdditionalCMakeArguments(const QStringList &args)
@@ -1757,7 +1759,8 @@ void CMakeBuildSystem::filterConfigArgumentsFromAdditionalCMakeArguments()
// which is already part of the CMake variables and should not be also
// in the addtional CMake options
const QStringList arguments = ProcessArgs::splitArgs(
- buildConfiguration()->aspect<AdditionalCMakeOptionsAspect>()->value());
+ buildConfiguration()->aspect<AdditionalCMakeOptionsAspect>()->value(),
+ HostOsInfo::hostOs());
QStringList unknownOptions;
const CMakeConfig config = CMakeConfig::fromArguments(arguments, unknownOptions);
@@ -2153,7 +2156,7 @@ const QStringList InitialCMakeArgumentsAspect::allValues() const
return ci.toArgument(nullptr);
});
- initialCMakeArguments.append(ProcessArgs::splitArgs(value()));
+ initialCMakeArguments.append(ProcessArgs::splitArgs(value(), HostOsInfo::hostOs()));
return initialCMakeArguments;
}
diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp
index 726414dafc3..ebc41fea787 100644
--- a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp
+++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp
@@ -601,7 +601,7 @@ void CMakeBuildSystem::updateProjectData()
for (RawProjectPart &rpp : rpps) {
rpp.setQtVersion(
kitInfo.projectPartQtVersion); // TODO: Check if project actually uses Qt.
- const QString includeFileBaseDir = buildConfiguration()->buildDirectory().toString();
+ const FilePath includeFileBaseDir = buildConfiguration()->buildDirectory();
QStringList cxxFlags = rpp.flagsForCxx.commandLineFlags;
QStringList cFlags = rpp.flagsForC.commandLineFlags;
addTargetFlagForIos(cxxFlags, cFlags, this, [this] {
diff --git a/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp b/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp
index 325a6012a53..01fbeaa2329 100644
--- a/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp
+++ b/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp
@@ -8,8 +8,9 @@
#include "cmaketool.h"
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
+
#include <texteditor/codeassist/assistinterface.h>
#include <QFileInfo>
@@ -39,7 +40,7 @@ IAssistProposal *CMakeFileCompletionAssist::performAsync()
Keywords kw;
const Utils::FilePath &filePath = interface()->filePath();
if (!filePath.isEmpty() && filePath.toFileInfo().isFile()) {
- Project *p = SessionManager::projectForFile(filePath);
+ Project *p = ProjectManager::projectForFile(filePath);
if (p && p->activeTarget()) {
CMakeTool *cmake = CMakeKitAspect::cmakeTool(p->activeTarget()->kit());
if (cmake && cmake->isValid())
diff --git a/src/plugins/cmakeprojectmanager/cmakelocatorfilter.cpp b/src/plugins/cmakeprojectmanager/cmakelocatorfilter.cpp
index ab646d1fa69..7141fb276cb 100644
--- a/src/plugins/cmakeprojectmanager/cmakelocatorfilter.cpp
+++ b/src/plugins/cmakeprojectmanager/cmakelocatorfilter.cpp
@@ -8,15 +8,14 @@
#include "cmakeproject.h"
#include "cmakeprojectmanagertr.h"
-#include <coreplugin/editormanager/editormanager.h>
-
#include <projectexplorer/buildmanager.h>
#include <projectexplorer/buildsteplist.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <utils/algorithm.h>
+using namespace Core;
using namespace ProjectExplorer;
using namespace Utils;
@@ -28,9 +27,9 @@ namespace CMakeProjectManager::Internal {
CMakeTargetLocatorFilter::CMakeTargetLocatorFilter()
{
- connect(SessionManager::instance(), &SessionManager::projectAdded,
+ connect(ProjectManager::instance(), &ProjectManager::projectAdded,
this, &CMakeTargetLocatorFilter::projectListUpdated);
- connect(SessionManager::instance(), &SessionManager::projectRemoved,
+ connect(ProjectManager::instance(), &ProjectManager::projectRemoved,
this, &CMakeTargetLocatorFilter::projectListUpdated);
// Initialize the filter
@@ -40,7 +39,7 @@ CMakeTargetLocatorFilter::CMakeTargetLocatorFilter()
void CMakeTargetLocatorFilter::prepareSearch(const QString &entry)
{
m_result.clear();
- const QList<Project *> projects = SessionManager::projects();
+ const QList<Project *> projects = ProjectManager::projects();
for (Project *p : projects) {
auto cmakeProject = qobject_cast<const CMakeProject *>(p);
if (!cmakeProject || !cmakeProject->activeTarget())
@@ -57,17 +56,13 @@ void CMakeTargetLocatorFilter::prepareSearch(const QString &entry)
if (index >= 0) {
const FilePath path = target.backtrace.isEmpty() ? cmakeProject->projectFilePath()
: target.backtrace.last().path;
- const int line = target.backtrace.isEmpty() ? -1 : target.backtrace.last().line;
-
- QVariantMap extraData;
- extraData.insert("project", cmakeProject->projectFilePath().toString());
- extraData.insert("line", line);
- extraData.insert("file", path.toString());
+ const int line = target.backtrace.isEmpty() ? 0 : target.backtrace.last().line;
- Core::LocatorFilterEntry filterEntry(this, target.title, extraData);
+ LocatorFilterEntry filterEntry(this, target.title);
+ filterEntry.linkForEditor = {path, line};
filterEntry.extraInfo = path.shortNativePath();
filterEntry.highlightInfo = {index, int(entry.length())};
- filterEntry.filePath = path;
+ filterEntry.filePath = cmakeProject->projectFilePath();
m_result.append(filterEntry);
}
@@ -75,7 +70,8 @@ void CMakeTargetLocatorFilter::prepareSearch(const QString &entry)
}
}
-QList<Core::LocatorFilterEntry> CMakeTargetLocatorFilter::matchesFor(QFutureInterface<Core::LocatorFilterEntry> &future, const QString &entry)
+QList<LocatorFilterEntry> CMakeTargetLocatorFilter::matchesFor(
+ QFutureInterface<LocatorFilterEntry> &future, const QString &entry)
{
Q_UNUSED(future)
Q_UNUSED(entry)
@@ -85,7 +81,8 @@ QList<Core::LocatorFilterEntry> CMakeTargetLocatorFilter::matchesFor(QFutureInte
void CMakeTargetLocatorFilter::projectListUpdated()
{
// Enable the filter if there's at least one CMake project
- setEnabled(Utils::contains(SessionManager::projects(), [](Project *p) { return qobject_cast<CMakeProject *>(p); }));
+ setEnabled(Utils::contains(ProjectManager::projects(),
+ [](Project *p) { return qobject_cast<CMakeProject *>(p); }));
}
// --------------------------------------------------------------------
@@ -101,21 +98,18 @@ BuildCMakeTargetLocatorFilter::BuildCMakeTargetLocatorFilter()
setPriority(High);
}
-void BuildCMakeTargetLocatorFilter::accept(const Core::LocatorFilterEntry &selection,
- QString *newText,
- int *selectionStart,
- int *selectionLength) const
+void BuildCMakeTargetLocatorFilter::accept(const LocatorFilterEntry &selection, QString *newText,
+ int *selectionStart, int *selectionLength) const
{
Q_UNUSED(newText)
Q_UNUSED(selectionStart)
Q_UNUSED(selectionLength)
- const QVariantMap extraData = selection.internalData.toMap();
- const FilePath projectPath = FilePath::fromString(extraData.value("project").toString());
+ const FilePath projectPath = selection.filePath;
// Get the project containing the target selected
const auto cmakeProject = qobject_cast<CMakeProject *>(
- Utils::findOrDefault(SessionManager::projects(), [projectPath](Project *p) {
+ Utils::findOrDefault(ProjectManager::projects(), [projectPath](Project *p) {
return p->projectFilePath() == projectPath;
}));
if (!cmakeProject || !cmakeProject->activeTarget()
@@ -151,25 +145,4 @@ OpenCMakeTargetLocatorFilter::OpenCMakeTargetLocatorFilter()
setPriority(Medium);
}
-void OpenCMakeTargetLocatorFilter::accept(const Core::LocatorFilterEntry &selection,
- QString *newText,
- int *selectionStart,
- int *selectionLength) const
-{
- Q_UNUSED(newText)
- Q_UNUSED(selectionStart)
- Q_UNUSED(selectionLength)
-
- const QVariantMap extraData = selection.internalData.toMap();
- const int line = extraData.value("line").toInt();
- const auto file = FilePath::fromVariant(extraData.value("file"));
-
- if (line >= 0)
- Core::EditorManager::openEditorAt({file, line},
- {},
- Core::EditorManager::AllowExternalEditor);
- else
- Core::EditorManager::openEditor(file, {}, Core::EditorManager::AllowExternalEditor);
-}
-
} // CMakeProjectManager::Internal
diff --git a/src/plugins/cmakeprojectmanager/cmakelocatorfilter.h b/src/plugins/cmakeprojectmanager/cmakelocatorfilter.h
index 0a74eeafeb3..b8a748be85a 100644
--- a/src/plugins/cmakeprojectmanager/cmakelocatorfilter.h
+++ b/src/plugins/cmakeprojectmanager/cmakelocatorfilter.h
@@ -37,11 +37,6 @@ class OpenCMakeTargetLocatorFilter : CMakeTargetLocatorFilter
{
public:
OpenCMakeTargetLocatorFilter();
-
- void accept(const Core::LocatorFilterEntry &selection,
- QString *newText,
- int *selectionStart,
- int *selectionLength) const final;
};
} // CMakeProjectManager::Internal
diff --git a/src/plugins/cmakeprojectmanager/cmakeproject.cpp b/src/plugins/cmakeprojectmanager/cmakeproject.cpp
index b0fab0894ae..be78fe28d34 100644
--- a/src/plugins/cmakeprojectmanager/cmakeproject.cpp
+++ b/src/plugins/cmakeprojectmanager/cmakeproject.cpp
@@ -7,16 +7,17 @@
#include "cmakeprojectconstants.h"
#include "cmakeprojectimporter.h"
#include "cmakeprojectmanagertr.h"
-#include "cmaketool.h"
#include <coreplugin/icontext.h>
#include <projectexplorer/buildconfiguration.h>
+#include <projectexplorer/buildinfo.h>
#include <projectexplorer/buildsteplist.h>
#include <projectexplorer/kitinformation.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/projectnodes.h>
#include <projectexplorer/target.h>
#include <projectexplorer/taskhub.h>
+#include <qtsupport/qtkitinformation.h>
using namespace ProjectExplorer;
using namespace Utils;
@@ -276,4 +277,17 @@ ProjectExplorer::DeploymentKnowledge CMakeProject::deploymentKnowledge() const
: DeploymentKnowledge::Bad;
}
+void CMakeProject::configureAsExampleProject(ProjectExplorer::Kit *kit)
+{
+ QList<BuildInfo> infoList;
+ const QList<Kit *> kits(kit != nullptr ? QList<Kit *>({kit}) : KitManager::kits());
+ for (Kit *k : kits) {
+ if (QtSupport::QtKitAspect::qtVersion(k) != nullptr) {
+ if (auto factory = BuildConfigurationFactory::find(k, projectFilePath()))
+ infoList << factory->allAvailableSetups(k, projectFilePath());
+ }
+ }
+ setup(infoList);
+}
+
} // namespace CMakeProjectManager
diff --git a/src/plugins/cmakeprojectmanager/cmakeproject.h b/src/plugins/cmakeprojectmanager/cmakeproject.h
index 2885b9eb4b1..09154615013 100644
--- a/src/plugins/cmakeprojectmanager/cmakeproject.h
+++ b/src/plugins/cmakeprojectmanager/cmakeproject.h
@@ -36,6 +36,8 @@ protected:
private:
ProjectExplorer::DeploymentKnowledge deploymentKnowledge() const override;
+ void configureAsExampleProject(ProjectExplorer::Kit *kit) override;
+
Internal::PresetsData combinePresets(Internal::PresetsData &cmakePresetsData,
Internal::PresetsData &cmakeUserPresetsData);
void setupBuildPresets(Internal::PresetsData &presetsData);
diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectimporter.h b/src/plugins/cmakeprojectmanager/cmakeprojectimporter.h
index 7c7716dc8b2..229ccfa36d5 100644
--- a/src/plugins/cmakeprojectmanager/cmakeprojectimporter.h
+++ b/src/plugins/cmakeprojectmanager/cmakeprojectimporter.h
@@ -4,10 +4,11 @@
#pragma once
#include "presetsparser.h"
-#include "utils/temporarydirectory.h"
#include <qtsupport/qtprojectimporter.h>
+#include <utils/temporarydirectory.h>
+
namespace CMakeProjectManager {
class CMakeTool;
diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.cpp b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.cpp
index e05e155cf6f..95baccee1e9 100644
--- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.cpp
+++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.cpp
@@ -16,12 +16,14 @@
#include <coreplugin/editormanager/ieditor.h>
#include <coreplugin/icore.h>
#include <coreplugin/messagemanager.h>
+
#include <cppeditor/cpptoolsreuse.h>
+
#include <projectexplorer/buildmanager.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectexplorerconstants.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/projecttree.h>
-#include <projectexplorer/session.h>
#include <projectexplorer/target.h>
#include <utils/parameteraction.h>
@@ -59,7 +61,7 @@ CMakeManager::CMakeManager()
command->setAttribute(Core::Command::CA_Hide);
mbuild->addAction(command, ProjectExplorer::Constants::G_BUILD_BUILD);
connect(m_runCMakeAction, &QAction::triggered, this, [this] {
- runCMake(SessionManager::startupBuildSystem());
+ runCMake(ProjectManager::startupBuildSystem());
});
command = Core::ActionManager::registerAction(m_clearCMakeCacheAction,
@@ -68,7 +70,7 @@ CMakeManager::CMakeManager()
command->setAttribute(Core::Command::CA_Hide);
mbuild->addAction(command, ProjectExplorer::Constants::G_BUILD_BUILD);
connect(m_clearCMakeCacheAction, &QAction::triggered, this, [this] {
- clearCMakeCache(SessionManager::startupBuildSystem());
+ clearCMakeCache(ProjectManager::startupBuildSystem());
});
command = Core::ActionManager::registerAction(m_runCMakeActionContextMenu,
@@ -111,7 +113,7 @@ CMakeManager::CMakeManager()
mbuild->addAction(command, ProjectExplorer::Constants::G_BUILD_BUILD);
connect(m_buildFileAction, &QAction::triggered, this, [this] { buildFile(); });
- connect(SessionManager::instance(), &SessionManager::startupProjectChanged, this, [this] {
+ connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged, this, [this] {
updateCmakeActions(ProjectTree::currentNode());
});
connect(BuildManager::instance(), &BuildManager::buildStateChanged, this, [this] {
@@ -127,7 +129,7 @@ CMakeManager::CMakeManager()
void CMakeManager::updateCmakeActions(Node *node)
{
- auto project = qobject_cast<CMakeProject *>(SessionManager::startupProject());
+ auto project = qobject_cast<CMakeProject *>(ProjectManager::startupProject());
const bool visible = project && !BuildManager::isBuilding(project);
m_runCMakeAction->setVisible(visible);
m_runCMakeActionContextMenu->setEnabled(visible);
diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectnodes.cpp b/src/plugins/cmakeprojectmanager/cmakeprojectnodes.cpp
index 9c0d687ca6f..10f18fbf675 100644
--- a/src/plugins/cmakeprojectmanager/cmakeprojectnodes.cpp
+++ b/src/plugins/cmakeprojectmanager/cmakeprojectnodes.cpp
@@ -198,6 +198,10 @@ void CMakeTargetNode::setTargetInformation(const QList<FilePath> &artifacts, con
m_tooltip += Tr::tr("Build artifacts:") + "<br>" + tmp.join("<br>");
m_artifact = artifacts.first();
}
+ if (type == "EXECUTABLE")
+ setProductType(ProductType::App);
+ else if (type == "SHARED_LIBRARY" || type == "STATIC_LIBRARY")
+ setProductType(ProductType::Lib);
}
} // CMakeProjectManager::Internal
diff --git a/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp b/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp
index 9293831fdcf..ab79be78aea 100644
--- a/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp
+++ b/src/plugins/cmakeprojectmanager/fileapidataextractor.cpp
@@ -245,7 +245,7 @@ QList<CMakeBuildTarget> generateBuildTargets(const PreprocessedData &input,
continue;
// CMake sometimes mixes several shell-escaped pieces into one fragment. Disentangle that again:
- const QStringList parts = ProcessArgs::splitArgs(f.fragment);
+ const QStringList parts = ProcessArgs::splitArgs(f.fragment, HostOsInfo::hostOs());
for (QString part : parts) {
// Library search paths that are added with target_link_directories are added as
// -LIBPATH:... (Windows/MSVC), or
@@ -306,7 +306,7 @@ static QStringList splitFragments(const QStringList &fragments)
{
QStringList result;
for (const QString &f : fragments) {
- result += ProcessArgs::splitArgs(f);
+ result += ProcessArgs::splitArgs(f, HostOsInfo::hostOs());
}
return result;
}
@@ -605,6 +605,28 @@ void addCompileGroups(ProjectNode *targetRoot,
std::move(otherFileNodes));
}
+static void addGeneratedFilesNode(ProjectNode *targetRoot, const FilePath &topLevelBuildDir,
+ const TargetDetails &td)
+{
+ if (td.artifacts.isEmpty())
+ return;
+ FileType type = FileType::Unknown;
+ if (td.type == "EXECUTABLE")
+ type = FileType::App;
+ else if (td.type == "SHARED_LIBRARY" || td.type == "STATIC_LIBRARY")
+ type = FileType::Lib;
+ if (type == FileType::Unknown)
+ return;
+ std::vector<std::unique_ptr<FileNode>> nodes;
+ const FilePath buildDir = topLevelBuildDir.resolvePath(td.buildDir);
+ for (const FilePath &artifact : td.artifacts) {
+ nodes.emplace_back(new FileNode(buildDir.resolvePath(artifact), type));
+ type = FileType::Unknown;
+ nodes.back()->setIsGenerated(true);
+ }
+ addCMakeVFolder(targetRoot, buildDir, 10, Tr::tr("<Generated Files>"), std::move(nodes));
+}
+
void addTargets(const QHash<Utils::FilePath, ProjectExplorer::ProjectNode *> &cmakeListsNodes,
const Configuration &config,
const std::vector<TargetDetails> &targetDetails,
@@ -635,6 +657,7 @@ void addTargets(const QHash<Utils::FilePath, ProjectExplorer::ProjectNode *> &cm
tNode->setBuildDirectory(directoryBuildDir(config, buildDir, t.directory));
addCompileGroups(tNode, sourceDir, dir, tNode->buildDirectory(), td);
+ addGeneratedFilesNode(tNode, buildDir, td);
}
}
diff --git a/src/plugins/cmakeprojectmanager/fileapiparser.cpp b/src/plugins/cmakeprojectmanager/fileapiparser.cpp
index 0f25f27580d..e4dca5c9b26 100644
--- a/src/plugins/cmakeprojectmanager/fileapiparser.cpp
+++ b/src/plugins/cmakeprojectmanager/fileapiparser.cpp
@@ -16,6 +16,7 @@
#include <QJsonDocument>
#include <QJsonObject>
#include <QLoggingCategory>
+#include <QPromise>
using namespace Utils;
@@ -825,7 +826,7 @@ static QStringList uniqueTargetFiles(const Configuration &config)
return files;
}
-FileApiData FileApiParser::parseData(QFutureInterface<std::shared_ptr<FileApiQtcData>> &fi,
+FileApiData FileApiParser::parseData(QPromise<std::shared_ptr<FileApiQtcData>> &promise,
const FilePath &replyFilePath,
const QString &cmakeBuildType,
QString &errorMessage)
@@ -836,8 +837,8 @@ FileApiData FileApiParser::parseData(QFutureInterface<std::shared_ptr<FileApiQtc
FileApiData result;
- const auto cancelCheck = [&fi, &errorMessage] {
- if (fi.isCanceled()) {
+ const auto cancelCheck = [&promise, &errorMessage] {
+ if (promise.isCanceled()) {
errorMessage = Tr::tr("CMake parsing was canceled.");
return true;
}
diff --git a/src/plugins/cmakeprojectmanager/fileapiparser.h b/src/plugins/cmakeprojectmanager/fileapiparser.h
index 14ac55873b1..1d7c5d5ab3f 100644
--- a/src/plugins/cmakeprojectmanager/fileapiparser.h
+++ b/src/plugins/cmakeprojectmanager/fileapiparser.h
@@ -14,13 +14,17 @@
#include <utils/fileutils.h>
#include <QDir>
-#include <QFutureInterface>
#include <QString>
#include <QVector>
#include <QVersionNumber>
#include <vector>
+QT_BEGIN_NAMESPACE
+template <typename Ret>
+class QPromise;
+QT_END_NAMESPACE
+
namespace CMakeProjectManager::Internal {
namespace FileApiDetails {
@@ -218,7 +222,7 @@ public:
class FileApiParser
{
public:
- static FileApiData parseData(QFutureInterface<std::shared_ptr<FileApiQtcData>> &fi,
+ static FileApiData parseData(QPromise<std::shared_ptr<FileApiQtcData>> &promise,
const Utils::FilePath &replyFilePath,
const QString &cmakeBuildType,
QString &errorMessage);
diff --git a/src/plugins/cmakeprojectmanager/fileapireader.cpp b/src/plugins/cmakeprojectmanager/fileapireader.cpp
index c6ee73d6abb..6167ad84278 100644
--- a/src/plugins/cmakeprojectmanager/fileapireader.cpp
+++ b/src/plugins/cmakeprojectmanager/fileapireader.cpp
@@ -5,7 +5,6 @@
#include "cmakeprocess.h"
#include "cmakeprojectmanagertr.h"
-#include "cmakeprojectplugin.h"
#include "cmakespecificsettings.h"
#include "fileapidataextractor.h"
#include "fileapiparser.h"
@@ -15,6 +14,7 @@
#include <projectexplorer/projectexplorer.h>
#include <utils/algorithm.h>
+#include <utils/asynctask.h>
#include <utils/qtcassert.h>
#include <utils/runextensions.h>
@@ -235,11 +235,11 @@ void FileApiReader::endState(const FilePath &replyFilePath, bool restoredFromBac
m_lastReplyTimestamp = replyFilePath.lastModified();
- m_future = runAsync(ProjectExplorerPlugin::sharedThreadPool(),
+ m_future = Utils::asyncRun(ProjectExplorerPlugin::sharedThreadPool(),
[replyFilePath, sourceDirectory, buildDirectory, cmakeBuildType](
- QFutureInterface<std::shared_ptr<FileApiQtcData>> &fi) {
+ QPromise<std::shared_ptr<FileApiQtcData>> &promise) {
auto result = std::make_shared<FileApiQtcData>();
- FileApiData data = FileApiParser::parseData(fi,
+ FileApiData data = FileApiParser::parseData(promise,
replyFilePath,
cmakeBuildType,
result->errorMessage);
@@ -248,7 +248,7 @@ void FileApiReader::endState(const FilePath &replyFilePath, bool restoredFromBac
else
qWarning() << result->errorMessage;
- fi.reportResult(result);
+ promise.addResult(result);
});
onResultReady(m_future.value(),
this,
diff --git a/src/plugins/cmakeprojectmanager/presetsmacros.cpp b/src/plugins/cmakeprojectmanager/presetsmacros.cpp
index c1007b202f4..3b750d4599c 100644
--- a/src/plugins/cmakeprojectmanager/presetsmacros.cpp
+++ b/src/plugins/cmakeprojectmanager/presetsmacros.cpp
@@ -9,6 +9,8 @@
#include <utils/hostosinfo.h>
#include <utils/osspecificaspects.h>
+using namespace Utils;
+
namespace CMakeProjectManager::Internal::CMakePresets::Macros {
static QString getHostSystemName(Utils::OsType osType)
@@ -106,32 +108,26 @@ static QString expandMacroEnv(const QString &macroPrefix,
return result;
}
-static Utils::Environment getEnvCombined(const std::optional<Utils::Environment> &optPresetEnv,
- const Utils::Environment &env)
+static Environment getEnvCombined(const std::optional<Environment> &optPresetEnv,
+ const Environment &env)
{
- Utils::Environment result = env;
-
- if (!optPresetEnv)
- return result;
+ Environment result = env;
- Utils::Environment presetEnv = optPresetEnv.value();
- for (auto it = presetEnv.constBegin(); it != presetEnv.constEnd(); ++it) {
- result.set(it.key().name, it.value().first);
+ if (optPresetEnv) {
+ optPresetEnv->forEachEntry([&result](const QString &key, const QString &value, bool) {
+ result.set(key, value);
+ });
}
return result;
}
template<class PresetType>
-void expand(const PresetType &preset,
- Utils::Environment &env,
- const Utils::FilePath &sourceDirectory)
+void expand(const PresetType &preset, Environment &env, const FilePath &sourceDirectory)
{
- const Utils::Environment presetEnv = getEnvCombined(preset.environment, env);
- for (auto it = presetEnv.constBegin(); it != presetEnv.constEnd(); ++it) {
- const QString key = it.key().name;
- QString value = it.value().first;
-
+ const Environment presetEnv = getEnvCombined(preset.environment, env);
+ presetEnv.forEachEntry([&](const QString &key, const QString &value_, bool) {
+ QString value = value_;
expandAllButEnv(preset, sourceDirectory, value);
value = expandMacroEnv("env", value, [presetEnv](const QString &macroName) {
return presetEnv.value(macroName);
@@ -140,7 +136,7 @@ void expand(const PresetType &preset,
QString sep;
bool append = true;
if (key.compare("PATH", Qt::CaseInsensitive) == 0) {
- sep = Utils::OsSpecificAspects::pathListSeparator(env.osType());
+ sep = OsSpecificAspects::pathListSeparator(env.osType());
const int index = value.indexOf("$penv{PATH}", 0, Qt::CaseInsensitive);
if (index != 0)
append = false;
@@ -158,20 +154,15 @@ void expand(const PresetType &preset,
env.appendOrSet(key, value, sep);
else
env.prependOrSet(key, value, sep);
- }
+ });
}
template<class PresetType>
-void expand(const PresetType &preset,
- Utils::EnvironmentItems &envItems,
- const Utils::FilePath &sourceDirectory)
+void expand(const PresetType &preset, EnvironmentItems &envItems, const FilePath &sourceDirectory)
{
- const Utils::Environment presetEnv = preset.environment ? preset.environment.value()
- : Utils::Environment();
- for (auto it = presetEnv.constBegin(); it != presetEnv.constEnd(); ++it) {
- const QString key = it.key().name;
- QString value = it.value().first;
-
+ const Environment presetEnv = preset.environment ? *preset.environment : Environment();
+ presetEnv.forEachEntry([&](const QString &key, const QString &value_, bool) {
+ QString value = value_;
expandAllButEnv(preset, sourceDirectory, value);
value = expandMacroEnv("env", value, [presetEnv](const QString &macroName) {
if (presetEnv.hasKey(macroName))
@@ -179,12 +170,12 @@ void expand(const PresetType &preset,
return QString("${%1}").arg(macroName);
});
- auto operation = Utils::EnvironmentItem::Operation::SetEnabled;
+ auto operation = EnvironmentItem::Operation::SetEnabled;
if (key.compare("PATH", Qt::CaseInsensitive) == 0) {
- operation = Utils::EnvironmentItem::Operation::Append;
+ operation = EnvironmentItem::Operation::Append;
const int index = value.indexOf("$penv{PATH}", 0, Qt::CaseInsensitive);
if (index != 0)
- operation = Utils::EnvironmentItem::Operation::Prepend;
+ operation = EnvironmentItem::Operation::Prepend;
value.replace("$penv{PATH}", "", Qt::CaseInsensitive);
}
@@ -196,7 +187,7 @@ void expand(const PresetType &preset,
expandAllButEnv(preset, sourceDirectory, value);
envItems.emplace_back(Utils::EnvironmentItem(key, value, operation));
- }
+ });
}
template<class PresetType>
diff --git a/src/plugins/cmakeprojectmanager/presetsparser.cpp b/src/plugins/cmakeprojectmanager/presetsparser.cpp
index eed174e2b42..06c12de8401 100644
--- a/src/plugins/cmakeprojectmanager/presetsparser.cpp
+++ b/src/plugins/cmakeprojectmanager/presetsparser.cpp
@@ -482,16 +482,6 @@ static QHash<QString, QString> merge(const QHash<QString, QString> &first,
return result;
}
-static Utils::Environment merge(const Utils::Environment &first, const Utils::Environment &second)
-{
- Utils::Environment result = first;
- for (auto it = second.constBegin(); it != second.constEnd(); ++it) {
- result.set(it.key().name, it.value().first);
- }
-
- return result;
-}
-
static CMakeConfig merge(const CMakeConfig &first, const CMakeConfig &second)
{
return Utils::setUnionMerge<CMakeConfig>(
@@ -549,7 +539,7 @@ void PresetsDetails::ConfigurePreset::inheritFrom(const ConfigurePreset &other)
if (!environment && other.environment)
environment = other.environment;
else if (environment && other.environment)
- environment = merge(other.environment.value(), environment.value());
+ environment = environment.value().appliedToEnvironment(other.environment.value());
if (!warnings && other.warnings)
warnings = other.warnings;
@@ -575,7 +565,7 @@ void PresetsDetails::BuildPreset::inheritFrom(const BuildPreset &other)
if (!environment && other.environment)
environment = other.environment;
else if (environment && other.environment)
- environment = merge(other.environment.value(), environment.value());
+ environment = environment.value().appliedToEnvironment(other.environment.value());
if (!configurePreset && other.configurePreset)
configurePreset = other.configurePreset;
diff --git a/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseproject.cpp b/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseproject.cpp
index 051552d16ca..49712c32061 100644
--- a/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseproject.cpp
+++ b/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseproject.cpp
@@ -148,8 +148,8 @@ void addDriverModeFlagIfNeeded(const ToolChain *toolchain,
RawProjectPart makeRawProjectPart(const Utils::FilePath &projectFile,
Kit *kit,
ProjectExplorer::KitInfo &kitInfo,
- const QString &workingDir,
- const Utils::FilePath &fileName,
+ const FilePath &workingDir,
+ const FilePath &filePath,
QStringList flags)
{
HeaderPaths headerPaths;
@@ -157,7 +157,7 @@ RawProjectPart makeRawProjectPart(const Utils::FilePath &projectFile,
CppEditor::ProjectFile::Kind fileKind = CppEditor::ProjectFile::Unclassified;
const QStringList originalFlags = flags;
- filteredFlags(fileName.fileName(),
+ filteredFlags(filePath,
workingDir,
flags,
headerPaths,
@@ -166,10 +166,12 @@ RawProjectPart makeRawProjectPart(const Utils::FilePath &projectFile,
kitInfo.sysRootPath);
RawProjectPart rpp;
+
rpp.setProjectFileLocation(projectFile.toString());
- rpp.setBuildSystemTarget(workingDir);
- rpp.setDisplayName(fileName.fileName());
- rpp.setFiles({fileName.toString()});
+ rpp.setBuildSystemTarget(workingDir.path());
+ rpp.setDisplayName(filePath.fileName());
+ rpp.setFiles({filePath.toFSPathString()});
+
rpp.setHeaderPaths(headerPaths);
rpp.setMacros(macros);
diff --git a/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseprojectmanager.qbs b/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseprojectmanager.qbs
index 9c12b05a3a7..ce7e4e35ac3 100644
--- a/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseprojectmanager.qbs
+++ b/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseprojectmanager.qbs
@@ -21,9 +21,7 @@ QtcPlugin {
"compilationdbparser.h",
]
- Group {
- name: "Tests"
- condition: qtc.testsEnabled
+ QtcTestFiles {
files: [
"compilationdatabasetests.cpp",
"compilationdatabasetests.h",
diff --git a/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseprojectmanagerplugin.cpp b/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseprojectmanagerplugin.cpp
index fc1da70ca75..13f03c0ed31 100644
--- a/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseprojectmanagerplugin.cpp
+++ b/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseprojectmanagerplugin.cpp
@@ -15,7 +15,7 @@
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/projectmanager.h>
#include <projectexplorer/projecttree.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <utils/fsengine/fileiconprovider.h>
#include <utils/parameteraction.h>
@@ -72,7 +72,7 @@ void CompilationDatabaseProjectManagerPlugin::initialize()
d->changeRootAction.setEnabled(currentProject);
};
- connect(SessionManager::instance(), &SessionManager::startupProjectChanged,
+ connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged,
this, onProjectChanged);
connect(ProjectTree::instance(), &ProjectTree::currentProjectChanged,
diff --git a/src/plugins/compilationdatabaseprojectmanager/compilationdatabasetests.cpp b/src/plugins/compilationdatabaseprojectmanager/compilationdatabasetests.cpp
index 6fff4947647..f8b25747716 100644
--- a/src/plugins/compilationdatabaseprojectmanager/compilationdatabasetests.cpp
+++ b/src/plugins/compilationdatabaseprojectmanager/compilationdatabasetests.cpp
@@ -92,7 +92,9 @@ public:
QStringList getFilteredFlags()
{
- filteredFlags(fileName, workingDir, flags, headerPaths, macros, fileKind, sysRoot);
+ filteredFlags(FilePath::fromString(fileName),
+ FilePath::fromString(workingDir),
+ flags, headerPaths, macros, fileKind, sysRoot);
return flags;
}
diff --git a/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseutils.cpp b/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseutils.cpp
index df8e08a68f5..f773d5fc5d5 100644
--- a/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseutils.cpp
+++ b/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseutils.cpp
@@ -22,15 +22,6 @@ using namespace Utils;
namespace CompilationDatabaseProjectManager {
namespace Internal {
-static QString updatedPathFlag(const QString &pathStr, const QString &workingDir)
-{
- QString result = pathStr;
- if (QDir(pathStr).isRelative())
- result = workingDir + "/" + pathStr;
-
- return result;
-}
-
static CppEditor::ProjectFile::Kind fileKindFromString(QString flag)
{
using namespace CppEditor;
@@ -86,13 +77,13 @@ QStringList filterFromFileName(const QStringList &flags, QString fileName)
return result;
}
-void filteredFlags(const QString &fileName,
- const QString &workingDir,
+void filteredFlags(const FilePath &filePath,
+ const FilePath &workingDir,
QStringList &flags,
HeaderPaths &headerPaths,
Macros &macros,
CppEditor::ProjectFile::Kind &fileKind,
- Utils::FilePath &sysRoot)
+ FilePath &sysRoot)
{
if (flags.empty())
return;
@@ -113,7 +104,7 @@ void filteredFlags(const QString &fileName,
}
if (includePathType) {
- const QString pathStr = updatedPathFlag(flag, workingDir);
+ const QString pathStr = workingDir.resolvePath(flag).toString();
headerPaths.append({pathStr, includePathType.value()});
includePathType.reset();
continue;
@@ -152,7 +143,7 @@ void filteredFlags(const QString &fileName,
return flag.startsWith(opt) && flag != opt;
});
if (!includeOpt.isEmpty()) {
- const QString pathStr = updatedPathFlag(flag.mid(includeOpt.length()), workingDir);
+ const QString pathStr = workingDir.resolvePath(flag.mid(includeOpt.length())).toString();
headerPaths.append({pathStr, userIncludeFlags.contains(includeOpt)
? HeaderPathType::User : HeaderPathType::System});
continue;
@@ -182,14 +173,14 @@ void filteredFlags(const QString &fileName,
if (flag.startsWith("--sysroot=")) {
if (sysRoot.isEmpty())
- sysRoot = FilePath::fromUserInput(updatedPathFlag(flag.mid(10), workingDir));
+ sysRoot = workingDir.resolvePath(flag.mid(10));
continue;
}
if ((flag.startsWith("-std=") || flag.startsWith("/std:"))
&& fileKind == CppEditor::ProjectFile::Unclassified) {
const bool cpp = (flag.contains("c++") || flag.contains("gnu++"));
- if (CppEditor::ProjectFile::isHeader(CppEditor::ProjectFile::classify(fileName)))
+ if (CppEditor::ProjectFile::isHeader(CppEditor::ProjectFile::classify(filePath.path())))
fileKind = cpp ? CppEditor::ProjectFile::CXXHeader : CppEditor::ProjectFile::CHeader;
else
fileKind = cpp ? CppEditor::ProjectFile::CXXSource : CppEditor::ProjectFile::CSource;
@@ -203,7 +194,7 @@ void filteredFlags(const QString &fileName,
}
if (fileKind == CppEditor::ProjectFile::Unclassified)
- fileKind = CppEditor::ProjectFile::classify(fileName);
+ fileKind = CppEditor::ProjectFile::classify(filePath.path());
flags = filtered;
}
diff --git a/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseutils.h b/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseutils.h
index 79130799b48..ec024443d8f 100644
--- a/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseutils.h
+++ b/src/plugins/compilationdatabaseprojectmanager/compilationdatabaseutils.h
@@ -4,10 +4,8 @@
#pragma once
#include <cppeditor/cppprojectfile.h>
-#include <utils/filepath.h>
#include <QHash>
-#include <QStringList>
namespace ProjectExplorer {
class HeaderPath;
@@ -21,7 +19,7 @@ class DbEntry {
public:
QStringList flags;
Utils::FilePath fileName;
- QString workingDir;
+ Utils::FilePath workingDir;
};
class DbContents {
@@ -35,8 +33,8 @@ using MimeBinaryCache = QHash<QString, bool>;
QStringList filterFromFileName(const QStringList &flags, QString baseName);
-void filteredFlags(const QString &fileName,
- const QString &workingDir,
+void filteredFlags(const Utils::FilePath &filePath,
+ const Utils::FilePath &workingDir,
QStringList &flags,
QVector<ProjectExplorer::HeaderPath> &headerPaths,
QVector<ProjectExplorer::Macro> &macros,
diff --git a/src/plugins/compilationdatabaseprojectmanager/compilationdbparser.cpp b/src/plugins/compilationdatabaseprojectmanager/compilationdbparser.cpp
index 251e9685c45..7d6b399d91a 100644
--- a/src/plugins/compilationdatabaseprojectmanager/compilationdbparser.cpp
+++ b/src/plugins/compilationdatabaseprojectmanager/compilationdbparser.cpp
@@ -8,8 +8,8 @@
#include <coreplugin/progressmanager/progressmanager.h>
#include <projectexplorer/treescanner.h>
+#include <utils/asynctask.h>
#include <utils/mimeutils.h>
-#include <utils/runextensions.h>
#include <QCryptographicHash>
#include <QDir>
@@ -95,7 +95,7 @@ void CompilationDbParser::start()
}
// Thread 2: Parse the project file.
- const QFuture<DbContents> future = runAsync(&CompilationDbParser::parseProject, this);
+ const QFuture<DbContents> future = Utils::asyncRun(&CompilationDbParser::parseProject, this);
Core::ProgressManager::addTask(future,
Tr::tr("Parse \"%1\" project").arg(m_projectName),
"CompilationDatabase.Parse");
@@ -182,7 +182,7 @@ std::vector<DbEntry> CompilationDbParser::readJsonObjects() const
const Utils::FilePath filePath = jsonObjectFilePath(object);
const QStringList flags = filterFromFileName(jsonObjectFlags(object, flagsCache),
filePath.fileName());
- result.push_back({flags, filePath, object["directory"].toString()});
+ result.push_back({flags, filePath, FilePath::fromUserInput(object["directory"].toString())});
objectStart = m_projectFileContents.indexOf('{', objectEnd + 1);
objectEnd = m_projectFileContents.indexOf('}', objectStart + 1);
diff --git a/src/plugins/conan/conanplugin.cpp b/src/plugins/conan/conanplugin.cpp
index a1a78acca33..3844b2983bd 100644
--- a/src/plugins/conan/conanplugin.cpp
+++ b/src/plugins/conan/conanplugin.cpp
@@ -13,8 +13,8 @@
#include <projectexplorer/buildmanager.h>
#include <projectexplorer/buildsteplist.h>
#include <projectexplorer/project.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/projecttree.h>
-#include <projectexplorer/session.h>
#include <projectexplorer/target.h>
using namespace Core;
@@ -39,7 +39,7 @@ void ConanPlugin::initialize()
d = new ConanPluginPrivate;
conanSettings()->readSettings(ICore::settings());
- connect(SessionManager::instance(), &SessionManager::projectAdded,
+ connect(ProjectManager::instance(), &ProjectManager::projectAdded,
this, &ConanPlugin::projectAdded);
}
@@ -48,12 +48,12 @@ static void connectTarget(Project *project, Target *target)
if (!ConanPlugin::conanFilePath(project).isEmpty()) {
const QList<BuildConfiguration *> buildConfigurations = target->buildConfigurations();
for (BuildConfiguration *buildConfiguration : buildConfigurations)
- buildConfiguration->buildSteps()->appendStep(Constants::INSTALL_STEP);
+ buildConfiguration->buildSteps()->insertStep(0, Constants::INSTALL_STEP);
}
QObject::connect(target, &Target::addedBuildConfiguration,
target, [project] (BuildConfiguration *buildConfiguration) {
if (!ConanPlugin::conanFilePath(project).isEmpty())
- buildConfiguration->buildSteps()->appendStep(Constants::INSTALL_STEP);
+ buildConfiguration->buildSteps()->insertStep(0, Constants::INSTALL_STEP);
});
}
diff --git a/src/plugins/copilot/CMakeLists.txt b/src/plugins/copilot/CMakeLists.txt
new file mode 100644
index 00000000000..b718ec12695
--- /dev/null
+++ b/src/plugins/copilot/CMakeLists.txt
@@ -0,0 +1,17 @@
+add_qtc_plugin(Copilot
+ PLUGIN_DEPENDS Core LanguageClient
+ SOURCES
+ authwidget.cpp authwidget.h
+ copilot.qrc
+ copilotclient.cpp copilotclient.h
+ copilothoverhandler.cpp copilothoverhandler.h
+ copilotoptionspage.cpp copilotoptionspage.h
+ copilotplugin.cpp copilotplugin.h
+ copilotsettings.cpp copilotsettings.h
+ copilotsuggestion.cpp copilotsuggestion.h
+ requests/checkstatus.h
+ requests/getcompletions.h
+ requests/signinconfirm.h
+ requests/signininitiate.h
+ requests/signout.h
+)
diff --git a/src/plugins/copilot/Copilot.json.in b/src/plugins/copilot/Copilot.json.in
new file mode 100644
index 00000000000..55ff6f6bf42
--- /dev/null
+++ b/src/plugins/copilot/Copilot.json.in
@@ -0,0 +1,19 @@
+{
+ \"Name\" : \"Copilot\",
+ \"Version\" : \"$$QTCREATOR_VERSION\",
+ \"CompatVersion\" : \"$$QTCREATOR_COMPAT_VERSION\",
+ \"Experimental\" : true,
+ \"Vendor\" : \"The Qt Company Ltd\",
+ \"Copyright\" : \"(C) $$QTCREATOR_COPYRIGHT_YEAR The Qt Company Ltd\",
+ \"License\" : [ \"Commercial Usage\",
+ \"\",
+ \"Licensees holding valid Qt Commercial licenses may use this plugin in accordance with the Qt Commercial License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and The Qt Company.\",
+ \"\",
+ \"GNU General Public License Usage\",
+ \"\",
+ \"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html.\"
+ ],
+ \"Description\" : \"Copilot support\",
+ \"Url\" : \"http://www.qt.io\",
+ $$dependencyList
+}
diff --git a/src/plugins/copilot/authwidget.cpp b/src/plugins/copilot/authwidget.cpp
new file mode 100644
index 00000000000..cba1f178d31
--- /dev/null
+++ b/src/plugins/copilot/authwidget.cpp
@@ -0,0 +1,149 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "authwidget.h"
+
+#include "copilotclient.h"
+#include "copilottr.h"
+
+#include <utils/layoutbuilder.h>
+#include <utils/stringutils.h>
+
+#include <languageclient/languageclientmanager.h>
+
+#include <QDesktopServices>
+#include <QMessageBox>
+
+using namespace LanguageClient;
+using namespace Copilot::Internal;
+
+namespace Copilot {
+
+AuthWidget::AuthWidget(QWidget *parent)
+ : QWidget(parent)
+{
+ using namespace Utils::Layouting;
+
+ m_button = new QPushButton(Tr::tr("Sign in"));
+ m_button->setEnabled(false);
+ m_progressIndicator = new Utils::ProgressIndicator(Utils::ProgressIndicatorSize::Small);
+ m_progressIndicator->setVisible(false);
+ m_statusLabel = new QLabel();
+ m_statusLabel->setVisible(false);
+
+ // clang-format off
+ Column {
+ Row {
+ m_button, m_progressIndicator, st
+ },
+ m_statusLabel
+ }.attachTo(this);
+ // clang-format on
+
+ connect(m_button, &QPushButton::clicked, this, [this]() {
+ if (m_status == Status::SignedIn)
+ signOut();
+ else if (m_status == Status::SignedOut)
+ signIn();
+ });
+}
+
+void AuthWidget::setState(const QString &buttonText, bool working)
+{
+ m_button->setText(buttonText);
+ m_button->setVisible(true);
+ m_progressIndicator->setVisible(working);
+ m_statusLabel->setVisible(!m_statusLabel->text().isEmpty());
+ m_button->setEnabled(!working);
+}
+
+void AuthWidget::checkStatus()
+{
+ QTC_ASSERT(m_client && m_client->reachable(), return);
+
+ setState("Checking status ...", true);
+
+ m_client->requestCheckStatus(false, [this](const CheckStatusRequest::Response &response) {
+ if (response.error()) {
+ setState("failed: " + response.error()->message(), false);
+ return;
+ }
+ const CheckStatusResponse result = *response.result();
+
+ if (result.user().isEmpty()) {
+ setState("Sign in", false);
+ m_status = Status::SignedOut;
+ return;
+ }
+
+ setState("Sign out " + result.user(), false);
+ m_status = Status::SignedIn;
+ });
+}
+
+void AuthWidget::updateClient(const Utils::FilePath &nodeJs, const Utils::FilePath &agent)
+{
+ LanguageClientManager::shutdownClient(m_client);
+ m_client = nullptr;
+ setState(Tr::tr("Sign in"), true);
+ if (!nodeJs.exists() || !agent.exists())
+ return;
+
+ m_client = new CopilotClient(nodeJs, agent);
+ connect(m_client, &Client::initialized, this, &AuthWidget::checkStatus);
+}
+
+void AuthWidget::signIn()
+{
+ qCritical() << "Not implemented";
+ QTC_ASSERT(m_client && m_client->reachable(), return);
+
+ setState("Signing in ...", true);
+
+ m_client->requestSignInInitiate([this](const SignInInitiateRequest::Response &response) {
+ QTC_ASSERT(!response.error(), return);
+
+ Utils::setClipboardAndSelection(response.result()->userCode());
+
+ QDesktopServices::openUrl(QUrl(response.result()->verificationUri()));
+
+ m_statusLabel->setText(Tr::tr("A browser window will open, enter the code %1 when "
+ "asked.\nThe code has been copied to your clipboard.")
+ .arg(response.result()->userCode()));
+ m_statusLabel->setVisible(true);
+
+ m_client
+ ->requestSignInConfirm(response.result()->userCode(),
+ [this](const SignInConfirmRequest::Response &response) {
+ m_statusLabel->setText("");
+
+ if (response.error()) {
+ QMessageBox::critical(this,
+ Tr::tr("Login failed"),
+ Tr::tr(
+ "The login request failed: ")
+ + response.error()->message());
+ setState("Sign in", false);
+ return;
+ }
+
+ setState("Sign Out " + response.result()->user(), false);
+ });
+ });
+}
+
+void AuthWidget::signOut()
+{
+ QTC_ASSERT(m_client && m_client->reachable(), return);
+
+ setState("Signing out ...", true);
+
+ m_client->requestSignOut([this](const SignOutRequest::Response &response) {
+ QTC_ASSERT(!response.error(), return);
+ QTC_ASSERT(response.result()->status() == "NotSignedIn", return);
+
+ checkStatus();
+ });
+}
+
+} // namespace Copilot
diff --git a/src/plugins/copilot/authwidget.h b/src/plugins/copilot/authwidget.h
new file mode 100644
index 00000000000..0d5406591e0
--- /dev/null
+++ b/src/plugins/copilot/authwidget.h
@@ -0,0 +1,46 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "copilotclient.h"
+
+#include <utils/progressindicator.h>
+
+#include <QLabel>
+#include <QPushButton>
+#include <QWidget>
+
+namespace LanguageClient {
+class Client;
+}
+
+namespace Copilot {
+
+class AuthWidget : public QWidget
+{
+ Q_OBJECT
+
+ enum class Status { SignedIn, SignedOut, Unknown };
+
+public:
+ explicit AuthWidget(QWidget *parent = nullptr);
+ void updateClient(const Utils::FilePath &nodeJs, const Utils::FilePath &agent);
+
+private:
+ void setState(const QString &buttonText, bool working);
+ void checkStatus();
+
+
+ void signIn();
+ void signOut();
+
+private:
+ Status m_status = Status::Unknown;
+ QPushButton *m_button = nullptr;
+ QLabel *m_statusLabel = nullptr;
+ Utils::ProgressIndicator *m_progressIndicator = nullptr;
+ Internal::CopilotClient *m_client = nullptr;
+};
+
+} // namespace Copilot
diff --git a/src/plugins/copilot/copilot.qbs b/src/plugins/copilot/copilot.qbs
new file mode 100644
index 00000000000..51d7febbd6b
--- /dev/null
+++ b/src/plugins/copilot/copilot.qbs
@@ -0,0 +1,33 @@
+import qbs 1.0
+
+QtcPlugin {
+ name: "Copilot"
+
+ Depends { name: "Core" }
+ Depends { name: "LanguageClient" }
+ Depends { name: "TextEditor" }
+ Depends { name: "Qt"; submodules: ["widgets", "xml", "network"] }
+
+ files: [
+ "authwidget.cpp",
+ "authwidget.h",
+ "copilot.qrc",
+ "copilotclient.cpp",
+ "copilotclient.h",
+ "copilothoverhandler.cpp",
+ "copilothoverhandler.h",
+ "copilotoptionspage.cpp",
+ "copilotoptionspage.h",
+ "copilotplugin.cpp",
+ "copilotplugin.h",
+ "copilotsettings.cpp",
+ "copilotsettings.h",
+ "copilotsuggestion.cpp",
+ "copilotsuggestion.h",
+ "requests/checkstatus.h",
+ "requests/getcompletions.h",
+ "requests/signinconfirm.h",
+ "requests/signininitiate.h",
+ "requests/signout.h",
+ ]
+}
diff --git a/src/plugins/copilot/copilot.qrc b/src/plugins/copilot/copilot.qrc
new file mode 100644
index 00000000000..2071a381011
--- /dev/null
+++ b/src/plugins/copilot/copilot.qrc
@@ -0,0 +1,6 @@
+<RCC>
+ <qresource prefix="/copilot">
+ <file>images/settingscategory_copilot.png</file>
+ <file>images/[email protected]</file>
+ </qresource>
+</RCC>
diff --git a/src/plugins/copilot/copilotclient.cpp b/src/plugins/copilot/copilotclient.cpp
new file mode 100644
index 00000000000..5d2a6aa4b7b
--- /dev/null
+++ b/src/plugins/copilot/copilotclient.cpp
@@ -0,0 +1,220 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "copilotclient.h"
+#include "copilotsuggestion.h"
+
+#include <languageclient/languageclientinterface.h>
+#include <languageclient/languageclientmanager.h>
+#include <languageclient/languageclientsettings.h>
+
+#include <coreplugin/editormanager/editormanager.h>
+
+#include <utils/filepath.h>
+
+#include <texteditor/textdocumentlayout.h>
+#include <texteditor/texteditor.h>
+
+#include <languageserverprotocol/lsptypes.h>
+
+#include <QTimer>
+
+using namespace LanguageServerProtocol;
+using namespace TextEditor;
+using namespace Utils;
+
+namespace Copilot::Internal {
+
+static LanguageClient::BaseClientInterface *clientInterface(const FilePath &nodePath,
+ const FilePath &distPath)
+{
+ CommandLine cmd{nodePath, {distPath.toFSPathString()}};
+
+ const auto interface = new LanguageClient::StdIOClientInterface;
+ interface->setCommandLine(cmd);
+ return interface;
+}
+
+CopilotClient::CopilotClient(const FilePath &nodePath, const FilePath &distPath)
+ : LanguageClient::Client(clientInterface(nodePath, distPath))
+{
+ setName("Copilot");
+ LanguageClient::LanguageFilter langFilter;
+
+ langFilter.filePattern = {"*"};
+
+ setSupportedLanguage(langFilter);
+ start();
+
+ auto openDoc = [this](Core::IDocument *document) {
+ if (auto *textDocument = qobject_cast<TextDocument *>(document))
+ openDocument(textDocument);
+ };
+
+ connect(Core::EditorManager::instance(), &Core::EditorManager::documentOpened, this, openDoc);
+ connect(Core::EditorManager::instance(),
+ &Core::EditorManager::documentClosed,
+ this,
+ [this](Core::IDocument *document) {
+ if (auto textDocument = qobject_cast<TextDocument *>(document))
+ closeDocument(textDocument);
+ });
+
+ for (Core::IDocument *doc : Core::DocumentModel::openedDocuments())
+ openDoc(doc);
+}
+
+CopilotClient::~CopilotClient()
+{
+ for (Core::IEditor *editor : Core::DocumentModel::editorsForOpenedDocuments()) {
+ if (auto textEditor = qobject_cast<BaseTextEditor *>(editor))
+ textEditor->editorWidget()->removeHoverHandler(&m_hoverHandler);
+ }
+}
+
+void CopilotClient::openDocument(TextDocument *document)
+{
+ Client::openDocument(document);
+ connect(document,
+ &TextDocument::contentsChangedWithPosition,
+ this,
+ [this, document](int position, int charsRemoved, int charsAdded) {
+ Q_UNUSED(charsRemoved)
+ auto textEditor = BaseTextEditor::currentTextEditor();
+ if (!textEditor || textEditor->document() != document)
+ return;
+ TextEditorWidget *widget = textEditor->editorWidget();
+ if (widget->multiTextCursor().hasMultipleCursors())
+ return;
+ const int cursorPosition = widget->textCursor().position();
+ if (cursorPosition < position || cursorPosition > position + charsAdded)
+ return;
+ scheduleRequest(textEditor->editorWidget());
+ });
+}
+
+void CopilotClient::scheduleRequest(TextEditorWidget *editor)
+{
+ cancelRunningRequest(editor);
+
+ if (!m_scheduledRequests.contains(editor)) {
+ auto timer = new QTimer(this);
+ timer->setSingleShot(true);
+ connect(timer, &QTimer::timeout, this, [this, editor]() {
+ if (m_scheduledRequests[editor].cursorPosition == editor->textCursor().position())
+ requestCompletions(editor);
+ });
+ connect(editor, &TextEditorWidget::destroyed, this, [this, editor]() {
+ delete m_scheduledRequests.take(editor).timer;
+ cancelRunningRequest(editor);
+ });
+ connect(editor, &TextEditorWidget::cursorPositionChanged, this, [this, editor] {
+ cancelRunningRequest(editor);
+ });
+ m_scheduledRequests.insert(editor, {editor->textCursor().position(), timer});
+ } else {
+ m_scheduledRequests[editor].cursorPosition = editor->textCursor().position();
+ }
+ m_scheduledRequests[editor].timer->start(500);
+}
+
+void CopilotClient::requestCompletions(TextEditorWidget *editor)
+{
+ Utils::MultiTextCursor cursor = editor->multiTextCursor();
+ if (cursor.hasMultipleCursors() || cursor.hasSelection() || editor->suggestionVisible())
+ return;
+
+ const Utils::FilePath filePath = editor->textDocument()->filePath();
+ GetCompletionRequest request{
+ {TextDocumentIdentifier(hostPathToServerUri(filePath)),
+ documentVersion(filePath),
+ Position(cursor.mainCursor())}};
+ request.setResponseCallback([this, editor = QPointer<TextEditorWidget>(editor)](
+ const GetCompletionRequest::Response &response) {
+ QTC_ASSERT(editor, return);
+ handleCompletions(response, editor);
+ });
+ m_runningRequests[editor] = request;
+ sendMessage(request);
+}
+
+void CopilotClient::handleCompletions(const GetCompletionRequest::Response &response,
+ TextEditorWidget *editor)
+{
+ if (response.error())
+ log(*response.error());
+
+ int requestPosition = -1;
+ if (const auto requestParams = m_runningRequests.take(editor).params())
+ requestPosition = requestParams->position().toPositionInDocument(editor->document());
+
+ const Utils::MultiTextCursor cursors = editor->multiTextCursor();
+ if (cursors.hasMultipleCursors())
+ return;
+
+ if (cursors.hasSelection() || cursors.mainCursor().position() != requestPosition)
+ return;
+
+ if (const std::optional<GetCompletionResponse> result = response.result()) {
+ QList<Completion> completions = result->completions().toListOrEmpty();
+ if (completions.isEmpty())
+ return;
+ editor->insertSuggestion(
+ std::make_unique<CopilotSuggestion>(completions, editor->document()));
+ m_lastCompletions[editor] = *result;
+ editor->addHoverHandler(&m_hoverHandler);
+ }
+}
+
+void CopilotClient::cancelRunningRequest(TextEditor::TextEditorWidget *editor)
+{
+ auto it = m_runningRequests.find(editor);
+ if (it == m_runningRequests.end())
+ return;
+ cancelRequest(it->id());
+ m_runningRequests.erase(it);
+}
+
+void CopilotClient::requestCheckStatus(
+ bool localChecksOnly, std::function<void(const CheckStatusRequest::Response &response)> callback)
+{
+ CheckStatusRequest request{localChecksOnly};
+ request.setResponseCallback(callback);
+
+ sendMessage(request);
+}
+
+void CopilotClient::requestSignOut(
+ std::function<void(const SignOutRequest::Response &response)> callback)
+{
+ SignOutRequest request;
+ request.setResponseCallback(callback);
+
+ sendMessage(request);
+}
+
+void CopilotClient::requestSignInInitiate(
+ std::function<void(const SignInInitiateRequest::Response &response)> callback)
+{
+ SignInInitiateRequest request;
+ request.setResponseCallback(callback);
+
+ sendMessage(request);
+}
+
+void CopilotClient::requestSignInConfirm(
+ const QString &userCode,
+ std::function<void(const SignInConfirmRequest::Response &response)> callback)
+{
+ SignInConfirmRequest request(userCode);
+ request.setResponseCallback(callback);
+
+ sendMessage(request);
+}
+
+GetCompletionResponse CopilotClient::lastCompletion(TextEditor::TextEditorWidget *editor) const
+{
+ return m_lastCompletions.value(editor);
+}
+
+} // namespace Copilot::Internal
diff --git a/src/plugins/copilot/copilotclient.h b/src/plugins/copilot/copilotclient.h
new file mode 100644
index 00000000000..13f43149f71
--- /dev/null
+++ b/src/plugins/copilot/copilotclient.h
@@ -0,0 +1,63 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "copilothoverhandler.h"
+#include "requests/checkstatus.h"
+#include "requests/getcompletions.h"
+#include "requests/signinconfirm.h"
+#include "requests/signininitiate.h"
+#include "requests/signout.h"
+
+#include <languageclient/client.h>
+
+#include <utils/filepath.h>
+
+#include <QHash>
+#include <QTemporaryDir>
+
+namespace Copilot::Internal {
+
+class CopilotClient : public LanguageClient::Client
+{
+public:
+ CopilotClient(const Utils::FilePath &nodePath, const Utils::FilePath &distPath);
+ ~CopilotClient() override;
+
+ void openDocument(TextEditor::TextDocument *document) override;
+
+ void scheduleRequest(TextEditor::TextEditorWidget *editor);
+ void requestCompletions(TextEditor::TextEditorWidget *editor);
+ void handleCompletions(const GetCompletionRequest::Response &response,
+ TextEditor::TextEditorWidget *editor);
+ void cancelRunningRequest(TextEditor::TextEditorWidget *editor);
+
+ void requestCheckStatus(
+ bool localChecksOnly,
+ std::function<void(const CheckStatusRequest::Response &response)> callback);
+
+ void requestSignOut(std::function<void(const SignOutRequest::Response &response)> callback);
+
+ void requestSignInInitiate(
+ std::function<void(const SignInInitiateRequest::Response &response)> callback);
+
+ void requestSignInConfirm(
+ const QString &userCode,
+ std::function<void(const SignInConfirmRequest::Response &response)> callback);
+
+ GetCompletionResponse lastCompletion(TextEditor::TextEditorWidget *editor) const;
+
+private:
+ QMap<TextEditor::TextEditorWidget *, GetCompletionRequest> m_runningRequests;
+ struct ScheduleData
+ {
+ int cursorPosition = -1;
+ QTimer *timer = nullptr;
+ };
+ QMap<TextEditor::TextEditorWidget *, ScheduleData> m_scheduledRequests;
+ CopilotHoverHandler m_hoverHandler;
+ QHash<TextEditor::TextEditorWidget *, GetCompletionResponse> m_lastCompletions;
+};
+
+} // namespace Copilot::Internal
diff --git a/src/plugins/copilot/copilothoverhandler.cpp b/src/plugins/copilot/copilothoverhandler.cpp
new file mode 100644
index 00000000000..07aca2cbc7c
--- /dev/null
+++ b/src/plugins/copilot/copilothoverhandler.cpp
@@ -0,0 +1,140 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "copilothoverhandler.h"
+
+#include "copilotclient.h"
+#include "copilotsuggestion.h"
+#include "copilottr.h"
+
+#include <texteditor/textdocument.h>
+#include <texteditor/textdocumentlayout.h>
+#include <texteditor/texteditor.h>
+
+#include <utils/tooltip/tooltip.h>
+#include <utils/utilsicons.h>
+
+#include <QPushButton>
+#include <QToolBar>
+#include <QToolButton>
+
+using namespace TextEditor;
+using namespace LanguageServerProtocol;
+using namespace Utils;
+
+namespace Copilot::Internal {
+
+class CopilotCompletionToolTip : public QToolBar
+{
+public:
+ CopilotCompletionToolTip(QList<Completion> completions,
+ int currentCompletion,
+ TextEditorWidget *editor)
+ : m_numberLabel(new QLabel)
+ , m_completions(completions)
+ , m_currentCompletion(std::max(0, std::min<int>(currentCompletion, completions.size() - 1)))
+ , m_editor(editor)
+ {
+ auto prev = addAction(Utils::Icons::PREV_TOOLBAR.icon(),
+ Tr::tr("Select Previous Copilot Suggestion"));
+ prev->setEnabled(m_completions.size() > 1);
+ addWidget(m_numberLabel);
+ auto next = addAction(Utils::Icons::NEXT_TOOLBAR.icon(),
+ Tr::tr("Select Next Copilot Suggestion"));
+ next->setEnabled(m_completions.size() > 1);
+
+ auto apply = addAction(Tr::tr("Apply (Tab)"));
+
+ connect(prev, &QAction::triggered, this, &CopilotCompletionToolTip::selectPrevious);
+ connect(next, &QAction::triggered, this, &CopilotCompletionToolTip::selectNext);
+ connect(apply, &QAction::triggered, this, &CopilotCompletionToolTip::apply);
+
+ updateLabels();
+ }
+
+private:
+ void updateLabels()
+ {
+ m_numberLabel->setText(Tr::tr("%1 of %2")
+ .arg(m_currentCompletion + 1)
+ .arg(m_completions.count()));
+ }
+
+ void selectPrevious()
+ {
+ --m_currentCompletion;
+ if (m_currentCompletion < 0)
+ m_currentCompletion = m_completions.size() - 1;
+ setCurrentCompletion();
+ }
+
+ void selectNext()
+ {
+ ++m_currentCompletion;
+ if (m_currentCompletion >= m_completions.size())
+ m_currentCompletion = 0;
+ setCurrentCompletion();
+ }
+
+ void setCurrentCompletion()
+ {
+ updateLabels();
+ if (TextSuggestion *suggestion = m_editor->currentSuggestion())
+ suggestion->reset();
+ m_editor->insertSuggestion(std::make_unique<CopilotSuggestion>(m_completions,
+ m_editor->document(),
+ m_currentCompletion));
+ }
+
+ void apply()
+ {
+ if (TextSuggestion *suggestion = m_editor->currentSuggestion())
+ suggestion->apply();
+ ToolTip::hide();
+ }
+
+ QLabel *m_numberLabel;
+ QList<Completion> m_completions;
+ int m_currentCompletion = 0;
+ TextEditorWidget *m_editor;
+};
+
+void CopilotHoverHandler::identifyMatch(TextEditorWidget *editorWidget,
+ int pos,
+ ReportPriority report)
+{
+ auto reportNone = qScopeGuard([&] { report(Priority_None); });
+ if (!editorWidget->suggestionVisible())
+ return;
+
+ QTextCursor cursor(editorWidget->document());
+ cursor.setPosition(pos);
+ m_block = cursor.block();
+ auto *suggestion = dynamic_cast<CopilotSuggestion *>(TextDocumentLayout::suggestion(m_block));
+
+ if (!suggestion)
+ return;
+
+ const QList<Completion> completions = suggestion->completions();
+ if (completions.isEmpty())
+ return;
+
+ reportNone.dismiss();
+ report(Priority_Suggestion);
+}
+
+void CopilotHoverHandler::operateTooltip(TextEditorWidget *editorWidget, const QPoint &point)
+{
+ auto *suggestion = dynamic_cast<CopilotSuggestion *>(TextDocumentLayout::suggestion(m_block));
+
+ if (!suggestion)
+ return;
+
+ auto tooltipWidget = new CopilotCompletionToolTip(suggestion->completions(),
+ suggestion->currentCompletion(),
+ editorWidget);
+ const qreal deltay = 2 * editorWidget->textDocument()->fontSettings().lineSpacing();
+ ToolTip::show(point - QPoint{0, int(deltay)}, tooltipWidget, editorWidget);
+}
+
+} // namespace Copilot::Internal
diff --git a/src/plugins/copilot/copilothoverhandler.h b/src/plugins/copilot/copilothoverhandler.h
new file mode 100644
index 00000000000..1c48e75d5b7
--- /dev/null
+++ b/src/plugins/copilot/copilothoverhandler.h
@@ -0,0 +1,32 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "requests/getcompletions.h"
+
+#include <texteditor/basehoverhandler.h>
+
+#include <QTextBlock>
+
+#pragma once
+
+namespace TextEditor { class TextSuggestion; }
+namespace Copilot::Internal {
+
+class CopilotClient;
+
+class CopilotHoverHandler final : public TextEditor::BaseHoverHandler
+{
+public:
+ CopilotHoverHandler() = default;
+
+protected:
+ void identifyMatch(TextEditor::TextEditorWidget *editorWidget,
+ int pos,
+ ReportPriority report) final;
+ void operateTooltip(TextEditor::TextEditorWidget *editorWidget, const QPoint &point) final;
+
+private:
+ QTextBlock m_block;
+};
+
+} // namespace Copilot::Internal
diff --git a/src/plugins/copilot/copilotoptionspage.cpp b/src/plugins/copilot/copilotoptionspage.cpp
new file mode 100644
index 00000000000..feb807d7f1e
--- /dev/null
+++ b/src/plugins/copilot/copilotoptionspage.cpp
@@ -0,0 +1,110 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "copilotoptionspage.h"
+
+#include "authwidget.h"
+#include "copilotsettings.h"
+#include "copilottr.h"
+
+#include <coreplugin/icore.h>
+
+#include <utils/layoutbuilder.h>
+#include <utils/pathchooser.h>
+
+using namespace Utils;
+using namespace LanguageClient;
+
+namespace Copilot {
+
+class CopilotOptionsPageWidget : public QWidget
+{
+public:
+ CopilotOptionsPageWidget(QWidget *parent = nullptr)
+ : QWidget(parent)
+ {
+ using namespace Layouting;
+
+ auto authWidget = new AuthWidget();
+
+ QLabel *helpLabel = new QLabel();
+ helpLabel->setTextFormat(Qt::MarkdownText);
+ helpLabel->setWordWrap(true);
+ helpLabel->setTextInteractionFlags(Qt::LinksAccessibleByMouse
+ | Qt::LinksAccessibleByKeyboard);
+ helpLabel->setOpenExternalLinks(true);
+
+ // clang-format off
+ helpLabel->setText(Tr::tr(R"(
+The Copilot plugin requires node.js and the Copilot neovim plugin.
+If you install the neovim plugin as described in the
+[README.md](https://github.com/github/copilot.vim),
+the plugin will find the agent.js file automatically.
+
+Otherwise you need to specify the path to the
+[agent.js](https://github.com/github/copilot.vim/tree/release/copilot/dist)
+file from the Copilot neovim plugin.
+ )", "Markdown text for the copilot instruction label"));
+
+ Column {
+ authWidget, br,
+ CopilotSettings::instance().nodeJsPath, br,
+ CopilotSettings::instance().distPath, br,
+ helpLabel, br,
+ st
+ }.attachTo(this);
+ // clang-format on
+
+ auto updateAuthWidget = [authWidget]() {
+ authWidget->updateClient(
+ FilePath::fromUserInput(
+ CopilotSettings::instance().nodeJsPath.volatileValue().toString()),
+ FilePath::fromUserInput(
+ CopilotSettings::instance().distPath.volatileValue().toString()));
+ };
+
+ connect(CopilotSettings::instance().nodeJsPath.pathChooser(),
+ &PathChooser::textChanged,
+ authWidget,
+ updateAuthWidget);
+ connect(CopilotSettings::instance().distPath.pathChooser(),
+ &PathChooser::textChanged,
+ authWidget,
+ updateAuthWidget);
+ updateAuthWidget();
+ }
+};
+
+CopilotOptionsPage::CopilotOptionsPage()
+{
+ setId("Copilot.General");
+ setDisplayName("Copilot");
+ setCategory("ZY.Copilot");
+ setDisplayCategory("Copilot");
+ setCategoryIconPath(":/copilot/images/settingscategory_copilot.png");
+}
+
+CopilotOptionsPage::~CopilotOptionsPage() {}
+
+void CopilotOptionsPage::init() {}
+
+QWidget *CopilotOptionsPage::widget()
+{
+ return new CopilotOptionsPageWidget();
+}
+
+void CopilotOptionsPage::apply()
+{
+ CopilotSettings::instance().apply();
+ CopilotSettings::instance().writeSettings(Core::ICore::settings());
+}
+
+void CopilotOptionsPage::finish() {}
+
+CopilotOptionsPage &CopilotOptionsPage::instance()
+{
+ static CopilotOptionsPage settingsPage;
+ return settingsPage;
+}
+
+} // namespace Copilot
diff --git a/src/plugins/copilot/copilotoptionspage.h b/src/plugins/copilot/copilotoptionspage.h
new file mode 100644
index 00000000000..1124f74dea6
--- /dev/null
+++ b/src/plugins/copilot/copilotoptionspage.h
@@ -0,0 +1,25 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <coreplugin/dialogs/ioptionspage.h>
+
+namespace Copilot {
+
+class CopilotOptionsPage : public Core::IOptionsPage
+{
+public:
+ CopilotOptionsPage();
+ ~CopilotOptionsPage() override;
+
+ static CopilotOptionsPage &instance();
+
+ void init();
+
+ QWidget *widget() override;
+ void apply() override;
+ void finish() override;
+};
+
+} // namespace Copilot
diff --git a/src/plugins/copilot/copilotplugin.cpp b/src/plugins/copilot/copilotplugin.cpp
new file mode 100644
index 00000000000..67e07b170c5
--- /dev/null
+++ b/src/plugins/copilot/copilotplugin.cpp
@@ -0,0 +1,64 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "copilotplugin.h"
+
+#include "copilotclient.h"
+#include "copilotoptionspage.h"
+#include "copilotsettings.h"
+#include "copilottr.h"
+
+#include <coreplugin/actionmanager/actionmanager.h>
+#include <coreplugin/editormanager/editormanager.h>
+#include <coreplugin/icore.h>
+
+#include <languageclient/languageclientmanager.h>
+
+#include <texteditor/texteditor.h>
+
+#include <QAction>
+
+using namespace Utils;
+using namespace Core;
+
+namespace Copilot {
+namespace Internal {
+
+void CopilotPlugin::initialize()
+{
+ CopilotSettings::instance().readSettings(ICore::settings());
+
+ restartClient();
+
+ connect(&CopilotSettings::instance(),
+ &CopilotSettings::applied,
+ this,
+ &CopilotPlugin::restartClient);
+
+ QAction *action = new QAction(this);
+ action->setText(Tr::tr("Request Copilot Suggestion"));
+ action->setToolTip(Tr::tr("Request Copilot Suggestion at the current editors cursor position."));
+
+ QObject::connect(action, &QAction::triggered, this, [this] {
+ if (auto editor = TextEditor::TextEditorWidget::currentTextEditorWidget()) {
+ if (m_client->reachable())
+ m_client->requestCompletions(editor);
+ }
+ });
+ ActionManager::registerAction(action, "Copilot.RequestSuggestion");
+}
+
+void CopilotPlugin::extensionsInitialized()
+{
+ CopilotOptionsPage::instance().init();
+}
+
+void CopilotPlugin::restartClient()
+{
+ LanguageClient::LanguageClientManager::shutdownClient(m_client);
+ m_client = new CopilotClient(CopilotSettings::instance().nodeJsPath.filePath(),
+ CopilotSettings::instance().distPath.filePath());
+}
+
+} // namespace Internal
+} // namespace Copilot
diff --git a/src/plugins/copilot/copilotplugin.h b/src/plugins/copilot/copilotplugin.h
new file mode 100644
index 00000000000..5d915030653
--- /dev/null
+++ b/src/plugins/copilot/copilotplugin.h
@@ -0,0 +1,32 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "copilotclient.h"
+
+#include <extensionsystem/iplugin.h>
+
+#include <QPointer>
+
+namespace TextEditor { class TextEditorWidget; }
+
+namespace Copilot {
+namespace Internal {
+
+class CopilotPlugin : public ExtensionSystem::IPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Copilot.json")
+
+public:
+ void initialize() override;
+ void extensionsInitialized() override;
+ void restartClient();
+
+private:
+ QPointer<CopilotClient> m_client;
+};
+
+} // namespace Internal
+} // namespace Copilot
diff --git a/src/plugins/copilot/copilotsettings.cpp b/src/plugins/copilot/copilotsettings.cpp
new file mode 100644
index 00000000000..ed298a0ab78
--- /dev/null
+++ b/src/plugins/copilot/copilotsettings.cpp
@@ -0,0 +1,65 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "copilotsettings.h"
+#include "copilottr.h"
+
+#include <utils/algorithm.h>
+#include <utils/environment.h>
+
+using namespace Utils;
+
+namespace Copilot {
+
+CopilotSettings &CopilotSettings::instance()
+{
+ static CopilotSettings settings;
+ return settings;
+}
+
+CopilotSettings::CopilotSettings()
+{
+ setAutoApply(false);
+
+ const FilePath nodeFromPath = FilePath("node").searchInPath();
+
+ const FilePaths searchDirs
+ = {FilePath::fromUserInput("~/.vim/pack/github/start/copilot.vim/copilot/dist/agent.js"),
+ FilePath::fromUserInput(
+ "~/.config/nvim/pack/github/start/copilot.vim/copilot/dist/agent.js"),
+ FilePath::fromUserInput(
+ "~/vimfiles/pack/github/start/copilot.vim/copilot/dist/agent.js"),
+ FilePath::fromUserInput(
+ "~/AppData/Local/nvim/pack/github/start/copilot.vim/copilot/dist/agent.js")};
+
+ const FilePath distFromVim = Utils::findOrDefault(searchDirs, [](const FilePath &fp) {
+ return fp.exists();
+ });
+
+ nodeJsPath.setExpectedKind(PathChooser::ExistingCommand);
+ nodeJsPath.setDefaultFilePath(nodeFromPath);
+ nodeJsPath.setSettingsKey("Copilot.NodeJsPath");
+ nodeJsPath.setDisplayStyle(StringAspect::PathChooserDisplay);
+ nodeJsPath.setLabelText(Tr::tr("Node.js path:"));
+ nodeJsPath.setHistoryCompleter("Copilot.NodePath.History");
+ nodeJsPath.setDisplayName(Tr::tr("Node.js Path"));
+ nodeJsPath.setToolTip(
+ Tr::tr("Select path to node.js executable. See https://nodejs.org/de/download/"
+ "for installation instructions."));
+
+ distPath.setExpectedKind(PathChooser::File);
+ distPath.setDefaultFilePath(distFromVim);
+ distPath.setSettingsKey("Copilot.DistPath");
+ distPath.setDisplayStyle(StringAspect::PathChooserDisplay);
+ distPath.setLabelText(Tr::tr("Path to agent.js:"));
+ distPath.setToolTip(Tr::tr(
+ "Select path to agent.js in copilot neovim plugin. See "
+ "https://github.com/github/copilot.vim#getting-started for installation instructions."));
+ distPath.setHistoryCompleter("Copilot.DistPath.History");
+ distPath.setDisplayName(Tr::tr("Agent.js path"));
+
+ registerAspect(&nodeJsPath);
+ registerAspect(&distPath);
+}
+
+} // namespace Copilot
diff --git a/src/plugins/copilot/copilotsettings.h b/src/plugins/copilot/copilotsettings.h
new file mode 100644
index 00000000000..d089410216b
--- /dev/null
+++ b/src/plugins/copilot/copilotsettings.h
@@ -0,0 +1,21 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <utils/aspects.h>
+
+namespace Copilot {
+
+class CopilotSettings : public Utils::AspectContainer
+{
+public:
+ CopilotSettings();
+
+ static CopilotSettings &instance();
+
+ Utils::StringAspect nodeJsPath;
+ Utils::StringAspect distPath;
+};
+
+} // namespace Copilot
diff --git a/src/plugins/copilot/copilotsuggestion.cpp b/src/plugins/copilot/copilotsuggestion.cpp
new file mode 100644
index 00000000000..96ccbbcd18a
--- /dev/null
+++ b/src/plugins/copilot/copilotsuggestion.cpp
@@ -0,0 +1,41 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "copilotsuggestion.h"
+
+namespace Copilot::Internal {
+
+CopilotSuggestion::CopilotSuggestion(const QList<Completion> &completions,
+ QTextDocument *origin,
+ int currentCompletion)
+ : m_completions(completions)
+ , m_currentCompletion(currentCompletion)
+{
+ const Completion completion = completions.value(currentCompletion);
+ document()->setPlainText(completion.text());
+ m_start = completion.position().toTextCursor(origin);
+ m_start.setKeepPositionOnInsert(true);
+ setCurrentPosition(m_start.position());
+}
+
+bool CopilotSuggestion::apply()
+{
+ reset();
+ const Completion completion = m_completions.value(m_currentCompletion);
+ QTextCursor cursor = completion.range().toSelection(m_start.document());
+ cursor.insertText(completion.text());
+ return true;
+}
+
+void CopilotSuggestion::reset()
+{
+ m_start.removeSelectedText();
+}
+
+int CopilotSuggestion::position()
+{
+ return m_start.position();
+}
+
+} // namespace Copilot::Internal
+
diff --git a/src/plugins/copilot/copilotsuggestion.h b/src/plugins/copilot/copilotsuggestion.h
new file mode 100644
index 00000000000..5374ab74c05
--- /dev/null
+++ b/src/plugins/copilot/copilotsuggestion.h
@@ -0,0 +1,30 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#pragma once
+
+#include "requests/getcompletions.h"
+
+#include <texteditor/textdocumentlayout.h>
+
+namespace Copilot::Internal {
+
+class CopilotSuggestion final : public TextEditor::TextSuggestion
+{
+public:
+ CopilotSuggestion(const QList<Completion> &completions,
+ QTextDocument *origin,
+ int currentCompletion = 0);
+
+ bool apply() final;
+ void reset() final;
+ int position() final;
+
+ const QList<Completion> &completions() const { return m_completions; }
+ const int currentCompletion() const { return m_currentCompletion; }
+
+private:
+ QList<Completion> m_completions;
+ int m_currentCompletion = 0;
+ QTextCursor m_start;
+};
+} // namespace Copilot::Internal
diff --git a/src/plugins/copilot/copilottr.h b/src/plugins/copilot/copilottr.h
new file mode 100644
index 00000000000..42fef818db0
--- /dev/null
+++ b/src/plugins/copilot/copilottr.h
@@ -0,0 +1,15 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <QCoreApplication>
+
+namespace Copilot {
+
+struct Tr
+{
+ Q_DECLARE_TR_FUNCTIONS(QtC::Copilot)
+};
+
+} // namespace Copilot
diff --git a/src/plugins/copilot/images/settingscategory_copilot.png b/src/plugins/copilot/images/settingscategory_copilot.png
new file mode 100644
index 00000000000..674fece4c51
--- /dev/null
+++ b/src/plugins/copilot/images/settingscategory_copilot.png
Binary files differ
diff --git a/src/plugins/copilot/images/[email protected] b/src/plugins/copilot/images/[email protected]
new file mode 100644
index 00000000000..a2d45562f7f
--- /dev/null
+++ b/src/plugins/copilot/images/[email protected]
Binary files differ
diff --git a/src/plugins/copilot/requests/checkstatus.h b/src/plugins/copilot/requests/checkstatus.h
new file mode 100644
index 00000000000..20e77eb1457
--- /dev/null
+++ b/src/plugins/copilot/requests/checkstatus.h
@@ -0,0 +1,54 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <languageserverprotocol/jsonrpcmessages.h>
+#include <languageserverprotocol/lsptypes.h>
+
+namespace Copilot {
+
+class CheckStatusParams : public LanguageServerProtocol::JsonObject
+{
+ static constexpr char16_t optionsKey[] = u"options";
+ static constexpr char16_t localChecksOnlyKey[] = u"options";
+
+public:
+ using JsonObject::JsonObject;
+
+ CheckStatusParams(bool localChecksOnly = false) { setLocalChecksOnly(localChecksOnly); }
+
+ void setLocalChecksOnly(bool localChecksOnly)
+ {
+ QJsonObject options;
+ options.insert(localChecksOnlyKey, localChecksOnly);
+ setOptions(options);
+ }
+
+ void setOptions(QJsonObject options) { insert(optionsKey, options); }
+};
+
+class CheckStatusResponse : public LanguageServerProtocol::JsonObject
+{
+ static constexpr char16_t userKey[] = u"user";
+ static constexpr char16_t statusKey[] = u"status";
+
+public:
+ using JsonObject::JsonObject;
+
+ QString status() const { return typedValue<QString>(statusKey); }
+ QString user() const { return typedValue<QString>(userKey); }
+};
+
+class CheckStatusRequest
+ : public LanguageServerProtocol::Request<CheckStatusResponse, std::nullptr_t, CheckStatusParams>
+{
+public:
+ explicit CheckStatusRequest(const CheckStatusParams &params)
+ : Request(methodName, params)
+ {}
+ using Request::Request;
+ constexpr static const char methodName[] = "checkStatus";
+};
+
+} // namespace Copilot
diff --git a/src/plugins/copilot/requests/getcompletions.h b/src/plugins/copilot/requests/getcompletions.h
new file mode 100644
index 00000000000..d602fea97d3
--- /dev/null
+++ b/src/plugins/copilot/requests/getcompletions.h
@@ -0,0 +1,119 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <languageserverprotocol/jsonkeys.h>
+#include <languageserverprotocol/jsonrpcmessages.h>
+#include <languageserverprotocol/lsptypes.h>
+
+namespace Copilot {
+
+class Completion : public LanguageServerProtocol::JsonObject
+{
+ static constexpr char16_t displayTextKey[] = u"displayText";
+ static constexpr char16_t uuidKey[] = u"uuid";
+
+public:
+ using JsonObject::JsonObject;
+
+ QString displayText() const { return typedValue<QString>(displayTextKey); }
+ LanguageServerProtocol::Position position() const
+ {
+ return typedValue<LanguageServerProtocol::Position>(LanguageServerProtocol::positionKey);
+ }
+ LanguageServerProtocol::Range range() const
+ {
+ return typedValue<LanguageServerProtocol::Range>(LanguageServerProtocol::rangeKey);
+ }
+ QString text() const { return typedValue<QString>(LanguageServerProtocol::textKey); }
+ QString uuid() const { return typedValue<QString>(uuidKey); }
+
+ bool isValid() const override
+ {
+ return contains(LanguageServerProtocol::textKey)
+ && contains(LanguageServerProtocol::rangeKey)
+ && contains(LanguageServerProtocol::positionKey);
+ }
+};
+
+class GetCompletionParams : public LanguageServerProtocol::JsonObject
+{
+public:
+ static constexpr char16_t docKey[] = u"doc";
+
+ GetCompletionParams(const LanguageServerProtocol::TextDocumentIdentifier &document,
+ int version,
+ const LanguageServerProtocol::Position &position)
+ {
+ setTextDocument(document);
+ setVersion(version);
+ setPosition(position);
+ }
+ using JsonObject::JsonObject;
+
+ // The text document.
+ LanguageServerProtocol::TextDocumentIdentifier textDocument() const
+ {
+ return typedValue<LanguageServerProtocol::TextDocumentIdentifier>(docKey);
+ }
+ void setTextDocument(const LanguageServerProtocol::TextDocumentIdentifier &id)
+ {
+ insert(docKey, id);
+ }
+
+ // The position inside the text document.
+ LanguageServerProtocol::Position position() const
+ {
+ return LanguageServerProtocol::fromJsonValue<LanguageServerProtocol::Position>(
+ value(docKey).toObject().value(LanguageServerProtocol::positionKey));
+ }
+ void setPosition(const LanguageServerProtocol::Position &position)
+ {
+ QJsonObject result = value(docKey).toObject();
+ result[LanguageServerProtocol::positionKey] = (QJsonObject) position;
+ insert(docKey, result);
+ }
+
+ // The version
+ int version() const { return typedValue<int>(LanguageServerProtocol::versionKey); }
+ void setVersion(int version)
+ {
+ QJsonObject result = value(docKey).toObject();
+ result[LanguageServerProtocol::versionKey] = version;
+ insert(docKey, result);
+ }
+
+ bool isValid() const override
+ {
+ return contains(docKey)
+ && value(docKey).toObject().contains(LanguageServerProtocol::positionKey)
+ && value(docKey).toObject().contains(LanguageServerProtocol::versionKey);
+ }
+};
+
+class GetCompletionResponse : public LanguageServerProtocol::JsonObject
+{
+ static constexpr char16_t completionKey[] = u"completions";
+
+public:
+ using JsonObject::JsonObject;
+
+ LanguageServerProtocol::LanguageClientArray<Completion> completions() const
+ {
+ return clientArray<Completion>(completionKey);
+ }
+};
+
+class GetCompletionRequest
+ : public LanguageServerProtocol::Request<GetCompletionResponse, std::nullptr_t, GetCompletionParams>
+{
+public:
+ explicit GetCompletionRequest(const GetCompletionParams &params = {})
+ : Request(methodName, params)
+ {}
+ using Request::Request;
+ constexpr static const char methodName[] = "getCompletionsCycling";
+};
+
+} // namespace Copilot
diff --git a/src/plugins/copilot/requests/signinconfirm.h b/src/plugins/copilot/requests/signinconfirm.h
new file mode 100644
index 00000000000..64f4ce7d53d
--- /dev/null
+++ b/src/plugins/copilot/requests/signinconfirm.h
@@ -0,0 +1,36 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "checkstatus.h"
+
+#include <languageserverprotocol/jsonrpcmessages.h>
+#include <languageserverprotocol/lsptypes.h>
+
+namespace Copilot {
+
+class SignInConfirmParams : public LanguageServerProtocol::JsonObject
+{
+ static constexpr char16_t userCodeKey[] = u"userCode";
+
+public:
+ using JsonObject::JsonObject;
+
+ SignInConfirmParams(const QString &userCode) { setUserCode(userCode); }
+
+ void setUserCode(const QString &userCode) { insert(userCodeKey, userCode); }
+};
+
+class SignInConfirmRequest
+ : public LanguageServerProtocol::Request<CheckStatusResponse, std::nullptr_t, SignInConfirmParams>
+{
+public:
+ explicit SignInConfirmRequest(const QString &userCode)
+ : Request(methodName, {userCode})
+ {}
+ using Request::Request;
+ constexpr static const char methodName[] = "signInConfirm";
+};
+
+} // namespace Copilot
diff --git a/src/plugins/copilot/requests/signininitiate.h b/src/plugins/copilot/requests/signininitiate.h
new file mode 100644
index 00000000000..005205e6e01
--- /dev/null
+++ b/src/plugins/copilot/requests/signininitiate.h
@@ -0,0 +1,43 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <languageserverprotocol/jsonrpcmessages.h>
+#include <languageserverprotocol/lsptypes.h>
+
+namespace Copilot {
+
+using SignInInitiateParams = LanguageServerProtocol::JsonObject;
+
+class SignInInitiateResponse : public LanguageServerProtocol::JsonObject
+{
+ static constexpr char16_t verificationUriKey[] = u"verificationUri";
+ static constexpr char16_t userCodeKey[] = u"userCode";
+
+public:
+ using JsonObject::JsonObject;
+
+public:
+ QString verificationUri() const { return typedValue<QString>(verificationUriKey); }
+ QString userCode() const { return typedValue<QString>(userCodeKey); }
+};
+
+class SignInInitiateRequest : public LanguageServerProtocol::Request<SignInInitiateResponse,
+ std::nullptr_t,
+ SignInInitiateParams>
+{
+public:
+ explicit SignInInitiateRequest()
+ : Request(methodName, {})
+ {}
+ using Request::Request;
+ constexpr static const char methodName[] = "signInInitiate";
+};
+
+} // namespace Copilot
diff --git a/src/plugins/copilot/requests/signout.h b/src/plugins/copilot/requests/signout.h
new file mode 100644
index 00000000000..944c10d414b
--- /dev/null
+++ b/src/plugins/copilot/requests/signout.h
@@ -0,0 +1,26 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "checkstatus.h"
+
+#include <languageserverprotocol/jsonrpcmessages.h>
+#include <languageserverprotocol/lsptypes.h>
+
+namespace Copilot {
+
+using SignOutParams = LanguageServerProtocol::JsonObject;
+
+class SignOutRequest
+ : public LanguageServerProtocol::Request<CheckStatusResponse, std::nullptr_t, SignOutParams>
+{
+public:
+ explicit SignOutRequest()
+ : Request(methodName, {})
+ {}
+ using Request::Request;
+ constexpr static const char methodName[] = "signOut";
+};
+
+} // namespace Copilot
diff --git a/src/plugins/coreplugin/actionmanager/actionmanager.cpp b/src/plugins/coreplugin/actionmanager/actionmanager.cpp
index 3d0a200283c..54e58dc217a 100644
--- a/src/plugins/coreplugin/actionmanager/actionmanager.cpp
+++ b/src/plugins/coreplugin/actionmanager/actionmanager.cpp
@@ -34,8 +34,6 @@ namespace Core::Internal {
class PresentationModeHandler : public QObject
{
- Q_OBJECT
-
public:
void connectCommand(Command *command);
@@ -520,5 +518,3 @@ void ActionManagerPrivate::saveSettings()
saveSettings(j.value());
}
}
-
-#include "actionmanager.moc"
diff --git a/src/plugins/coreplugin/actionsfilter.cpp b/src/plugins/coreplugin/actionsfilter.cpp
index 927e7da6331..00c1be83e52 100644
--- a/src/plugins/coreplugin/actionsfilter.cpp
+++ b/src/plugins/coreplugin/actionsfilter.cpp
@@ -12,7 +12,6 @@
#include <utils/algorithm.h>
#include <utils/fuzzymatcher.h>
-#include <utils/mapreduce.h>
#include <utils/qtcassert.h>
#include <utils/stringutils.h>
@@ -21,6 +20,7 @@
#include <QMenuBar>
#include <QPointer>
#include <QRegularExpression>
+#include <QtConcurrent>
#include <QTextDocument>
static const char lastTriggeredC[] = "LastTriggeredActions";
@@ -155,8 +155,8 @@ QList<LocatorFilterEntry> ActionsFilter::matchesFor(QFutureInterface<LocatorFilt
};
QMap<MatchLevel, QList<LocatorFilterEntry>> filtered;
- const QList<std::optional<FilterResult>> filterResults = Utils::map(std::as_const(m_entries), filter)
- .results();
+ const QList<std::optional<FilterResult>> filterResults
+ = QtConcurrent::blockingMapped(m_entries, filter);
for (const std::optional<FilterResult> &filterResult : filterResults) {
if (filterResult)
filtered[filterResult->first] << filterResult->second;
diff --git a/src/plugins/coreplugin/coreplugin.qbs b/src/plugins/coreplugin/coreplugin.qbs
index be30292174b..b99b8be2dfb 100644
--- a/src/plugins/coreplugin/coreplugin.qbs
+++ b/src/plugins/coreplugin/coreplugin.qbs
@@ -271,9 +271,7 @@ Project {
]
}
- Group {
- name: "Tests"
- condition: qtc.testsEnabled
+ QtcTestFiles {
files: [
"testdatadir.cpp",
"testdatadir.h",
diff --git a/src/plugins/coreplugin/editormanager/documentmodel.cpp b/src/plugins/coreplugin/editormanager/documentmodel.cpp
index 04a877ec491..876b22b1281 100644
--- a/src/plugins/coreplugin/editormanager/documentmodel.cpp
+++ b/src/plugins/coreplugin/editormanager/documentmodel.cpp
@@ -11,6 +11,8 @@
#include <utils/algorithm.h>
#include <utils/dropsupport.h>
+#include <utils/filepath.h>
+#include <utils/fileutils.h>
#include <utils/hostosinfo.h>
#include <utils/qtcassert.h>
#include <utils/utilsicons.h>
@@ -121,63 +123,55 @@ DocumentModel::Entry *DocumentModelPrivate::addEntry(DocumentModel::Entry *entry
bool DocumentModelPrivate::disambiguateDisplayNames(DocumentModel::Entry *entry)
{
const QString displayName = entry->plainDisplayName();
- int minIdx = -1, maxIdx = -1;
- QList<DynamicEntry> dups;
+ QList<DocumentModel::Entry *> dups;
+ FilePaths paths;
+ int minIdx = m_entries.count();
+ int maxIdx = 0;
- for (int i = 0, total = m_entries.count(); i < total; ++i) {
+ for (int i = 0; i < m_entries.count(); ++i) {
DocumentModel::Entry *e = m_entries.at(i);
if (e == entry || e->plainDisplayName() == displayName) {
- e->document->setUniqueDisplayName(QString());
- dups += DynamicEntry(e);
- maxIdx = i;
- if (minIdx < 0)
+ if (minIdx > i)
minIdx = i;
+ if (maxIdx < i)
+ maxIdx = i;
+ dups += e;
+ if (!e->filePath().isEmpty())
+ paths += e->filePath();
}
}
- const int dupsCount = dups.count();
- if (dupsCount == 0)
+ const auto triggerDataChanged = [this](int minIdx, int maxIdx) {
+ const QModelIndex idxMin = index(minIdx + 1 /*<no document>*/, 0);
+ const QModelIndex idxMax = index(maxIdx + 1 /*<no document>*/, 0);
+ if (idxMin.isValid() && idxMax.isValid())
+ emit dataChanged(idxMin, idxMax);
+ };
+
+ if (dups.count() == 1) {
+ dups.at(0)->document->setUniqueDisplayName({});
+ triggerDataChanged(minIdx, maxIdx);
return false;
+ }
- if (dupsCount > 1) {
- int serial = 0;
- int count = 0;
- // increase uniqueness unless no dups are left
- forever {
- bool seenDups = false;
- for (int i = 0; i < dupsCount - 1; ++i) {
- DynamicEntry &e = dups[i];
- const Utils::FilePath myFileName = e->document->filePath();
- if (e->document->isTemporary() || myFileName.isEmpty() || count > 10) {
- // path-less entry, append number
- e.setNumberedName(++serial);
- continue;
- }
- for (int j = i + 1; j < dupsCount; ++j) {
- DynamicEntry &e2 = dups[j];
- if (e->displayName().compare(e2->displayName(), Utils::HostOsInfo::fileNameCaseSensitivity()) == 0) {
- const Utils::FilePath otherFileName = e2->document->filePath();
- if (otherFileName.isEmpty())
- continue;
- seenDups = true;
- e2.disambiguate();
- if (j > maxIdx)
- maxIdx = j;
- }
- }
- if (seenDups) {
- e.disambiguate();
- ++count;
- break;
- }
- }
- if (!seenDups)
- break;
+ const FilePath commonAncestor = FileUtils::commonPath(paths);
+
+ int countWithoutFilePath = 0;
+ for (DocumentModel::Entry *e : std::as_const(dups)) {
+ const FilePath path = e->filePath();
+ if (path.isEmpty()) {
+ e->document->setUniqueDisplayName(QStringLiteral("%1 (%2)")
+ .arg(e->document->displayName())
+ .arg(++countWithoutFilePath));
+ continue;
+ }
+ const QString uniqueDisplayName = path.relativeChildPath(commonAncestor).toString();
+ if (uniqueDisplayName != "" && e->document->uniqueDisplayName() != uniqueDisplayName) {
+ e->document->setUniqueDisplayName(uniqueDisplayName);
}
}
-
- emit dataChanged(index(minIdx + 1, 0), index(maxIdx + 1, 0));
+ triggerDataChanged(minIdx, maxIdx);
return true;
}
@@ -483,30 +477,6 @@ void DocumentModelPrivate::removeAllSuspendedEntries(PinnedFileRemovalPolicy pin
}
}
-DocumentModelPrivate::DynamicEntry::DynamicEntry(DocumentModel::Entry *e) :
- entry(e),
- pathComponents(0)
-{
-}
-
-DocumentModel::Entry *DocumentModelPrivate::DynamicEntry::operator->() const
-{
- return entry;
-}
-
-void DocumentModelPrivate::DynamicEntry::disambiguate()
-{
- const QString display = entry->filePath().fileNameWithPathComponents(++pathComponents);
- entry->document->setUniqueDisplayName(display);
-}
-
-void DocumentModelPrivate::DynamicEntry::setNumberedName(int number)
-{
- entry->document->setUniqueDisplayName(QStringLiteral("%1 (%2)")
- .arg(entry->document->displayName())
- .arg(number));
-}
-
} // Internal
DocumentModel::Entry::Entry() :
diff --git a/src/plugins/coreplugin/editormanager/documentmodel_p.h b/src/plugins/coreplugin/editormanager/documentmodel_p.h
index 8e3efa98b0b..6b99d3fe438 100644
--- a/src/plugins/coreplugin/editormanager/documentmodel_p.h
+++ b/src/plugins/coreplugin/editormanager/documentmodel_p.h
@@ -61,18 +61,6 @@ public:
void itemChanged(IDocument *document);
- class DynamicEntry
- {
- public:
- DocumentModel::Entry *entry;
- int pathComponents;
-
- DynamicEntry(DocumentModel::Entry *e);
- DocumentModel::Entry *operator->() const;
- void disambiguate();
- void setNumberedName(int number);
- };
-
QList<DocumentModel::Entry *> m_entries;
QMap<IDocument *, QList<IEditor *> > m_editors;
QHash<Utils::FilePath, DocumentModel::Entry *> m_entryByFixedPath;
diff --git a/src/plugins/coreplugin/editormanager/editormanager.cpp b/src/plugins/coreplugin/editormanager/editormanager.cpp
index 2953a833b70..f84465303fb 100644
--- a/src/plugins/coreplugin/editormanager/editormanager.cpp
+++ b/src/plugins/coreplugin/editormanager/editormanager.cpp
@@ -28,6 +28,7 @@
#include "../findplaceholder.h"
#include "../icore.h"
#include "../iversioncontrol.h"
+#include "../locator/ilocatorfilter.h"
#include "../modemanager.h"
#include "../outputpane.h"
#include "../outputpanemanager.h"
@@ -3157,6 +3158,17 @@ IEditor *EditorManager::openEditorAt(const Link &link,
newEditor);
}
+IEditor *EditorManager::openEditor(const LocatorFilterEntry &entry)
+{
+ const OpenEditorFlags defaultFlags = EditorManager::AllowExternalEditor;
+ if (entry.linkForEditor)
+ return EditorManager::openEditorAt(*entry.linkForEditor, {}, defaultFlags);
+ else if (!entry.filePath.isEmpty())
+ return EditorManager::openEditor(entry.filePath, {}, defaultFlags);
+ return nullptr;
+}
+
+
/*!
Opens the document at the position of the search result \a item using the
editor type \a editorId and the specified \a flags.
diff --git a/src/plugins/coreplugin/editormanager/editormanager.h b/src/plugins/coreplugin/editormanager/editormanager.h
index c1c4c4644c3..6118d15c3dd 100644
--- a/src/plugins/coreplugin/editormanager/editormanager.h
+++ b/src/plugins/coreplugin/editormanager/editormanager.h
@@ -9,8 +9,8 @@
#include "documentmodel.h"
#include "ieditor.h"
-#include "utils/link.h"
-#include "utils/textfileformat.h"
+#include <utils/link.h>
+#include <utils/textfileformat.h>
#include <QFileDialog>
#include <QList>
@@ -18,15 +18,16 @@
#include <functional>
-QT_FORWARD_DECLARE_CLASS(QMenu)
+QT_BEGIN_NAMESPACE
+class QMenu;
+QT_END_NAMESPACE
-namespace Utils {
-class MimeType;
-}
+namespace Utils { class MimeType; }
namespace Core {
class IDocument;
+class LocatorFilterEntry;
class SearchResultItem;
namespace Internal {
@@ -76,6 +77,7 @@ public:
Utils::Id editorId = {},
OpenEditorFlags flags = NoFlags,
bool *newEditor = nullptr);
+ static IEditor *openEditor(const LocatorFilterEntry &entry);
static void openEditorAtSearchResult(const SearchResultItem &item,
Utils::Id editorId = {},
diff --git a/src/plugins/coreplugin/fileutils.cpp b/src/plugins/coreplugin/fileutils.cpp
index 6c783488971..a48cd34da70 100644
--- a/src/plugins/coreplugin/fileutils.cpp
+++ b/src/plugins/coreplugin/fileutils.cpp
@@ -19,6 +19,7 @@
#include <utils/hostosinfo.h>
#include <utils/qtcprocess.h>
#include <utils/terminalcommand.h>
+#include <utils/terminalhooks.h>
#include <utils/textfileformat.h>
#include <utils/unixutils.h>
@@ -75,7 +76,8 @@ void FileUtils::showInGraphicalShell(QWidget *parent, const FilePath &pathIn)
const QString folder = fileInfo.isDir() ? fileInfo.absoluteFilePath() : fileInfo.filePath();
const QString app = UnixUtils::fileBrowser(ICore::settings());
QStringList browserArgs = ProcessArgs::splitArgs(
- UnixUtils::substituteFileBrowserParameters(app, folder));
+ UnixUtils::substituteFileBrowserParameters(app, folder),
+ HostOsInfo::hostOs());
QString error;
if (browserArgs.isEmpty()) {
error = Tr::tr("The command for file browser is not set.");
@@ -104,8 +106,7 @@ void FileUtils::showInFileSystemView(const FilePath &path)
void FileUtils::openTerminal(const FilePath &path, const Environment &env)
{
- QTC_ASSERT(DeviceFileHooks::instance().openTerminal, return);
- DeviceFileHooks::instance().openTerminal(path, env);
+ Terminal::Hooks::instance().openTerminal({std::nullopt, path, env});
}
QString FileUtils::msgFindInDirectory()
diff --git a/src/plugins/coreplugin/find/searchresultwidget.cpp b/src/plugins/coreplugin/find/searchresultwidget.cpp
index 36344ed804d..5959cd40fda 100644
--- a/src/plugins/coreplugin/find/searchresultwidget.cpp
+++ b/src/plugins/coreplugin/find/searchresultwidget.cpp
@@ -93,7 +93,7 @@ SearchResultWidget::SearchResultWidget(QWidget *parent) :
topLayout->addWidget(m_topReplaceWidget);
m_messageWidget = new QFrame;
- pal.setColor(QPalette::WindowText, creatorTheme()->color(Theme::CanceledSearchTextColor));
+ pal.setColor(QPalette::WindowText, creatorTheme()->color(Theme::TextColorError));
m_messageWidget->setPalette(pal);
if (creatorTheme()->flag(Theme::DrawSearchResultWidgetFrame)) {
m_messageWidget->setFrameStyle(QFrame::Panel | QFrame::Raised);
diff --git a/src/plugins/coreplugin/locator/basefilefilter.cpp b/src/plugins/coreplugin/locator/basefilefilter.cpp
index 9ce27cc0528..3f8e87af20f 100644
--- a/src/plugins/coreplugin/locator/basefilefilter.cpp
+++ b/src/plugins/coreplugin/locator/basefilefilter.cpp
@@ -3,10 +3,8 @@
#include "basefilefilter.h"
-#include <coreplugin/editormanager/editormanager.h>
#include <utils/algorithm.h>
#include <utils/filepath.h>
-#include <utils/linecolumn.h>
#include <utils/link.h>
#include <utils/qtcassert.h>
@@ -193,27 +191,6 @@ QList<LocatorFilterEntry> BaseFileFilter::matchesFor(QFutureInterface<LocatorFil
}
/*!
- \reimp
-*/
-void BaseFileFilter::accept(const LocatorFilterEntry &selection,
- QString *newText, int *selectionStart, int *selectionLength) const
-{
- Q_UNUSED(newText)
- Q_UNUSED(selectionStart)
- Q_UNUSED(selectionLength)
- openEditorAt(selection);
-}
-
-void BaseFileFilter::openEditorAt(const LocatorFilterEntry &entry)
-{
- if (entry.linkForEditor) {
- EditorManager::openEditorAt(*entry.linkForEditor, {}, EditorManager::AllowExternalEditor);
- return;
- }
- EditorManager::openEditor(entry.filePath, {}, EditorManager::AllowExternalEditor);
-}
-
-/*!
Takes ownership of the \a iterator. The previously set iterator might not be deleted until
a currently running search is finished.
*/
diff --git a/src/plugins/coreplugin/locator/basefilefilter.h b/src/plugins/coreplugin/locator/basefilefilter.h
index 710cd21edf4..55289e57b0a 100644
--- a/src/plugins/coreplugin/locator/basefilefilter.h
+++ b/src/plugins/coreplugin/locator/basefilefilter.h
@@ -46,10 +46,6 @@ public:
void prepareSearch(const QString &entry) override;
QList<LocatorFilterEntry> matchesFor(QFutureInterface<LocatorFilterEntry> &future,
const QString &entry) override;
- void accept(const LocatorFilterEntry &selection,
- QString *newText, int *selectionStart, int *selectionLength) const override;
- static void openEditorAt(const LocatorFilterEntry &entry);
-
protected:
void setFileIterator(Iterator *iterator);
QSharedPointer<Iterator> fileIterator();
diff --git a/src/plugins/coreplugin/locator/directoryfilter.cpp b/src/plugins/coreplugin/locator/directoryfilter.cpp
index 19a9f098e62..785c411f19a 100644
--- a/src/plugins/coreplugin/locator/directoryfilter.cpp
+++ b/src/plugins/coreplugin/locator/directoryfilter.cpp
@@ -7,12 +7,12 @@
#include "../coreplugintr.h"
#include <utils/algorithm.h>
+#include <utils/asynctask.h>
#include <utils/fileutils.h>
#include <utils/filesearch.h>
#include <utils/layoutbuilder.h>
#include <QCheckBox>
-#include <QCoreApplication>
#include <QDialog>
#include <QDialogButtonBox>
#include <QJsonArray>
@@ -46,6 +46,28 @@ static QString defaultDisplayName()
return Tr::tr("Generic Directory Filter");
}
+static void refresh(QPromise<FilePaths> &promise, const FilePaths &directories,
+ const QStringList &filters, const QStringList &exclusionFilters,
+ const QString &displayName)
+{
+ SubDirFileIterator subDirIterator(directories, filters, exclusionFilters);
+ promise.setProgressRange(0, subDirIterator.maxProgress());
+ FilePaths files;
+ const auto end = subDirIterator.end();
+ for (auto it = subDirIterator.begin(); it != end; ++it) {
+ if (promise.isCanceled()) {
+ promise.setProgressValueAndText(subDirIterator.currentProgress(),
+ Tr::tr("%1 filter update: canceled").arg(displayName));
+ return;
+ }
+ files << (*it).filePath;
+ promise.setProgressValueAndText(subDirIterator.currentProgress(),
+ Tr::tr("%1 filter update: %n files", nullptr, files.size()).arg(displayName));
+ }
+ promise.setProgressValue(subDirIterator.maxProgress());
+ promise.addResult(files);
+}
+
DirectoryFilter::DirectoryFilter(Id id)
: m_filters(kFiltersDefault)
, m_exclusionFilters(kExclusionFiltersDefault)
@@ -56,12 +78,32 @@ DirectoryFilter::DirectoryFilter(Id id)
setDescription(Tr::tr("Matches all files from a custom set of directories. Append \"+<number>\" "
"or \":<number>\" to jump to the given line number. Append another "
"\"+<number>\" or \":<number>\" to jump to the column number as well."));
+
+ using namespace Tasking;
+ const auto groupSetup = [this] {
+ if (!m_directories.isEmpty())
+ return TaskAction::Continue; // Async task will run
+ m_files.clear();
+ updateFileIterator();
+ return TaskAction::StopWithDone; // Group stops, skips async task
+ };
+ const auto asyncSetup = [this](AsyncTask<FilePaths> &async) {
+ async.setConcurrentCallData(&refresh, m_directories, m_filters, m_exclusionFilters,
+ displayName());
+ };
+ const auto asyncDone = [this](const AsyncTask<FilePaths> &async) {
+ m_files = async.isResultAvailable() ? async.result() : FilePaths();
+ updateFileIterator();
+ };
+ const Group root {
+ OnGroupSetup(groupSetup),
+ Async<FilePaths>(asyncSetup, asyncDone)
+ };
+ setRefreshRecipe(root);
}
void DirectoryFilter::saveState(QJsonObject &object) const
{
- QMutexLocker locker(&m_lock); // m_files is modified in other thread
-
if (displayName() != defaultDisplayName())
object.insert(kDisplayNameKey, displayName());
if (!m_directories.isEmpty()) {
@@ -92,7 +134,6 @@ static FilePaths toFilePaths(const QJsonArray &array)
void DirectoryFilter::restoreState(const QJsonObject &object)
{
- QMutexLocker locker(&m_lock);
setDisplayName(object.value(kDisplayNameKey).toString(defaultDisplayName()));
m_directories = toFilePaths(object.value(kDirectoriesKey).toArray());
m_filters = toStringList(
@@ -107,8 +148,6 @@ void DirectoryFilter::restoreState(const QByteArray &state)
{
if (isOldSetting(state)) {
// TODO read old settings, remove some time after Qt Creator 4.15
- QMutexLocker locker(&m_lock);
-
QString name;
QStringList directories;
QString shortcut;
@@ -136,8 +175,6 @@ void DirectoryFilter::restoreState(const QByteArray &state)
setDisplayName(name);
setShortcutString(shortcut);
setIncludedByDefault(defaultFilter);
-
- locker.unlock();
} else {
ILocatorFilter::restoreState(state);
}
@@ -263,8 +300,6 @@ bool DirectoryFilter::openConfigDialog(QWidget *parent, bool &needsRefresh)
&DirectoryFilter::updateOptionButtons,
Qt::DirectConnection);
m_dialog->directoryList->clear();
- // Note: assuming we only change m_directories in the Gui thread,
- // we don't need to protect it here with mutex
m_dialog->directoryList->addItems(Utils::transform(m_directories, &FilePath::toString));
m_dialog->nameLabel->setVisible(m_isCustomFilter);
m_dialog->nameEdit->setVisible(m_isCustomFilter);
@@ -276,14 +311,10 @@ bool DirectoryFilter::openConfigDialog(QWidget *parent, bool &needsRefresh)
m_dialog->filePatternLabel->setText(Utils::msgFilePatternLabel());
m_dialog->filePatternLabel->setBuddy(m_dialog->filePattern);
m_dialog->filePattern->setToolTip(Utils::msgFilePatternToolTip());
- // Note: assuming we only change m_filters in the Gui thread,
- // we don't need to protect it here with mutex
m_dialog->filePattern->setText(Utils::transform(m_filters, &QDir::toNativeSeparators).join(','));
m_dialog->exclusionPatternLabel->setText(Utils::msgExclusionPatternLabel());
m_dialog->exclusionPatternLabel->setBuddy(m_dialog->exclusionPattern);
m_dialog->exclusionPattern->setToolTip(Utils::msgFilePatternToolTip());
- // Note: assuming we only change m_exclusionFilters in the Gui thread,
- // we don't need to protect it here with mutex
m_dialog->exclusionPattern->setText(
Utils::transform(m_exclusionFilters, &QDir::toNativeSeparators).join(','));
m_dialog->shortcutEdit->setText(shortcutString());
@@ -291,7 +322,6 @@ bool DirectoryFilter::openConfigDialog(QWidget *parent, bool &needsRefresh)
updateOptionButtons();
dialog.adjustSize();
if (dialog.exec() == QDialog::Accepted) {
- QMutexLocker locker(&m_lock);
bool directoriesChanged = false;
const FilePaths oldDirectories = m_directories;
const QStringList oldFilters = m_filters;
@@ -353,53 +383,9 @@ void DirectoryFilter::updateOptionButtons()
void DirectoryFilter::updateFileIterator()
{
- QMutexLocker locker(&m_lock);
setFileIterator(new BaseFileFilter::ListIterator(m_files));
}
-void DirectoryFilter::refresh(QFutureInterface<void> &future)
-{
- FilePaths directories;
- QStringList filters, exclusionFilters;
- {
- QMutexLocker locker(&m_lock);
- if (m_directories.isEmpty()) {
- m_files.clear();
- QMetaObject::invokeMethod(this, &DirectoryFilter::updateFileIterator,
- Qt::QueuedConnection);
- future.setProgressRange(0, 1);
- future.setProgressValueAndText(1, Tr::tr("%1 filter update: 0 files").arg(displayName()));
- return;
- }
- directories = m_directories;
- filters = m_filters;
- exclusionFilters = m_exclusionFilters;
- }
- Utils::SubDirFileIterator subDirIterator(directories, filters, exclusionFilters);
- future.setProgressRange(0, subDirIterator.maxProgress());
- Utils::FilePaths filesFound;
- auto end = subDirIterator.end();
- for (auto it = subDirIterator.begin(); it != end; ++it) {
- if (future.isCanceled())
- break;
- filesFound << (*it).filePath;
- if (future.isProgressUpdateNeeded()
- || future.progressValue() == 0 /*workaround for regression in Qt*/) {
- future.setProgressValueAndText(subDirIterator.currentProgress(),
- Tr::tr("%1 filter update: %n files", nullptr, filesFound.size()).arg(displayName()));
- }
- }
-
- if (!future.isCanceled()) {
- QMutexLocker locker(&m_lock);
- m_files = filesFound;
- QMetaObject::invokeMethod(this, &DirectoryFilter::updateFileIterator, Qt::QueuedConnection);
- future.setProgressValue(subDirIterator.maxProgress());
- } else {
- future.setProgressValueAndText(subDirIterator.currentProgress(), Tr::tr("%1 filter update: canceled").arg(displayName()));
- }
-}
-
void DirectoryFilter::setIsCustomFilter(bool value)
{
m_isCustomFilter = value;
@@ -409,10 +395,7 @@ void DirectoryFilter::setDirectories(const FilePaths &directories)
{
if (directories == m_directories)
return;
- {
- QMutexLocker locker(&m_lock);
- m_directories = directories;
- }
+ m_directories = directories;
Internal::Locator::instance()->refresh({this});
}
@@ -435,13 +418,11 @@ FilePaths DirectoryFilter::directories() const
void DirectoryFilter::setFilters(const QStringList &filters)
{
- QMutexLocker locker(&m_lock);
m_filters = filters;
}
void DirectoryFilter::setExclusionFilters(const QStringList &exclusionFilters)
{
- QMutexLocker locker(&m_lock);
m_exclusionFilters = exclusionFilters;
}
diff --git a/src/plugins/coreplugin/locator/directoryfilter.h b/src/plugins/coreplugin/locator/directoryfilter.h
index 7fb88ce112d..06916165003 100644
--- a/src/plugins/coreplugin/locator/directoryfilter.h
+++ b/src/plugins/coreplugin/locator/directoryfilter.h
@@ -7,10 +7,7 @@
#include <coreplugin/core_global.h>
-#include <QString>
#include <QByteArray>
-#include <QFutureInterface>
-#include <QMutex>
namespace Core {
@@ -22,7 +19,6 @@ public:
DirectoryFilter(Utils::Id id);
void restoreState(const QByteArray &state) override;
bool openConfigDialog(QWidget *parent, bool &needsRefresh) override;
- void refresh(QFutureInterface<void> &future) override;
void setIsCustomFilter(bool value);
void setDirectories(const Utils::FilePaths &directories);
@@ -49,7 +45,6 @@ private:
// Our config dialog, uses in addDirectory and editDirectory
// to give their dialogs the right parent
class DirectoryFilterOptions *m_dialog = nullptr;
- mutable QMutex m_lock;
Utils::FilePaths m_files;
bool m_isCustomFilter = true;
};
diff --git a/src/plugins/coreplugin/locator/filesystemfilter.cpp b/src/plugins/coreplugin/locator/filesystemfilter.cpp
index 17c54c748dc..fdc127e5d92 100644
--- a/src/plugins/coreplugin/locator/filesystemfilter.cpp
+++ b/src/plugins/coreplugin/locator/filesystemfilter.cpp
@@ -3,7 +3,6 @@
#include "filesystemfilter.h"
-#include "basefilefilter.h"
#include "../coreplugintr.h"
#include "../documentmanager.h"
#include "../editormanager/editormanager.h"
@@ -61,7 +60,7 @@ FileSystemFilter::FileSystemFilter()
void FileSystemFilter::prepareSearch(const QString &entry)
{
Q_UNUSED(entry)
- m_currentDocumentDirectory = DocumentManager::fileDialogInitialDirectory().toString();
+ m_currentDocumentDirectory = DocumentManager::fileDialogInitialDirectory();
m_currentIncludeHidden = m_includeHidden;
}
@@ -72,18 +71,21 @@ QList<LocatorFilterEntry> FileSystemFilter::matchesFor(QFutureInterface<LocatorF
Environment env = Environment::systemEnvironment();
const QString expandedEntry = env.expandVariables(entry);
-
- const QFileInfo entryInfo(expandedEntry);
- const QString entryFileName = entryInfo.fileName();
- QString directory = entryInfo.path();
- if (entryInfo.isRelative()) {
- if (entryInfo.filePath().startsWith("~/"))
- directory.replace(0, 1, QDir::homePath());
- else if (!m_currentDocumentDirectory.isEmpty())
- directory.prepend(m_currentDocumentDirectory + "/");
- }
- const QDir dirInfo(directory);
- QDir::Filters dirFilter = QDir::Dirs|QDir::Drives|QDir::NoDot|QDir::NoDotDot;
+ const auto expandedEntryPath = FilePath::fromUserInput(expandedEntry);
+ const auto absoluteEntryPath = m_currentDocumentDirectory.isEmpty()
+ ? expandedEntryPath
+ : m_currentDocumentDirectory.resolvePath(expandedEntryPath);
+
+ // Consider the entered path a directory if it ends with slash/backslash.
+ // If it is a dir but doesn't end with a backslash, we want to still show all (other) matching
+ // items from the same parent directory.
+ // Unfortunately fromUserInput removes slash/backslash at the end, so manually check the original.
+ const bool isDir = expandedEntry.isEmpty() || expandedEntry.endsWith('/')
+ || expandedEntry.endsWith('\\');
+ const FilePath directory = isDir ? absoluteEntryPath : absoluteEntryPath.parentDir();
+ const QString entryFileName = isDir ? QString() : absoluteEntryPath.fileName();
+
+ QDir::Filters dirFilter = QDir::Dirs | QDir::Drives | QDir::NoDot | QDir::NoDotDot;
QDir::Filters fileFilter = QDir::Files;
if (m_currentIncludeHidden) {
dirFilter |= QDir::Hidden;
@@ -92,25 +94,26 @@ QList<LocatorFilterEntry> FileSystemFilter::matchesFor(QFutureInterface<LocatorF
// use only 'name' for case sensitivity decision, because we need to make the path
// match the case on the file system for case-sensitive file systems
const Qt::CaseSensitivity caseSensitivity_ = caseSensitivity(entryFileName);
- const QStringList dirs = QStringList("..")
- + dirInfo.entryList(dirFilter, QDir::Name|QDir::IgnoreCase|QDir::LocaleAware);
- const QStringList files = dirInfo.entryList(fileFilter,
- QDir::Name|QDir::IgnoreCase|QDir::LocaleAware);
+ const FilePaths dirs = FilePaths({directory / ".."})
+ + directory.dirEntries({{}, dirFilter},
+ QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
+ const FilePaths files = directory.dirEntries({{}, fileFilter},
+ QDir::Name | QDir::IgnoreCase | QDir::LocaleAware);
QRegularExpression regExp = createRegExp(entryFileName, caseSensitivity_);
if (!regExp.isValid())
return {};
- for (const QString &dir : dirs) {
+ for (const FilePath &dir : dirs) {
if (future.isCanceled())
break;
- const QRegularExpressionMatch match = regExp.match(dir);
+ const QString dirString = dir.relativeChildPath(directory).nativePath();
+ const QRegularExpressionMatch match = regExp.match(dirString);
if (match.hasMatch()) {
- const MatchLevel level = matchLevelFor(match, dir);
- const QString fullPath = dirInfo.filePath(dir);
- LocatorFilterEntry filterEntry(this, dir);
- filterEntry.filePath = FilePath::fromString(fullPath);
+ const MatchLevel level = matchLevelFor(match, dirString);
+ LocatorFilterEntry filterEntry(this, dirString);
+ filterEntry.filePath = dir;
filterEntry.highlightInfo = highlightInfo(match);
entries[int(level)].append(filterEntry);
@@ -121,16 +124,16 @@ QList<LocatorFilterEntry> FileSystemFilter::matchesFor(QFutureInterface<LocatorF
regExp = createRegExp(link.targetFilePath.toString(), caseSensitivity_);
if (!regExp.isValid())
return {};
- for (const QString &file : files) {
+ for (const FilePath &file : files) {
if (future.isCanceled())
break;
- const QRegularExpressionMatch match = regExp.match(file);
+ const QString fileString = file.relativeChildPath(directory).nativePath();
+ const QRegularExpressionMatch match = regExp.match(fileString);
if (match.hasMatch()) {
- const MatchLevel level = matchLevelFor(match, file);
- const QString fullPath = dirInfo.filePath(file);
- LocatorFilterEntry filterEntry(this, file);
- filterEntry.filePath = FilePath::fromString(fullPath);
+ const MatchLevel level = matchLevelFor(match, fileString);
+ LocatorFilterEntry filterEntry(this, fileString);
+ filterEntry.filePath = file;
filterEntry.highlightInfo = highlightInfo(match);
filterEntry.linkForEditor = Link(filterEntry.filePath, link.targetLine,
link.targetColumn);
@@ -139,12 +142,12 @@ QList<LocatorFilterEntry> FileSystemFilter::matchesFor(QFutureInterface<LocatorF
}
// "create and open" functionality
- const QString fullFilePath = dirInfo.filePath(entryFileName);
+ const FilePath fullFilePath = directory / entryFileName;
const bool containsWildcard = expandedEntry.contains('?') || expandedEntry.contains('*');
- if (!containsWildcard && !QFileInfo::exists(fullFilePath) && dirInfo.exists()) {
+ if (!containsWildcard && !fullFilePath.exists() && directory.exists()) {
LocatorFilterEntry createAndOpen(this, Tr::tr("Create and Open \"%1\"").arg(expandedEntry));
- createAndOpen.filePath = FilePath::fromString(fullFilePath);
- createAndOpen.extraInfo = FilePath::fromString(dirInfo.absolutePath()).shortNativePath();
+ createAndOpen.filePath = fullFilePath;
+ createAndOpen.extraInfo = directory.absoluteFilePath().shortNativePath();
entries[int(MatchLevel::Normal)].append(createAndOpen);
}
@@ -187,12 +190,12 @@ void FileSystemFilter::accept(const LocatorFilterEntry &selection,
if (messageBox.isChecked())
CheckableMessageBox::doNotAskAgain(ICore::settings(), kAlwaysCreate);
}
- QFile file(selection.filePath.toString());
+ QFile file(selection.filePath.toFSPathString());
file.open(QFile::WriteOnly);
file.close();
VcsManager::promptToAdd(selection.filePath.absolutePath(), {selection.filePath});
}
- BaseFileFilter::openEditorAt(selection);
+ EditorManager::openEditor(selection);
}, Qt::QueuedConnection);
}
}
diff --git a/src/plugins/coreplugin/locator/filesystemfilter.h b/src/plugins/coreplugin/locator/filesystemfilter.h
index 33338d626e2..fea41e5779e 100644
--- a/src/plugins/coreplugin/locator/filesystemfilter.h
+++ b/src/plugins/coreplugin/locator/filesystemfilter.h
@@ -37,7 +37,7 @@ private:
static const bool kIncludeHiddenDefault = true;
bool m_includeHidden = kIncludeHiddenDefault;
bool m_currentIncludeHidden = kIncludeHiddenDefault;
- QString m_currentDocumentDirectory;
+ Utils::FilePath m_currentDocumentDirectory;
};
} // namespace Internal
diff --git a/src/plugins/coreplugin/locator/ilocatorfilter.cpp b/src/plugins/coreplugin/locator/ilocatorfilter.cpp
index 4523c719fb6..f529aa0285a 100644
--- a/src/plugins/coreplugin/locator/ilocatorfilter.cpp
+++ b/src/plugins/coreplugin/locator/ilocatorfilter.cpp
@@ -4,8 +4,10 @@
#include "ilocatorfilter.h"
#include "../coreplugintr.h"
+#include "../editormanager/editormanager.h"
#include <utils/fuzzymatcher.h>
+#include <utils/tasktree.h>
#include <QBoxLayout>
#include <QCheckBox>
@@ -98,6 +100,42 @@ void ILocatorFilter::prepareSearch(const QString &entry)
Q_UNUSED(entry)
}
+
+/*!
+ Sets the refresh recipe for refreshing cached data.
+*/
+void ILocatorFilter::setRefreshRecipe(const std::optional<Utils::Tasking::TaskItem> &recipe)
+{
+ m_refreshRecipe = recipe;
+}
+
+/*!
+ Returns the refresh recipe for refreshing cached data. By default, the locator filter has
+ no recipe set, so that it won't be refreshed.
+*/
+std::optional<Utils::Tasking::TaskItem> ILocatorFilter::refreshRecipe() const
+{
+ return m_refreshRecipe;
+}
+
+/*!
+ Called with the entry specified by \a selection when the user activates it
+ in the result list.
+ Implementations can return a new search term \a newText, which has \a selectionLength characters
+ starting from \a selectionStart preselected, and the cursor set to the end of the selection.
+
+ The default implementation tries to open an editor for \a selections's linkForEditor,
+ if it exists.
+*/
+void ILocatorFilter::accept(const LocatorFilterEntry &selection, QString *newText,
+ int *selectionStart, int *selectionLength) const
+{
+ Q_UNUSED(newText)
+ Q_UNUSED(selectionStart)
+ Q_UNUSED(selectionLength)
+ EditorManager::openEditor(selection);
+}
+
/*!
Sets the default \a shortcut string that can be used to explicitly choose
this filter in the locator input field. Call for example from the
@@ -201,13 +239,13 @@ void ILocatorFilter::restoreState(const QByteArray &state)
various aspects of the filter. Called when the user requests to configure
the filter.
- Set \a needsRefresh to \c true, if a refresh() should be done after
+ Set \a needsRefresh to \c true, if a refresh should be done after
closing the dialog. Return \c false if the user canceled the dialog.
The default implementation allows changing the shortcut and whether the
filter is included by default.
- \sa refresh()
+ \sa refreshRecipe()
*/
bool ILocatorFilter::openConfigDialog(QWidget *parent, bool &needsRefresh)
{
@@ -598,23 +636,6 @@ bool ILocatorFilter::isOldSetting(const QByteArray &state)
*/
/*!
- \fn void Core::ILocatorFilter::accept(Core::const LocatorFilterEntry &selection, QString *newText, int *selectionStart, int *selectionLength) const
-
- Called with the entry specified by \a selection when the user activates it
- in the result list.
- Implementations can return a new search term \a newText, which has \a selectionLength characters
- starting from \a selectionStart preselected, and the cursor set to the end of the selection.
-*/
-
-/*!
- \fn void Core::ILocatorFilter::refresh(QFutureInterface<void> &future)
-
- Refreshes cached data asynchronously.
-
- If \a future is \c canceled, the refresh should be aborted.
-*/
-
-/*!
\enum Core::ILocatorFilter::Priority
This enum value holds the priority that is used for ordering the results
diff --git a/src/plugins/coreplugin/locator/ilocatorfilter.h b/src/plugins/coreplugin/locator/ilocatorfilter.h
index e6b49ab5e9c..a8e30029d6f 100644
--- a/src/plugins/coreplugin/locator/ilocatorfilter.h
+++ b/src/plugins/coreplugin/locator/ilocatorfilter.h
@@ -8,6 +8,7 @@
#include <utils/filepath.h>
#include <utils/id.h>
#include <utils/link.h>
+#include <utils/tasktree.h>
#include <QFutureInterface>
#include <QIcon>
@@ -17,12 +18,17 @@
#include <optional>
+namespace Utils::Tasking { class TaskItem; }
+
namespace Core {
+namespace Internal { class Locator; }
+
class ILocatorFilter;
-struct LocatorFilterEntry
+class LocatorFilterEntry
{
+public:
struct HighlightInfo {
enum DataType {
DisplayName,
@@ -153,10 +159,8 @@ public:
virtual QList<LocatorFilterEntry> matchesFor(QFutureInterface<LocatorFilterEntry> &future, const QString &entry) = 0;
- virtual void accept(const LocatorFilterEntry &selection,
- QString *newText, int *selectionStart, int *selectionLength) const = 0;
-
- virtual void refresh(QFutureInterface<void> &future) { Q_UNUSED(future) };
+ virtual void accept(const LocatorFilterEntry &selection, QString *newText,
+ int *selectionStart, int *selectionLength) const;
virtual QByteArray saveState() const;
virtual void restoreState(const QByteArray &state);
@@ -198,9 +202,14 @@ protected:
virtual void saveState(QJsonObject &object) const;
virtual void restoreState(const QJsonObject &object);
+ void setRefreshRecipe(const std::optional<Utils::Tasking::TaskItem> &recipe);
+ std::optional<Utils::Tasking::TaskItem> refreshRecipe() const;
+
static bool isOldSetting(const QByteArray &state);
private:
+ friend class Internal::Locator;
+
Utils::Id m_id;
QString m_shortcut;
Priority m_priority = Medium;
@@ -208,6 +217,7 @@ private:
QString m_description;
QString m_defaultShortcut;
std::optional<QString> m_defaultSearchText;
+ std::optional<Utils::Tasking::TaskItem> m_refreshRecipe;
QKeySequence m_defaultKeySequence;
bool m_defaultIncludedByDefault = false;
bool m_includedByDefault = m_defaultIncludedByDefault;
diff --git a/src/plugins/coreplugin/locator/locator.cpp b/src/plugins/coreplugin/locator/locator.cpp
index afc060385bc..d43b77e7508 100644
--- a/src/plugins/coreplugin/locator/locator.cpp
+++ b/src/plugins/coreplugin/locator/locator.cpp
@@ -382,14 +382,16 @@ void Locator::refresh(const QList<ILocatorFilter *> &filters)
using namespace Tasking;
QList<TaskItem> tasks{parallel};
for (ILocatorFilter *filter : std::as_const(m_refreshingFilters)) {
- const auto setupRefresh = [filter](AsyncTask<void> &async) {
- async.setAsyncCallData(&ILocatorFilter::refresh, filter);
- };
- const auto onRefreshDone = [this, filter](const AsyncTask<void> &async) {
- Q_UNUSED(async)
- m_refreshingFilters.removeOne(filter);
+ const auto task = filter->refreshRecipe();
+ if (!task.has_value())
+ continue;
+
+ const Group group {
+ optional,
+ *task,
+ OnGroupDone([this, filter] { m_refreshingFilters.removeOne(filter); })
};
- tasks.append(Async<void>(setupRefresh, onRefreshDone));
+ tasks.append(group);
}
m_taskTree.reset(new TaskTree{tasks});
diff --git a/src/plugins/coreplugin/locator/locator_test.cpp b/src/plugins/coreplugin/locator/locator_test.cpp
index eca70f2eaab..a261d15586b 100644
--- a/src/plugins/coreplugin/locator/locator_test.cpp
+++ b/src/plugins/coreplugin/locator/locator_test.cpp
@@ -28,8 +28,6 @@ public:
{
setFileIterator(new BaseFileFilter::ListIterator(theFiles));
}
-
- void refresh(QFutureInterface<void> &) override {}
};
class ReferenceData
diff --git a/src/plugins/coreplugin/locator/locatorsearchutils.cpp b/src/plugins/coreplugin/locator/locatorsearchutils.cpp
index 77e460ad4f3..a6a90107fe3 100644
--- a/src/plugins/coreplugin/locator/locatorsearchutils.cpp
+++ b/src/plugins/coreplugin/locator/locatorsearchutils.cpp
@@ -10,7 +10,7 @@
void Core::Internal::runSearch(QFutureInterface<Core::LocatorFilterEntry> &future,
const QList<ILocatorFilter *> &filters, const QString &searchText)
{
- std::unordered_set<Utils::FilePath> addedCache;
+ std::unordered_set<Utils::Link> addedCache;
const bool checkDuplicates = (filters.size() > 1);
const auto duplicatesRemoved = [&](const QList<LocatorFilterEntry> &entries) {
if (!checkDuplicates)
@@ -19,7 +19,7 @@ void Core::Internal::runSearch(QFutureInterface<Core::LocatorFilterEntry> &futur
results.reserve(entries.size());
for (const LocatorFilterEntry &entry : entries) {
const auto &link = entry.linkForEditor;
- if (!link || addedCache.emplace(link->targetFilePath).second)
+ if (!link || addedCache.emplace(*link).second)
results.append(entry);
}
return results;
diff --git a/src/plugins/coreplugin/locator/opendocumentsfilter.cpp b/src/plugins/coreplugin/locator/opendocumentsfilter.cpp
index f8febc69f30..2ec6aca0531 100644
--- a/src/plugins/coreplugin/locator/opendocumentsfilter.cpp
+++ b/src/plugins/coreplugin/locator/opendocumentsfilter.cpp
@@ -3,12 +3,11 @@
#include "opendocumentsfilter.h"
-#include "basefilefilter.h"
#include "../coreplugintr.h"
#include <utils/filepath.h>
#include <utils/link.h>
-#include <utils/linecolumn.h>
+#include <utils/tasktree.h>
#include <QAbstractItemModel>
#include <QMutexLocker>
@@ -25,6 +24,7 @@ OpenDocumentsFilter::OpenDocumentsFilter()
setDefaultShortcutString("o");
setPriority(High);
setDefaultIncludedByDefault(true);
+ setRefreshRecipe(Tasking::Sync([this] { refreshInternally(); return true; }));
connect(DocumentModel::model(), &QAbstractItemModel::dataChanged,
this, &OpenDocumentsFilter::slotDataChanged);
@@ -117,13 +117,15 @@ QList<OpenDocumentsFilter::Entry> OpenDocumentsFilter::editors() const
return m_editors;
}
-void OpenDocumentsFilter::accept(const LocatorFilterEntry &selection,
- QString *newText, int *selectionStart, int *selectionLength) const
+void OpenDocumentsFilter::refreshInternally()
{
- Q_UNUSED(newText)
- Q_UNUSED(selectionStart)
- Q_UNUSED(selectionLength)
- BaseFileFilter::openEditorAt(selection);
+ QMutexLocker lock(&m_mutex);
+ m_editors.clear();
+ const QList<DocumentModel::Entry *> documentEntries = DocumentModel::entries();
+ // create copy with only the information relevant to use
+ // to avoid model deleting entries behind our back
+ for (DocumentModel::Entry *e : documentEntries)
+ m_editors.append({e->filePath(), e->displayName()});
}
} // Core::Internal
diff --git a/src/plugins/coreplugin/locator/opendocumentsfilter.h b/src/plugins/coreplugin/locator/opendocumentsfilter.h
index 537bdd45a5a..7bbd98698fa 100644
--- a/src/plugins/coreplugin/locator/opendocumentsfilter.h
+++ b/src/plugins/coreplugin/locator/opendocumentsfilter.h
@@ -7,10 +7,7 @@
#include <coreplugin/editormanager/documentmodel.h>
-#include <QFutureInterface>
-#include <QList>
#include <QMutex>
-#include <QString>
namespace Core {
namespace Internal {
@@ -23,9 +20,6 @@ public:
OpenDocumentsFilter();
QList<LocatorFilterEntry> matchesFor(QFutureInterface<LocatorFilterEntry> &future,
const QString &entry) override;
- void accept(const LocatorFilterEntry &selection,
- QString *newText, int *selectionStart, int *selectionLength) const override;
-
public slots:
void slotDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight,
const QVector<int> &roles);
@@ -41,6 +35,7 @@ private:
};
QList<Entry> editors() const;
+ void refreshInternally();
mutable QMutex m_mutex;
QList<Entry> m_editors;
diff --git a/src/plugins/coreplugin/manhattanstyle.cpp b/src/plugins/coreplugin/manhattanstyle.cpp
index 3ec83405535..7cf4746f951 100644
--- a/src/plugins/coreplugin/manhattanstyle.cpp
+++ b/src/plugins/coreplugin/manhattanstyle.cpp
@@ -189,6 +189,12 @@ int ManhattanStyle::pixelMetric(PixelMetric metric, const QStyleOption *option,
int retval = 0;
retval = QProxyStyle::pixelMetric(metric, option, widget);
switch (metric) {
+#ifdef Q_OS_MACOS
+ case PM_MenuButtonIndicator:
+ if (widget && option->type == QStyleOption::SO_ToolButton)
+ return 12;
+ break;
+#endif
case PM_SplitterWidth:
if (widget && widget->property("minisplitter").toBool())
retval = 1;
diff --git a/src/plugins/coreplugin/plugindialog.cpp b/src/plugins/coreplugin/plugindialog.cpp
index 1298238722c..f6ea25e719c 100644
--- a/src/plugins/coreplugin/plugindialog.cpp
+++ b/src/plugins/coreplugin/plugindialog.cpp
@@ -40,6 +40,7 @@ PluginDialog::PluginDialog(QWidget *parent)
auto filterLayout = new QHBoxLayout;
vl->addLayout(filterLayout);
auto filterEdit = new Utils::FancyLineEdit(this);
+ filterEdit->setFocus();
filterEdit->setFiltering(true);
connect(filterEdit, &Utils::FancyLineEdit::filterChanged,
m_view, &ExtensionSystem::PluginView::setFilter);
diff --git a/src/plugins/coreplugin/plugininstallwizard.cpp b/src/plugins/coreplugin/plugininstallwizard.cpp
index 6547c78835f..3587d211068 100644
--- a/src/plugins/coreplugin/plugininstallwizard.cpp
+++ b/src/plugins/coreplugin/plugininstallwizard.cpp
@@ -11,6 +11,7 @@
#include <extensionsystem/pluginspec.h>
#include <utils/archive.h>
+#include <utils/asynctask.h>
#include <utils/fileutils.h>
#include <utils/hostosinfo.h>
#include <utils/infolabel.h>
@@ -221,8 +222,8 @@ public:
m_label->setText(Tr::tr("There was an error while unarchiving."));
}
} else { // unarchiving was successful, run a check
- m_archiveCheck = Utils::runAsync(
- [this](QFutureInterface<ArchiveIssue> &fi) { return checkContents(fi); });
+ m_archiveCheck = Utils::asyncRun([this](QPromise<ArchiveIssue> &promise)
+ { return checkContents(promise); });
Utils::onFinished(m_archiveCheck, this, [this](const QFuture<ArchiveIssue> &f) {
m_cancelButton->setVisible(false);
m_cancelButton->disconnect();
@@ -248,7 +249,7 @@ public:
}
// Async. Result is set if any issue was found.
- void checkContents(QFutureInterface<ArchiveIssue> &fi)
+ void checkContents(QPromise<ArchiveIssue> &promise)
{
QTC_ASSERT(m_tempDir.get(), return );
@@ -260,7 +261,7 @@ public:
QDir::Files | QDir::NoSymLinks,
QDirIterator::Subdirectories);
while (it.hasNext()) {
- if (fi.isCanceled())
+ if (promise.isCanceled())
return;
it.next();
PluginSpec *spec = PluginSpec::read(it.filePath());
@@ -275,18 +276,18 @@ public:
});
if (found != dependencies.constEnd()) {
if (!coreplugin->provides(found->name, found->version)) {
- fi.reportResult({Tr::tr("Plugin requires an incompatible version of %1 (%2).")
- .arg(Constants::IDE_DISPLAY_NAME)
- .arg(found->version),
- InfoLabel::Error});
+ promise.addResult(ArchiveIssue{
+ Tr::tr("Plugin requires an incompatible version of %1 (%2).")
+ .arg(Constants::IDE_DISPLAY_NAME).arg(found->version),
+ InfoLabel::Error});
return;
}
}
return; // successful / no error
}
}
- fi.reportResult({Tr::tr("Did not find %1 plugin.").arg(Constants::IDE_DISPLAY_NAME),
- InfoLabel::Error});
+ promise.addResult(ArchiveIssue{Tr::tr("Did not find %1 plugin.")
+ .arg(Constants::IDE_DISPLAY_NAME), InfoLabel::Error});
}
void cleanupPage() final
diff --git a/src/plugins/coreplugin/progressmanager/progressmanager.cpp b/src/plugins/coreplugin/progressmanager/progressmanager.cpp
index efc97312217..18cc2f6f34f 100644
--- a/src/plugins/coreplugin/progressmanager/progressmanager.cpp
+++ b/src/plugins/coreplugin/progressmanager/progressmanager.cpp
@@ -740,6 +740,33 @@ FutureProgress *ProgressManager::addTimedTask(const QFutureInterface<void> &futu
return fp;
}
+FutureProgress *ProgressManager::addTimedTask(const QFuture<void> &future, const QString &title,
+ Id type, int expectedSeconds, ProgressFlags flags)
+{
+ QFutureInterface<void> dummyFutureInterface;
+ QFuture<void> dummyFuture = dummyFutureInterface.future();
+ FutureProgress *fp = m_instance->doAddTask(dummyFuture, title, type, flags);
+ (void) new ProgressTimer(dummyFutureInterface, expectedSeconds, fp);
+
+ QFutureWatcher<void> *dummyWatcher = new QFutureWatcher<void>(fp);
+ connect(dummyWatcher, &QFutureWatcher<void>::canceled, dummyWatcher, [future] {
+ QFuture<void> mutableFuture = future;
+ mutableFuture.cancel();
+ });
+ dummyWatcher->setFuture(dummyFuture);
+
+ QFutureWatcher<void> *origWatcher = new QFutureWatcher<void>(fp);
+ connect(origWatcher, &QFutureWatcher<void>::finished, origWatcher, [future, dummyFutureInterface] {
+ QFutureInterface<void> mutableDummyFutureInterface = dummyFutureInterface;
+ if (future.isCanceled())
+ mutableDummyFutureInterface.reportCanceled();
+ mutableDummyFutureInterface.reportFinished();
+ });
+ origWatcher->setFuture(future);
+
+ return fp;
+}
+
/*!
Shows the given \a text in a platform dependent way in the application
icon in the system's task bar or dock. This is used to show the number
diff --git a/src/plugins/coreplugin/progressmanager/progressmanager.h b/src/plugins/coreplugin/progressmanager/progressmanager.h
index 78c1b771aea..8c51bf4ccd4 100644
--- a/src/plugins/coreplugin/progressmanager/progressmanager.h
+++ b/src/plugins/coreplugin/progressmanager/progressmanager.h
@@ -40,6 +40,8 @@ public:
Utils::Id type, ProgressFlags flags = {});
static FutureProgress *addTimedTask(const QFutureInterface<void> &fi, const QString &title,
Utils::Id type, int expectedSeconds, ProgressFlags flags = {});
+ static FutureProgress *addTimedTask(const QFuture<void> &future, const QString &title,
+ Utils::Id type, int expectedSeconds, ProgressFlags flags = {});
static void setApplicationLabel(const QString &text);
public slots:
diff --git a/src/plugins/coreplugin/progressmanager/progressview.cpp b/src/plugins/coreplugin/progressmanager/progressview.cpp
index 498ec8ae7b0..45b10286dc4 100644
--- a/src/plugins/coreplugin/progressmanager/progressview.cpp
+++ b/src/plugins/coreplugin/progressmanager/progressview.cpp
@@ -5,11 +5,19 @@
#include "../coreplugintr.h"
+#include <utils/icon.h>
+#include <utils/overlaywidget.h>
+
#include <QApplication>
#include <QEvent>
#include <QMouseEvent>
+#include <QPainter>
#include <QVBoxLayout>
+using namespace Utils;
+
+const int PIN_SIZE = 12;
+
namespace Core::Internal {
ProgressView::ProgressView(QWidget *parent)
@@ -21,15 +29,29 @@ ProgressView::ProgressView(QWidget *parent)
m_layout->setSpacing(0);
m_layout->setSizeConstraint(QLayout::SetFixedSize);
setWindowTitle(Tr::tr("Processes"));
+
+ auto pinButton = new OverlayWidget(this);
+ pinButton->attachToWidget(this);
+ pinButton->setAttribute(Qt::WA_TransparentForMouseEvents, false); // override OverlayWidget
+ pinButton->setPaintFunction([](QWidget *that, QPainter &p, QPaintEvent *) {
+ static const QIcon icon = Icon({{":/utils/images/pinned_small.png", Theme::IconsBaseColor}},
+ Icon::Tint)
+ .icon();
+ QRect iconRect(0, 0, PIN_SIZE, PIN_SIZE);
+ iconRect.moveTopRight(that->rect().topRight());
+ icon.paint(&p, iconRect);
+ });
+ pinButton->setVisible(false);
+ pinButton->installEventFilter(this);
+ m_pinButton = pinButton;
}
ProgressView::~ProgressView() = default;
void ProgressView::addProgressWidget(QWidget *widget)
{
- if (m_layout->count() == 0)
- m_anchorBottomRight = {}; // reset temporarily user-moved progress details
m_layout->insertWidget(0, widget);
+ m_pinButton->raise();
}
void ProgressView::removeProgressWidget(QWidget *widget)
@@ -63,9 +85,12 @@ bool ProgressView::event(QEvent *event)
reposition();
} else if (event->type() == QEvent::Enter) {
m_hovered = true;
+ if (m_anchorBottomRight != QPoint())
+ m_pinButton->setVisible(true);
emit hoveredChanged(m_hovered);
} else if (event->type() == QEvent::Leave) {
m_hovered = false;
+ m_pinButton->setVisible(false);
emit hoveredChanged(m_hovered);
} else if (event->type() == QEvent::Show) {
m_anchorBottomRight = {}; // reset temporarily user-moved progress details
@@ -78,6 +103,16 @@ bool ProgressView::eventFilter(QObject *obj, QEvent *event)
{
if ((obj == parentWidget() || obj == m_referenceWidget) && event->type() == QEvent::Resize)
reposition();
+ if (obj == m_pinButton && event->type() == QEvent::MouseButtonRelease) {
+ auto me = static_cast<QMouseEvent *>(event);
+ if (me->button() == Qt::LeftButton
+ && QRectF(m_pinButton->width() - PIN_SIZE, 0, PIN_SIZE, PIN_SIZE)
+ .contains(me->position())) {
+ me->accept();
+ m_anchorBottomRight = {};
+ reposition();
+ }
+ }
return false;
}
@@ -133,6 +168,9 @@ void ProgressView::reposition()
{
if (!parentWidget() || !m_referenceWidget)
return;
+
+ m_pinButton->setVisible(m_anchorBottomRight != QPoint() && m_hovered);
+
move(boundedInParent(this, topRightReferenceInParent() + m_anchorBottomRight, parentWidget())
- rect().bottomRight());
}
diff --git a/src/plugins/coreplugin/progressmanager/progressview.h b/src/plugins/coreplugin/progressmanager/progressview.h
index 37cfa248265..e0b42c8547e 100644
--- a/src/plugins/coreplugin/progressmanager/progressview.h
+++ b/src/plugins/coreplugin/progressmanager/progressview.h
@@ -44,6 +44,7 @@ private:
QVBoxLayout *m_layout;
QWidget *m_referenceWidget = nullptr;
+ QWidget *m_pinButton = nullptr;
// dragging
std::optional<QPointF> m_clickPosition;
diff --git a/src/plugins/coreplugin/systemsettings.cpp b/src/plugins/coreplugin/systemsettings.cpp
index f34e7fb5b70..a65f7fee8b1 100644
--- a/src/plugins/coreplugin/systemsettings.cpp
+++ b/src/plugins/coreplugin/systemsettings.cpp
@@ -438,9 +438,9 @@ void SystemSettingsWidget::resetFileBrowser()
void SystemSettingsWidget::updatePath()
{
- EnvironmentChange change;
- change.addAppendToPath(VcsManager::additionalToolsPath());
- m_patchChooser->setEnvironmentChange(change);
+ Environment env;
+ env.appendToPath(VcsManager::additionalToolsPath());
+ m_patchChooser->setEnvironment(env);
}
void SystemSettingsWidget::updateEnvironmentChangesLabel()
diff --git a/src/plugins/coreplugin/welcomepagehelper.cpp b/src/plugins/coreplugin/welcomepagehelper.cpp
index 0c46ea98f30..0e8f1211bf2 100644
--- a/src/plugins/coreplugin/welcomepagehelper.cpp
+++ b/src/plugins/coreplugin/welcomepagehelper.cpp
@@ -633,13 +633,9 @@ void ListItemDelegate::goon()
SectionedGridView::SectionedGridView(QWidget *parent)
: QStackedWidget(parent)
- , m_allItemsView(new Core::GridView(this))
{
- auto allItemsModel = new ListModel(this);
- allItemsModel->setPixmapFunction(m_pixmapFunction);
- // it just "borrows" the items from the section models:
- allItemsModel->setOwnsItems(false);
- m_filteredAllItemsModel = new Core::ListModelFilter(allItemsModel, this);
+ m_allItemsModel.reset(new ListModel);
+ m_allItemsModel->setPixmapFunction(m_pixmapFunction);
auto area = new QScrollArea(this);
area->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
@@ -655,16 +651,18 @@ SectionedGridView::SectionedGridView(QWidget *parent)
area->setWidget(sectionedView);
addWidget(area);
-
- m_allItemsView->setModel(m_filteredAllItemsModel);
- addWidget(m_allItemsView);
}
-SectionedGridView::~SectionedGridView() = default;
+SectionedGridView::~SectionedGridView()
+{
+ clear();
+}
void SectionedGridView::setItemDelegate(QAbstractItemDelegate *delegate)
{
- m_allItemsView->setItemDelegate(delegate);
+ m_itemDelegate = delegate;
+ if (m_allItemsView)
+ m_allItemsView->setItemDelegate(delegate);
for (GridView *view : std::as_const(m_gridViews))
view->setItemDelegate(delegate);
}
@@ -672,28 +670,44 @@ void SectionedGridView::setItemDelegate(QAbstractItemDelegate *delegate)
void SectionedGridView::setPixmapFunction(const Core::ListModel::PixmapFunction &pixmapFunction)
{
m_pixmapFunction = pixmapFunction;
- auto allProducts = static_cast<ListModel *>(m_filteredAllItemsModel->sourceModel());
- allProducts->setPixmapFunction(pixmapFunction);
+ m_allItemsModel->setPixmapFunction(pixmapFunction);
for (ListModel *model : std::as_const(m_sectionModels))
model->setPixmapFunction(pixmapFunction);
}
void SectionedGridView::setSearchString(const QString &searchString)
{
- int view = searchString.isEmpty() ? 0 // sectioned view
- : 1; // search view
- setCurrentIndex(view);
- m_filteredAllItemsModel->setSearchString(searchString);
+ if (searchString.isEmpty()) {
+ // back to sectioned view
+ setCurrentIndex(0);
+ return;
+ }
+ if (!m_allItemsView) {
+ // We don't have a grid set for searching yet.
+ // Create all items view for filtering.
+ m_allItemsView.reset(new GridView);
+ m_allItemsView->setModel(new ListModelFilter(m_allItemsModel.get(), m_allItemsView.get()));
+ if (m_itemDelegate)
+ m_allItemsView->setItemDelegate(m_itemDelegate);
+ addWidget(m_allItemsView.get());
+ }
+ setCurrentWidget(m_allItemsView.get());
+ auto filterModel = static_cast<ListModelFilter *>(m_allItemsView.get()->model());
+ filterModel->setSearchString(searchString);
}
ListModel *SectionedGridView::addSection(const Section &section, const QList<ListItem *> &items)
{
auto model = new ListModel(this);
model->setPixmapFunction(m_pixmapFunction);
+ // the sections only keep a weak reference to the items,
+ // they are owned by the allProducts model, since multiple sections can contain duplicates
+ // of the same item
+ model->setOwnsItems(false);
model->appendItems(items);
auto gridView = new SectionGridView(this);
- gridView->setItemDelegate(m_allItemsView->itemDelegate());
+ gridView->setItemDelegate(m_itemDelegate);
gridView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
gridView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
gridView->setModel(model);
@@ -715,8 +729,11 @@ ListModel *SectionedGridView::addSection(const Section &section, const QList<Lis
vbox->insertWidget(position + 1, gridView);
// add the items also to the all products model to be able to search correctly
- auto allProducts = static_cast<ListModel *>(m_filteredAllItemsModel->sourceModel());
- allProducts->appendItems(items);
+ const QSet<ListItem *> allItems = toSet(m_allItemsModel->items());
+ const QList<ListItem *> newItems = filtered(items, [&allItems](ListItem *item) {
+ return !allItems.contains(item);
+ });
+ m_allItemsModel->appendItems(newItems);
// only show section label(s) if there is more than one section
m_sectionLabels.at(0)->setVisible(m_sectionLabels.size() > 1);
@@ -726,14 +743,14 @@ ListModel *SectionedGridView::addSection(const Section &section, const QList<Lis
void SectionedGridView::clear()
{
- auto allProducts = static_cast<ListModel *>(m_filteredAllItemsModel->sourceModel());
- allProducts->clear();
+ m_allItemsModel->clear();
qDeleteAll(m_sectionModels);
qDeleteAll(m_sectionLabels);
qDeleteAll(m_gridViews);
m_sectionModels.clear();
m_sectionLabels.clear();
m_gridViews.clear();
+ m_allItemsView.reset();
}
} // namespace Core
diff --git a/src/plugins/coreplugin/welcomepagehelper.h b/src/plugins/coreplugin/welcomepagehelper.h
index 77760000e9e..cf9f438aced 100644
--- a/src/plugins/coreplugin/welcomepagehelper.h
+++ b/src/plugins/coreplugin/welcomepagehelper.h
@@ -40,7 +40,7 @@ public:
class CORE_EXPORT GridView : public QListView
{
public:
- explicit GridView(QWidget *parent);
+ explicit GridView(QWidget *parent = nullptr);
protected:
void leaveEvent(QEvent *) final;
@@ -74,7 +74,7 @@ public:
using PixmapFunction = std::function<QPixmap(QString)>;
- explicit ListModel(QObject *parent);
+ explicit ListModel(QObject *parent = nullptr);
~ListModel() override;
void appendItems(const QList<ListItem *> &items);
@@ -199,9 +199,10 @@ private:
QMap<Section, Core::ListModel *> m_sectionModels;
QList<QWidget *> m_sectionLabels;
QMap<Section, Core::GridView *> m_gridViews;
- Core::GridView *m_allItemsView = nullptr;
- Core::ListModelFilter *m_filteredAllItemsModel = nullptr;
+ std::unique_ptr<Core::ListModel> m_allItemsModel;
+ std::unique_ptr<Core::GridView> m_allItemsView;
Core::ListModel::PixmapFunction m_pixmapFunction;
+ QAbstractItemDelegate *m_itemDelegate = nullptr;
};
} // namespace Core
diff --git a/src/plugins/cppcheck/cppcheckplugin.cpp b/src/plugins/cppcheck/cppcheckplugin.cpp
index 5dd09a1820b..8d44f0dda56 100644
--- a/src/plugins/cppcheck/cppcheckplugin.cpp
+++ b/src/plugins/cppcheck/cppcheckplugin.cpp
@@ -16,7 +16,7 @@
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectexplorerconstants.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <coreplugin/actionmanager/actioncontainer.h>
@@ -97,8 +97,9 @@ CppcheckPluginPrivate::CppcheckPluginPrivate()
}
}
-void CppcheckPluginPrivate::startManualRun() {
- auto project = ProjectExplorer::SessionManager::startupProject();
+void CppcheckPluginPrivate::startManualRun()
+{
+ auto project = ProjectExplorer::ProjectManager::startupProject();
if (!project)
return;
@@ -121,8 +122,8 @@ void CppcheckPluginPrivate::startManualRun() {
void CppcheckPluginPrivate::updateManualRunAction()
{
using namespace ProjectExplorer;
- const Project *project = SessionManager::startupProject();
- const Target *target = SessionManager::startupTarget();
+ const Project *project = ProjectManager::startupProject();
+ const Target *target = ProjectManager::startupTarget();
const Utils::Id cxx = ProjectExplorer::Constants::CXX_LANGUAGE_ID;
const bool canRun = target && project->projectLanguages().contains(cxx)
&& ToolChainKitAspect::cxxToolChain(target->kit());
diff --git a/src/plugins/cppcheck/cppchecktrigger.cpp b/src/plugins/cppcheck/cppchecktrigger.cpp
index 55b1cf7ac01..8be098df558 100644
--- a/src/plugins/cppcheck/cppchecktrigger.cpp
+++ b/src/plugins/cppcheck/cppchecktrigger.cpp
@@ -13,7 +13,7 @@
#include <coreplugin/editormanager/ieditor.h>
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
using namespace Core;
using namespace ProjectExplorer;
@@ -34,7 +34,7 @@ CppcheckTrigger::CppcheckTrigger(CppcheckTextMarkManager &marks, CppcheckTool &t
connect(EditorManager::instance(), &EditorManager::aboutToSave,
this, &CppcheckTrigger::checkChangedDocument);
- connect(SessionManager::instance(), &SessionManager::startupProjectChanged,
+ connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged,
this, &CppcheckTrigger::changeCurrentProject);
connect(CppModelManager::instance(), &CppModelManager::projectPartsUpdated,
diff --git a/src/plugins/cppeditor/baseeditordocumentparser.cpp b/src/plugins/cppeditor/baseeditordocumentparser.cpp
index 8a8d1bc9afd..cbfa6f96859 100644
--- a/src/plugins/cppeditor/baseeditordocumentparser.cpp
+++ b/src/plugins/cppeditor/baseeditordocumentparser.cpp
@@ -8,6 +8,8 @@
#include "cppprojectpartchooser.h"
#include "editordocumenthandle.h"
+#include <QPromise>
+
using namespace Utils;
namespace CppEditor {
@@ -59,15 +61,16 @@ void BaseEditorDocumentParser::setConfiguration(const Configuration &configurati
void BaseEditorDocumentParser::update(const UpdateParams &updateParams)
{
- QFutureInterface<void> dummy;
+ QPromise<void> dummy;
+ dummy.start();
update(dummy, updateParams);
}
-void BaseEditorDocumentParser::update(const QFutureInterface<void> &future,
+void BaseEditorDocumentParser::update(const QPromise<void> &promise,
const UpdateParams &updateParams)
{
QMutexLocker locker(&m_updateIsRunning);
- updateImpl(future, updateParams);
+ updateImpl(promise, updateParams);
}
BaseEditorDocumentParser::State BaseEditorDocumentParser::state() const
diff --git a/src/plugins/cppeditor/baseeditordocumentparser.h b/src/plugins/cppeditor/baseeditordocumentparser.h
index fb7f79d1016..45f6b953990 100644
--- a/src/plugins/cppeditor/baseeditordocumentparser.h
+++ b/src/plugins/cppeditor/baseeditordocumentparser.h
@@ -6,14 +6,17 @@
#include "cppeditor_global.h"
#include "cpptoolsreuse.h"
#include "cppworkingcopy.h"
-#include "projectpart.h"
#include <projectexplorer/project.h>
-#include <QFutureInterface>
#include <QObject>
#include <QMutex>
+QT_BEGIN_NAMESPACE
+template <typename T>
+class QPromise;
+QT_END_NAMESPACE
+
namespace ProjectExplorer { class Project; }
namespace CppEditor {
@@ -66,7 +69,7 @@ public:
void setConfiguration(const Configuration &configuration);
void update(const UpdateParams &updateParams);
- void update(const QFutureInterface<void> &future, const UpdateParams &updateParams);
+ void update(const QPromise<void> &promise, const UpdateParams &updateParams);
ProjectPartInfo projectPartInfo() const;
@@ -91,7 +94,7 @@ protected:
mutable QMutex m_stateAndConfigurationMutex;
private:
- virtual void updateImpl(const QFutureInterface<void> &future,
+ virtual void updateImpl(const QPromise<void> &promise,
const UpdateParams &updateParams) = 0;
const Utils::FilePath m_filePath;
diff --git a/src/plugins/cppeditor/baseeditordocumentprocessor.cpp b/src/plugins/cppeditor/baseeditordocumentprocessor.cpp
index ca344aa6c7c..c6c51675eb9 100644
--- a/src/plugins/cppeditor/baseeditordocumentprocessor.cpp
+++ b/src/plugins/cppeditor/baseeditordocumentprocessor.cpp
@@ -8,9 +8,12 @@
#include "cpptoolsreuse.h"
#include "editordocumenthandle.h"
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
+
#include <texteditor/quickfix.h>
+#include <QPromise>
+
namespace CppEditor {
/*!
@@ -37,7 +40,7 @@ void BaseEditorDocumentProcessor::run(bool projectsUpdated)
: Utils::Language::Cxx;
runImpl({CppModelManager::instance()->workingCopy(),
- ProjectExplorer::SessionManager::startupProject(),
+ ProjectExplorer::ProjectManager::startupProject(),
languagePreference,
projectsUpdated});
}
@@ -58,20 +61,20 @@ void BaseEditorDocumentProcessor::setParserConfig(
parser()->setConfiguration(config);
}
-void BaseEditorDocumentProcessor::runParser(QFutureInterface<void> &future,
+void BaseEditorDocumentProcessor::runParser(QPromise<void> &promise,
BaseEditorDocumentParser::Ptr parser,
BaseEditorDocumentParser::UpdateParams updateParams)
{
- future.setProgressRange(0, 1);
- if (future.isCanceled()) {
- future.setProgressValue(1);
+ promise.setProgressRange(0, 1);
+ if (promise.isCanceled()) {
+ promise.setProgressValue(1);
return;
}
- parser->update(future, updateParams);
+ parser->update(promise, updateParams);
CppModelManager::instance()->finishedRefreshingSourceFiles({parser->filePath().toString()});
- future.setProgressValue(1);
+ promise.setProgressValue(1);
}
} // namespace CppEditor
diff --git a/src/plugins/cppeditor/baseeditordocumentprocessor.h b/src/plugins/cppeditor/baseeditordocumentprocessor.h
index afb74f46e64..78c0f55aabc 100644
--- a/src/plugins/cppeditor/baseeditordocumentprocessor.h
+++ b/src/plugins/cppeditor/baseeditordocumentprocessor.h
@@ -23,6 +23,11 @@
#include <functional>
+QT_BEGIN_NAMESPACE
+template <typename T>
+class QPromise;
+QT_END_NAMESPACE
+
namespace TextEditor { class TextDocument; }
namespace CppEditor {
@@ -83,7 +88,7 @@ signals:
void semanticInfoUpdated(const SemanticInfo semanticInfo); // TODO: Remove me
protected:
- static void runParser(QFutureInterface<void> &future,
+ static void runParser(QPromise<void> &promise,
BaseEditorDocumentParser::Ptr parser,
BaseEditorDocumentParser::UpdateParams updateParams);
diff --git a/src/plugins/cppeditor/builtincursorinfo.cpp b/src/plugins/cppeditor/builtincursorinfo.cpp
index 14d99ce4db8..6a0897c2433 100644
--- a/src/plugins/cppeditor/builtincursorinfo.cpp
+++ b/src/plugins/cppeditor/builtincursorinfo.cpp
@@ -14,9 +14,9 @@
#include <cplusplus/Macro.h>
#include <cplusplus/TranslationUnit.h>
-#include <utils/textutils.h>
+#include <utils/asynctask.h>
#include <utils/qtcassert.h>
-#include <utils/runextensions.h>
+#include <utils/textutils.h>
#include <QTextBlock>
@@ -322,7 +322,7 @@ QFuture<CursorInfo> BuiltinCursorInfo::run(const CursorInfoParams &cursorInfoPar
QString expression;
Scope *scope = canonicalSymbol.getScopeAndExpression(textCursor, &expression);
- return Utils::runAsync(&FindUses::find, document, snapshot, line, column, scope, expression);
+ return Utils::asyncRun(&FindUses::find, document, snapshot, line, column, scope, expression);
}
SemanticInfo::LocalUseMap
diff --git a/src/plugins/cppeditor/builtineditordocumentparser.cpp b/src/plugins/cppeditor/builtineditordocumentparser.cpp
index 60c768b3a15..ca7b83960dd 100644
--- a/src/plugins/cppeditor/builtineditordocumentparser.cpp
+++ b/src/plugins/cppeditor/builtineditordocumentparser.cpp
@@ -11,6 +11,8 @@
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
+#include <QPromise>
+
using namespace CPlusPlus;
using namespace Utils;
@@ -42,7 +44,7 @@ BuiltinEditorDocumentParser::BuiltinEditorDocumentParser(const FilePath &filePat
qRegisterMetaType<CPlusPlus::Snapshot>("CPlusPlus::Snapshot");
}
-void BuiltinEditorDocumentParser::updateImpl(const QFutureInterface<void> &future,
+void BuiltinEditorDocumentParser::updateImpl(const QPromise<void> &promise,
const UpdateParams &updateParams)
{
if (filePath().isEmpty())
@@ -180,7 +182,7 @@ void BuiltinEditorDocumentParser::updateImpl(const QFutureInterface<void> &futur
doc->releaseSourceAndAST();
});
sourceProcessor.setFileSizeLimitInMb(m_fileSizeLimitInMb);
- sourceProcessor.setCancelChecker([future]() { return future.isCanceled(); });
+ sourceProcessor.setCancelChecker([&promise] { return promise.isCanceled(); });
Snapshot globalSnapshot = modelManager->snapshot();
globalSnapshot.remove(filePath());
diff --git a/src/plugins/cppeditor/builtineditordocumentparser.h b/src/plugins/cppeditor/builtineditordocumentparser.h
index b1a400a7c43..31ac126c694 100644
--- a/src/plugins/cppeditor/builtineditordocumentparser.h
+++ b/src/plugins/cppeditor/builtineditordocumentparser.h
@@ -34,8 +34,7 @@ public:
static Ptr get(const Utils::FilePath &filePath);
private:
- void updateImpl(const QFutureInterface<void> &future,
- const UpdateParams &updateParams) override;
+ void updateImpl(const QPromise<void> &promise, const UpdateParams &updateParams) override;
void addFileAndDependencies(CPlusPlus::Snapshot *snapshot,
QSet<Utils::FilePath> *toRemove,
const Utils::FilePath &fileName) const;
diff --git a/src/plugins/cppeditor/builtineditordocumentprocessor.cpp b/src/plugins/cppeditor/builtineditordocumentprocessor.cpp
index c94a639eefb..0ea0c9c2dd7 100644
--- a/src/plugins/cppeditor/builtineditordocumentprocessor.cpp
+++ b/src/plugins/cppeditor/builtineditordocumentprocessor.cpp
@@ -18,9 +18,9 @@
#include <cplusplus/CppDocument.h>
#include <cplusplus/SimpleLexer.h>
-#include <utils/textutils.h>
+#include <utils/asynctask.h>
#include <utils/qtcassert.h>
-#include <utils/runextensions.h>
+#include <utils/textutils.h>
#include <QLoggingCategory>
#include <QTextBlock>
@@ -180,10 +180,8 @@ BuiltinEditorDocumentProcessor::~BuiltinEditorDocumentProcessor()
void BuiltinEditorDocumentProcessor::runImpl(
const BaseEditorDocumentParser::UpdateParams &updateParams)
{
- m_parserFuture = Utils::runAsync(CppModelManager::instance()->sharedThreadPool(),
- runParser,
- parser(),
- updateParams);
+ m_parserFuture = Utils::asyncRun(CppModelManager::instance()->sharedThreadPool(),
+ runParser, parser(), updateParams);
}
BaseEditorDocumentParser::Ptr BuiltinEditorDocumentProcessor::parser()
diff --git a/src/plugins/cppeditor/cppcodeformatter.cpp b/src/plugins/cppeditor/cppcodeformatter.cpp
index ce250f672b0..6b30e78fa6c 100644
--- a/src/plugins/cppeditor/cppcodeformatter.cpp
+++ b/src/plugins/cppeditor/cppcodeformatter.cpp
@@ -924,6 +924,7 @@ bool CodeFormatter::tryStatement()
return true;
switch (kind) {
case T_RETURN:
+ case T_CO_RETURN:
enter(return_statement);
enter(expression);
return true;
@@ -1651,6 +1652,7 @@ void QtStyleCodeFormatter::adjustIndent(const Tokens &tokens, int lexerState, in
case T_BREAK:
case T_CONTINUE:
case T_RETURN:
+ case T_CO_RETURN:
if (topState.type == case_cont) {
*indentDepth = topState.savedIndentDepth;
if (m_styleSettings.indentControlFlowRelativeToSwitchLabels)
diff --git a/src/plugins/cppeditor/cppcodemodelinspectordumper.cpp b/src/plugins/cppeditor/cppcodemodelinspectordumper.cpp
index 45de8f30539..2a6dfb6e1e8 100644
--- a/src/plugins/cppeditor/cppcodemodelinspectordumper.cpp
+++ b/src/plugins/cppeditor/cppcodemodelinspectordumper.cpp
@@ -254,12 +254,19 @@ QString Utils::toString(CPlusPlus::Kind kind)
TOKEN(T_CASE);
TOKEN(T_CATCH);
TOKEN(T_CHAR);
+ TOKEN(T_CHAR8_T);
TOKEN(T_CHAR16_T);
TOKEN(T_CHAR32_T);
TOKEN(T_CLASS);
+ TOKEN(T_CO_AWAIT);
+ TOKEN(T_CO_RETURN);
+ TOKEN(T_CO_YIELD);
+ TOKEN(T_CONCEPT);
TOKEN_AND_ALIASES(T_CONST, T___CONST/T___CONST__);
TOKEN(T_CONST_CAST);
TOKEN(T_CONSTEXPR);
+ TOKEN(T_CONSTEVAL);
+ TOKEN(T_CONSTINIT);
TOKEN(T_CONTINUE);
TOKEN_AND_ALIASES(T_DECLTYPE, T___DECLTYPE);
TOKEN(T_DEFAULT);
@@ -292,6 +299,7 @@ QString Utils::toString(CPlusPlus::Kind kind)
TOKEN(T_PUBLIC);
TOKEN(T_REGISTER);
TOKEN(T_REINTERPRET_CAST);
+ TOKEN(T_REQUIRES);
TOKEN(T_RETURN);
TOKEN(T_SHORT);
TOKEN(T_SIGNED);
diff --git a/src/plugins/cppeditor/cppcodemodelsettings.cpp b/src/plugins/cppeditor/cppcodemodelsettings.cpp
index e0873427f79..d04d6c70eb6 100644
--- a/src/plugins/cppeditor/cppcodemodelsettings.cpp
+++ b/src/plugins/cppeditor/cppcodemodelsettings.cpp
@@ -9,6 +9,7 @@
#include "cpptoolsreuse.h"
#include <coreplugin/icore.h>
+
#include <projectexplorer/project.h>
#include <projectexplorer/session.h>
diff --git a/src/plugins/cppeditor/cppcompletion_test.cpp b/src/plugins/cppeditor/cppcompletion_test.cpp
index 78407d59599..1e212dde609 100644
--- a/src/plugins/cppeditor/cppcompletion_test.cpp
+++ b/src/plugins/cppeditor/cppcompletion_test.cpp
@@ -70,6 +70,10 @@ public:
// Get Document
const Document::Ptr document = waitForFileInGlobalSnapshot(filePath);
QVERIFY(document);
+ if (!document->diagnosticMessages().isEmpty()) {
+ for (const Document::DiagnosticMessage &m : document->diagnosticMessages())
+ qDebug().noquote() << m.text();
+ }
QVERIFY(document->diagnosticMessages().isEmpty());
m_snapshot.insert(document);
@@ -409,16 +413,16 @@ static void enumTestCase(const QByteArray &tag, const QByteArray &source,
const QByteArray &prefix = QByteArray())
{
QByteArray fullSource = source;
- fullSource.replace('$', "enum E { val1, val2, val3 };");
- QTest::newRow(tag) << fullSource << (prefix + "val")
- << QStringList({"val1", "val2", "val3"});
+ fullSource.replace('$', "enum E { value1, value2, value3 };");
+ QTest::newRow(tag) << fullSource << (prefix + "value")
+ << QStringList({"value1", "value2", "value3"});
QTest::newRow(QByteArray{tag + "_cxx11"}) << fullSource << QByteArray{prefix + "E::"}
- << QStringList({"E", "val1", "val2", "val3"});
+ << QStringList({"E", "value1", "value2", "value3"});
fullSource.replace("enum E ", "enum ");
- QTest::newRow(QByteArray{tag + "_anon"}) << fullSource << QByteArray{prefix + "val"}
- << QStringList({"val1", "val2", "val3"});
+ QTest::newRow(QByteArray{tag + "_anon"}) << fullSource << QByteArray{prefix + "value"}
+ << QStringList({"value1", "value2", "value3"});
}
void CompletionTest::testCompletion_data()
diff --git a/src/plugins/cppeditor/cppcurrentdocumentfilter.cpp b/src/plugins/cppeditor/cppcurrentdocumentfilter.cpp
index 0285fd71fd1..6c6a568fac9 100644
--- a/src/plugins/cppeditor/cppcurrentdocumentfilter.cpp
+++ b/src/plugins/cppeditor/cppcurrentdocumentfilter.cpp
@@ -9,10 +9,12 @@
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/editormanager/ieditor.h>
-#include <coreplugin/idocument.h>
+#include <utils/algorithm.h>
+#include <QHash>
#include <QRegularExpression>
+using namespace Core;
using namespace CPlusPlus;
namespace CppEditor::Internal {
@@ -33,9 +35,9 @@ CppCurrentDocumentFilter::CppCurrentDocumentFilter(CppModelManager *manager)
connect(manager, &CppModelManager::documentUpdated,
this, &CppCurrentDocumentFilter::onDocumentUpdated);
- connect(Core::EditorManager::instance(), &Core::EditorManager::currentEditorChanged,
+ connect(EditorManager::instance(), &EditorManager::currentEditorChanged,
this, &CppCurrentDocumentFilter::onCurrentEditorChanged);
- connect(Core::EditorManager::instance(), &Core::EditorManager::editorAboutToClose,
+ connect(EditorManager::instance(), &EditorManager::editorAboutToClose,
this, &CppCurrentDocumentFilter::onEditorAboutToClose);
}
@@ -48,16 +50,20 @@ void CppCurrentDocumentFilter::makeAuxiliary()
setHidden(true);
}
-QList<Core::LocatorFilterEntry> CppCurrentDocumentFilter::matchesFor(
- QFutureInterface<Core::LocatorFilterEntry> &future, const QString & entry)
+QList<LocatorFilterEntry> CppCurrentDocumentFilter::matchesFor(
+ QFutureInterface<LocatorFilterEntry> &future, const QString & entry)
{
- QList<Core::LocatorFilterEntry> goodEntries;
- QList<Core::LocatorFilterEntry> betterEntries;
-
const QRegularExpression regexp = createRegExp(entry);
if (!regexp.isValid())
- return goodEntries;
-
+ return {};
+
+ struct Entry
+ {
+ LocatorFilterEntry entry;
+ IndexItem::Ptr info;
+ };
+ QList<Entry> goodEntries;
+ QList<Entry> betterEntries;
const QList<IndexItem::Ptr> items = itemsOfCurrentDocument();
for (const IndexItem::Ptr &info : items) {
if (future.isCanceled())
@@ -72,7 +78,6 @@ QList<Core::LocatorFilterEntry> CppCurrentDocumentFilter::matchesFor(
QRegularExpressionMatch match = regexp.match(matchString);
if (match.hasMatch()) {
const bool betterMatch = match.capturedStart() == 0;
- QVariant id = QVariant::fromValue(info);
QString name = matchString;
QString extraInfo = info->symbolScope();
if (info->type() == IndexItem::Function) {
@@ -82,38 +87,55 @@ QList<Core::LocatorFilterEntry> CppCurrentDocumentFilter::matchesFor(
}
}
- Core::LocatorFilterEntry filterEntry(this, name, id, info->icon());
+ LocatorFilterEntry filterEntry(this, name, {}, info->icon());
+ filterEntry.linkForEditor = {info->filePath(), info->line(), info->column()};
filterEntry.extraInfo = extraInfo;
if (match.hasMatch()) {
filterEntry.highlightInfo = highlightInfo(match);
} else {
match = regexp.match(extraInfo);
filterEntry.highlightInfo =
- highlightInfo(match, Core::LocatorFilterEntry::HighlightInfo::ExtraInfo);
+ highlightInfo(match, LocatorFilterEntry::HighlightInfo::ExtraInfo);
}
if (betterMatch)
- betterEntries.append(filterEntry);
+ betterEntries.append({filterEntry, info});
else
- goodEntries.append(filterEntry);
+ goodEntries.append({filterEntry, info});
}
}
// entries are unsorted by design!
-
betterEntries += goodEntries;
- return betterEntries;
-}
-void CppCurrentDocumentFilter::accept(const Core::LocatorFilterEntry &selection,
- QString *newText, int *selectionStart,
- int *selectionLength) const
-{
- Q_UNUSED(newText)
- Q_UNUSED(selectionStart)
- Q_UNUSED(selectionLength)
- IndexItem::Ptr info = qvariant_cast<IndexItem::Ptr>(selection.internalData);
- Core::EditorManager::openEditorAt({info->filePath(), info->line(), info->column()});
+ QHash<QString, QList<Entry>> possibleDuplicates;
+ for (const Entry &e : std::as_const(betterEntries))
+ possibleDuplicates[e.info->scopedSymbolName() + e.info->symbolType()] << e;
+ for (auto it = possibleDuplicates.cbegin(); it != possibleDuplicates.cend(); ++it) {
+ const QList<Entry> &duplicates = it.value();
+ if (duplicates.size() == 1)
+ continue;
+ QList<Entry> declarations;
+ QList<Entry> definitions;
+ for (const Entry &candidate : duplicates) {
+ const IndexItem::Ptr info = candidate.info;
+ if (info->type() != IndexItem::Function)
+ break;
+ if (info->isFunctionDefinition())
+ definitions << candidate;
+ else
+ declarations << candidate;
+ }
+ if (definitions.size() == 1
+ && declarations.size() + definitions.size() == duplicates.size()) {
+ for (const Entry &decl : std::as_const(declarations)) {
+ Utils::erase(betterEntries, [&decl](const Entry &e) {
+ return e.info == decl.info;
+ });
+ }
+ }
+ }
+ return Utils::transform(betterEntries, [](const Entry &entry) { return entry.entry; });
}
void CppCurrentDocumentFilter::onDocumentUpdated(Document::Ptr doc)
@@ -123,7 +145,7 @@ void CppCurrentDocumentFilter::onDocumentUpdated(Document::Ptr doc)
m_itemsOfCurrentDoc.clear();
}
-void CppCurrentDocumentFilter::onCurrentEditorChanged(Core::IEditor *currentEditor)
+void CppCurrentDocumentFilter::onCurrentEditorChanged(IEditor *currentEditor)
{
QMutexLocker locker(&m_mutex);
if (currentEditor)
@@ -133,7 +155,7 @@ void CppCurrentDocumentFilter::onCurrentEditorChanged(Core::IEditor *currentEdit
m_itemsOfCurrentDoc.clear();
}
-void CppCurrentDocumentFilter::onEditorAboutToClose(Core::IEditor *editorAboutToClose)
+void CppCurrentDocumentFilter::onEditorAboutToClose(IEditor *editorAboutToClose)
{
if (!editorAboutToClose)
return;
diff --git a/src/plugins/cppeditor/cppcurrentdocumentfilter.h b/src/plugins/cppeditor/cppcurrentdocumentfilter.h
index 484812a0cba..72a8359b097 100644
--- a/src/plugins/cppeditor/cppcurrentdocumentfilter.h
+++ b/src/plugins/cppeditor/cppcurrentdocumentfilter.h
@@ -27,9 +27,6 @@ public:
QList<Core::LocatorFilterEntry> matchesFor(QFutureInterface<Core::LocatorFilterEntry> &future,
const QString &entry) override;
- void accept(const Core::LocatorFilterEntry &selection,
- QString *newText, int *selectionStart, int *selectionLength) const override;
-
private:
void onDocumentUpdated(CPlusPlus::Document::Ptr doc);
void onCurrentEditorChanged(Core::IEditor *currentEditor);
diff --git a/src/plugins/cppeditor/cppeditor.qbs b/src/plugins/cppeditor/cppeditor.qbs
index cb7b145078c..1824af07090 100644
--- a/src/plugins/cppeditor/cppeditor.qbs
+++ b/src/plugins/cppeditor/cppeditor.qbs
@@ -245,9 +245,7 @@ QtcPlugin {
]
}
- Group {
- name: "Tests"
- condition: qtc.testsEnabled
+ QtcTestFiles {
cpp.defines: outer.concat(['SRCDIR="' + FileInfo.path(filePath) + '"'])
files: [
"compileroptionsbuilder_test.cpp",
diff --git a/src/plugins/cppeditor/cppeditorplugin.cpp b/src/plugins/cppeditor/cppeditorplugin.cpp
index f1fc63b49ab..2d4cabe726e 100644
--- a/src/plugins/cppeditor/cppeditorplugin.cpp
+++ b/src/plugins/cppeditor/cppeditorplugin.cpp
@@ -431,6 +431,10 @@ void CppEditorPlugin::initialize()
contextMenu->addAction(cmd, Constants::G_CONTEXT_FIRST);
cppToolsMenu->addAction(cmd);
+ cmd = ActionManager::command(TextEditor::Constants::OPEN_CALL_HIERARCHY);
+ contextMenu->addAction(cmd, Constants::G_CONTEXT_FIRST);
+ cppToolsMenu->addAction(cmd);
+
// Refactoring sub-menu
Command *sep = contextMenu->addSeparator();
sep->action()->setObjectName(QLatin1String(Constants::M_REFACTORING_MENU_INSERTION_POINT));
diff --git a/src/plugins/cppeditor/cppeditorwidget.cpp b/src/plugins/cppeditor/cppeditorwidget.cpp
index 20fe8e6254b..44f9e01a017 100644
--- a/src/plugins/cppeditor/cppeditorwidget.cpp
+++ b/src/plugins/cppeditor/cppeditorwidget.cpp
@@ -30,9 +30,9 @@
#include <projectexplorer/buildsystem.h>
#include <projectexplorer/extracompiler.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/projectnodes.h>
#include <projectexplorer/projecttree.h>
-#include <projectexplorer/session.h>
#include <projectexplorer/target.h>
#include <texteditor/basefilefind.h>
@@ -755,7 +755,7 @@ void CppEditorWidget::showRenameWarningIfFileIsGenerated(const Utils::FilePath &
{
if (filePath.isEmpty())
return;
- for (const Project * const project : SessionManager::projects()) {
+ for (const Project * const project : ProjectManager::projects()) {
const Node * const node = project->nodeForFilePath(filePath);
if (!node)
continue;
@@ -989,7 +989,7 @@ void CppEditorWidget::findLinkAt(const QTextCursor &cursor,
const QString fileName = filePath.fileName();
if (fileName.startsWith("ui_") && fileName.endsWith(".h")) {
const QString uiFileName = fileName.mid(3, fileName.length() - 4) + "ui";
- for (const Project * const project : SessionManager::projects()) {
+ for (const Project * const project : ProjectManager::projects()) {
const auto nodeMatcher = [uiFileName](Node *n) {
return n->filePath().fileName() == uiFileName;
};
diff --git a/src/plugins/cppeditor/cppelementevaluator.cpp b/src/plugins/cppeditor/cppelementevaluator.cpp
index dabb8cffc0d..3ce1761ce59 100644
--- a/src/plugins/cppeditor/cppelementevaluator.cpp
+++ b/src/plugins/cppeditor/cppelementevaluator.cpp
@@ -14,7 +14,7 @@
#include <cplusplus/Icons.h>
#include <cplusplus/TypeOfExpression.h>
-#include <utils/runextensions.h>
+#include <utils/asynctask.h>
#include <QDir>
#include <QQueue>
@@ -140,20 +140,20 @@ CppClass *CppClass::toCppClass()
return this;
}
-void CppClass::lookupBases(QFutureInterfaceBase &futureInterface,
- Symbol *declaration, const LookupContext &context)
+void CppClass::lookupBases(const QFuture<void> &future, Symbol *declaration,
+ const LookupContext &context)
{
ClassOrNamespace *hierarchy = context.lookupType(declaration);
if (!hierarchy)
return;
QSet<ClassOrNamespace *> visited;
- addBaseHierarchy(futureInterface, context, hierarchy, &visited);
+ addBaseHierarchy(future, context, hierarchy, &visited);
}
-void CppClass::addBaseHierarchy(QFutureInterfaceBase &futureInterface, const LookupContext &context,
+void CppClass::addBaseHierarchy(const QFuture<void> &future, const LookupContext &context,
ClassOrNamespace *hierarchy, QSet<ClassOrNamespace *> *visited)
{
- if (futureInterface.isCanceled())
+ if (future.isCanceled())
return;
visited->insert(hierarchy);
const QList<ClassOrNamespace *> &baseClasses = hierarchy->usings();
@@ -165,21 +165,21 @@ void CppClass::addBaseHierarchy(QFutureInterfaceBase &futureInterface, const Loo
ClassOrNamespace *baseHierarchy = context.lookupType(symbol);
if (baseHierarchy && !visited->contains(baseHierarchy)) {
CppClass classSymbol(symbol);
- classSymbol.addBaseHierarchy(futureInterface, context, baseHierarchy, visited);
+ classSymbol.addBaseHierarchy(future, context, baseHierarchy, visited);
bases.append(classSymbol);
}
}
}
}
-void CppClass::lookupDerived(QFutureInterfaceBase &futureInterface,
- Symbol *declaration, const Snapshot &snapshot)
+void CppClass::lookupDerived(const QFuture<void> &future, Symbol *declaration,
+ const Snapshot &snapshot)
{
- snapshot.updateDependencyTable(futureInterface);
- if (futureInterface.isCanceled())
+ snapshot.updateDependencyTable(future);
+ if (future.isCanceled())
return;
addDerivedHierarchy(TypeHierarchyBuilder::buildDerivedTypeHierarchy(
- futureInterface, declaration, snapshot));
+ declaration, snapshot, future));
}
void CppClass::addDerivedHierarchy(const TypeHierarchy &hierarchy)
@@ -340,13 +340,13 @@ static Symbol *followTemplateAsClass(Symbol *symbol)
return symbol;
}
-static void createTypeHierarchy(QFutureInterface<QSharedPointer<CppElement>> &futureInterface,
+static void createTypeHierarchy(QPromise<QSharedPointer<CppElement>> &promise,
const Snapshot &snapshot,
const LookupItem &lookupItem,
const LookupContext &context,
SymbolFinder symbolFinder)
{
- if (futureInterface.isCanceled())
+ if (promise.isCanceled())
return;
Symbol *declaration = lookupItem.declaration();
@@ -360,16 +360,17 @@ static void createTypeHierarchy(QFutureInterface<QSharedPointer<CppElement>> &fu
declaration = followClassDeclaration(declaration, snapshot, symbolFinder, &contextToUse);
declaration = followTemplateAsClass(declaration);
- if (futureInterface.isCanceled())
+ if (promise.isCanceled())
return;
QSharedPointer<CppClass> cppClass(new CppClass(declaration));
- cppClass->lookupBases(futureInterface, declaration, contextToUse);
- if (futureInterface.isCanceled())
+ const QFuture<void> future = QFuture<void>(promise.future());
+ cppClass->lookupBases(future, declaration, contextToUse);
+ if (promise.isCanceled())
return;
- cppClass->lookupDerived(futureInterface, declaration, snapshot);
- if (futureInterface.isCanceled())
+ cppClass->lookupDerived(future, declaration, snapshot);
+ if (promise.isCanceled())
return;
- futureInterface.reportResult(cppClass);
+ promise.addResult(cppClass);
}
static QSharedPointer<CppElement> handleLookupItemMatch(const Snapshot &snapshot,
@@ -495,7 +496,7 @@ static QFuture<QSharedPointer<CppElement>> asyncExec(
const CPlusPlus::Snapshot &snapshot, const CPlusPlus::LookupItem &lookupItem,
const CPlusPlus::LookupContext &lookupContext)
{
- return Utils::runAsync(&createTypeHierarchy, snapshot, lookupItem, lookupContext,
+ return Utils::asyncRun(&createTypeHierarchy, snapshot, lookupItem, lookupContext,
*CppModelManager::instance()->symbolFinder());
}
diff --git a/src/plugins/cppeditor/cppelementevaluator.h b/src/plugins/cppeditor/cppelementevaluator.h
index 7d03df67121..47ddcbeae19 100644
--- a/src/plugins/cppeditor/cppelementevaluator.h
+++ b/src/plugins/cppeditor/cppelementevaluator.h
@@ -92,16 +92,16 @@ public:
CppClass *toCppClass() final;
- void lookupBases(QFutureInterfaceBase &futureInterface,
- CPlusPlus::Symbol *declaration, const CPlusPlus::LookupContext &context);
- void lookupDerived(QFutureInterfaceBase &futureInterface,
- CPlusPlus::Symbol *declaration, const CPlusPlus::Snapshot &snapshot);
+ void lookupBases(const QFuture<void> &future, CPlusPlus::Symbol *declaration,
+ const CPlusPlus::LookupContext &context);
+ void lookupDerived(const QFuture<void> &future, CPlusPlus::Symbol *declaration,
+ const CPlusPlus::Snapshot &snapshot);
QList<CppClass> bases;
QList<CppClass> derived;
private:
- void addBaseHierarchy(QFutureInterfaceBase &futureInterface,
+ void addBaseHierarchy(const QFuture<void> &future,
const CPlusPlus::LookupContext &context,
CPlusPlus::ClassOrNamespace *hierarchy,
QSet<CPlusPlus::ClassOrNamespace *> *visited);
diff --git a/src/plugins/cppeditor/cppfindreferences.cpp b/src/plugins/cppeditor/cppfindreferences.cpp
index 940eb358e93..589ac2d1a67 100644
--- a/src/plugins/cppeditor/cppfindreferences.cpp
+++ b/src/plugins/cppeditor/cppfindreferences.cpp
@@ -10,25 +10,27 @@
#include "cpptoolsreuse.h"
#include "cppworkingcopy.h"
+#include <cplusplus/Overview.h>
+
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/icore.h>
#include <coreplugin/progressmanager/futureprogress.h>
#include <coreplugin/progressmanager/progressmanager.h>
+
#include <projectexplorer/projectexplorer.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/projectnodes.h>
#include <projectexplorer/projecttree.h>
-#include <projectexplorer/session.h>
+
#include <texteditor/basefilefind.h>
#include <utils/algorithm.h>
+#include <utils/asynctask.h>
#include <utils/qtcassert.h>
-#include <utils/runextensions.h>
#include <utils/textfileformat.h>
-#include <cplusplus/Overview.h>
#include <QtConcurrentMap>
#include <QCheckBox>
-#include <QDir>
#include <QFutureWatcher>
#include <QVBoxLayout>
@@ -218,7 +220,7 @@ class ProcessFile
const CPlusPlus::Snapshot snapshot;
CPlusPlus::Document::Ptr symbolDocument;
CPlusPlus::Symbol *symbol;
- QFutureInterface<CPlusPlus::Usage> *future;
+ QPromise<CPlusPlus::Usage> *m_promise;
const bool categorize;
public:
@@ -230,22 +232,21 @@ public:
const CPlusPlus::Snapshot snapshot,
CPlusPlus::Document::Ptr symbolDocument,
CPlusPlus::Symbol *symbol,
- QFutureInterface<CPlusPlus::Usage> *future,
+ QPromise<CPlusPlus::Usage> *promise,
bool categorize)
: workingCopy(workingCopy),
snapshot(snapshot),
symbolDocument(symbolDocument),
symbol(symbol),
- future(future),
+ m_promise(promise),
categorize(categorize)
{ }
QList<CPlusPlus::Usage> operator()(const Utils::FilePath &filePath)
{
QList<CPlusPlus::Usage> usages;
- if (future->isPaused())
- future->waitForResume();
- if (future->isCanceled())
+ m_promise->suspendIfRequested();
+ if (m_promise->isCanceled())
return usages;
const CPlusPlus::Identifier *symbolId = symbol->identifier();
@@ -275,25 +276,24 @@ public:
usages = process.usages();
}
- if (future->isPaused())
- future->waitForResume();
+ m_promise->suspendIfRequested();
return usages;
}
};
class UpdateUI
{
- QFutureInterface<CPlusPlus::Usage> *future;
+ QPromise<CPlusPlus::Usage> *m_promise;
public:
- explicit UpdateUI(QFutureInterface<CPlusPlus::Usage> *future): future(future) {}
+ explicit UpdateUI(QPromise<CPlusPlus::Usage> *promise): m_promise(promise) {}
void operator()(QList<CPlusPlus::Usage> &, const QList<CPlusPlus::Usage> &usages)
{
for (const CPlusPlus::Usage &u : usages)
- future->reportResult(u);
+ m_promise->addResult(u);
- future->setProgressValue(future->progressValue() + 1);
+ m_promise->setProgressValue(m_promise->future().progressValue() + 1);
}
};
@@ -319,7 +319,7 @@ QList<int> CppFindReferences::references(CPlusPlus::Symbol *symbol,
return references;
}
-static void find_helper(QFutureInterface<CPlusPlus::Usage> &future,
+static void find_helper(QPromise<CPlusPlus::Usage> &promise,
const WorkingCopy workingCopy,
const CPlusPlus::LookupContext &context,
CPlusPlus::Symbol *symbol,
@@ -353,16 +353,16 @@ static void find_helper(QFutureInterface<CPlusPlus::Usage> &future,
}
files = Utils::filteredUnique(files);
- future.setProgressRange(0, files.size());
+ promise.setProgressRange(0, files.size());
- ProcessFile process(workingCopy, snapshot, context.thisDocument(), symbol, &future, categorize);
- UpdateUI reduce(&future);
+ ProcessFile process(workingCopy, snapshot, context.thisDocument(), symbol, &promise, categorize);
+ UpdateUI reduce(&promise);
// This thread waits for blockingMappedReduced to finish, so reduce the pool's used thread count
// so the blockingMappedReduced can use one more thread, and increase it again afterwards.
QThreadPool::globalInstance()->releaseThread();
QtConcurrent::blockingMappedReduced<QList<CPlusPlus::Usage> > (files, process, reduce);
QThreadPool::globalInstance()->reserveThread();
- future.setProgressValue(files.size());
+ promise.setProgressValue(files.size());
}
void CppFindReferences::findUsages(CPlusPlus::Symbol *symbol,
@@ -437,7 +437,7 @@ void CppFindReferences::findAll_helper(SearchResult *search, CPlusPlus::Symbol *
SearchResultWindow::instance()->popup(IOutputPane::ModeSwitch | IOutputPane::WithFocus);
const WorkingCopy workingCopy = m_modelManager->workingCopy();
QFuture<CPlusPlus::Usage> result;
- result = Utils::runAsync(m_modelManager->sharedThreadPool(), find_helper,
+ result = Utils::asyncRun(m_modelManager->sharedThreadPool(), find_helper,
workingCopy, context, symbol, categorize);
createWatcher(result, search);
@@ -577,7 +577,7 @@ static void displayResults(SearchResult *search,
item.setStyle(colorStyleForUsageType(result.tags));
item.setUseTextEditorFont(true);
if (search->supportsReplace())
- item.setSelectForReplacement(SessionManager::projectForFile(result.path));
+ item.setSelectForReplacement(ProjectManager::projectForFile(result.path));
search->addResult(item);
if (parameters.prettySymbolName.isEmpty())
@@ -586,7 +586,7 @@ static void displayResults(SearchResult *search,
if (parameters.filesToRename.contains(result.path))
continue;
- if (!SessionManager::projectForFile(result.path))
+ if (!ProjectManager::projectForFile(result.path))
continue;
if (result.path.baseName().compare(parameters.prettySymbolName, Qt::CaseInsensitive) == 0)
@@ -623,7 +623,7 @@ class FindMacroUsesInFile
const WorkingCopy workingCopy;
const CPlusPlus::Snapshot snapshot;
const CPlusPlus::Macro &macro;
- QFutureInterface<CPlusPlus::Usage> *future;
+ QPromise<CPlusPlus::Usage> *m_promise;
public:
// needed by QtConcurrent
@@ -633,8 +633,8 @@ public:
FindMacroUsesInFile(const WorkingCopy &workingCopy,
const CPlusPlus::Snapshot snapshot,
const CPlusPlus::Macro &macro,
- QFutureInterface<CPlusPlus::Usage> *future)
- : workingCopy(workingCopy), snapshot(snapshot), macro(macro), future(future)
+ QPromise<CPlusPlus::Usage> *promise)
+ : workingCopy(workingCopy), snapshot(snapshot), macro(macro), m_promise(promise)
{ }
QList<CPlusPlus::Usage> operator()(const Utils::FilePath &fileName)
@@ -644,9 +644,8 @@ public:
QByteArray source;
restart_search:
- if (future->isPaused())
- future->waitForResume();
- if (future->isCanceled())
+ m_promise->suspendIfRequested();
+ if (m_promise->isCanceled())
return usages;
usages.clear();
@@ -674,8 +673,7 @@ restart_search:
}
}
- if (future->isPaused())
- future->waitForResume();
+ m_promise->suspendIfRequested();
return usages;
}
@@ -704,7 +702,7 @@ restart_search:
} // end of anonymous namespace
-static void findMacroUses_helper(QFutureInterface<CPlusPlus::Usage> &future,
+static void findMacroUses_helper(QPromise<CPlusPlus::Usage> &promise,
const WorkingCopy workingCopy,
const CPlusPlus::Snapshot snapshot,
const CPlusPlus::Macro macro)
@@ -713,15 +711,15 @@ static void findMacroUses_helper(QFutureInterface<CPlusPlus::Usage> &future,
FilePaths files{sourceFile};
files = Utils::filteredUnique(files + snapshot.filesDependingOn(sourceFile));
- future.setProgressRange(0, files.size());
- FindMacroUsesInFile process(workingCopy, snapshot, macro, &future);
- UpdateUI reduce(&future);
+ promise.setProgressRange(0, files.size());
+ FindMacroUsesInFile process(workingCopy, snapshot, macro, &promise);
+ UpdateUI reduce(&promise);
// This thread waits for blockingMappedReduced to finish, so reduce the pool's used thread count
// so the blockingMappedReduced can use one more thread, and increase it again afterwards.
QThreadPool::globalInstance()->releaseThread();
QtConcurrent::blockingMappedReduced<QList<CPlusPlus::Usage> > (files, process, reduce);
QThreadPool::globalInstance()->reserveThread();
- future.setProgressValue(files.size());
+ promise.setProgressValue(files.size());
}
void CppFindReferences::findMacroUses(const CPlusPlus::Macro &macro)
@@ -766,12 +764,12 @@ void CppFindReferences::findMacroUses(const CPlusPlus::Macro &macro, const QStri
item.setMainRange(macro.line(), column, macro.nameToQString().length());
item.setUseTextEditorFont(true);
if (search->supportsReplace())
- item.setSelectForReplacement(SessionManager::projectForFile(filePath));
+ item.setSelectForReplacement(ProjectManager::projectForFile(filePath));
search->addResult(item);
}
QFuture<CPlusPlus::Usage> result;
- result = Utils::runAsync(m_modelManager->sharedThreadPool(), findMacroUses_helper,
+ result = Utils::asyncRun(m_modelManager->sharedThreadPool(), findMacroUses_helper,
workingCopy, snapshot, macro);
createWatcher(result, search);
@@ -832,7 +830,7 @@ void CppFindReferences::checkUnused(Core::SearchResult *search, const Link &link
});
connect(search, &SearchResult::canceled, watcher, [watcher] { watcher->cancel(); });
connect(search, &SearchResult::destroyed, watcher, [watcher] { watcher->cancel(); });
- watcher->setFuture(Utils::runAsync(m_modelManager->sharedThreadPool(), find_helper,
+ watcher->setFuture(Utils::asyncRun(m_modelManager->sharedThreadPool(), find_helper,
m_modelManager->workingCopy(), context, symbol, true));
}
diff --git a/src/plugins/cppeditor/cppfunctiondecldeflink.cpp b/src/plugins/cppeditor/cppfunctiondecldeflink.cpp
index fa271108cf0..d196261db22 100644
--- a/src/plugins/cppeditor/cppfunctiondecldeflink.cpp
+++ b/src/plugins/cppeditor/cppfunctiondecldeflink.cpp
@@ -21,9 +21,9 @@
#include <cplusplus/Overview.h>
#include <cplusplus/TypeOfExpression.h>
+#include <utils/asynctask.h>
#include <utils/proxyaction.h>
#include <utils/qtcassert.h>
-#include <utils/runextensions.h>
#include <utils/tooltip/tooltip.h>
#include <QRegularExpression>
@@ -232,7 +232,7 @@ void FunctionDeclDefLinkFinder::startFindLinkAt(
// handle the rest in a thread
m_watcher.reset(new QFutureWatcher<QSharedPointer<FunctionDeclDefLink> >());
connect(m_watcher.data(), &QFutureWatcherBase::finished, this, &FunctionDeclDefLinkFinder::onFutureDone);
- m_watcher->setFuture(Utils::runAsync(findLinkHelper, result, refactoringChanges));
+ m_watcher->setFuture(Utils::asyncRun(findLinkHelper, result, refactoringChanges));
}
bool FunctionDeclDefLink::isValid() const
diff --git a/src/plugins/cppeditor/cpphighlighter.cpp b/src/plugins/cppeditor/cpphighlighter.cpp
index 1f83b317ada..0f9184600b8 100644
--- a/src/plugins/cppeditor/cpphighlighter.cpp
+++ b/src/plugins/cppeditor/cpphighlighter.cpp
@@ -513,6 +513,7 @@ void CppHighlighterTest::test_data()
QTest::newRow("struct keyword") << 25 << 1 << 25 << 6 << C_KEYWORD;
QTest::newRow("operator keyword") << 26 << 5 << 26 << 12 << C_KEYWORD;
QTest::newRow("type in conversion operator") << 26 << 14 << 26 << 16 << C_PRIMITIVE_TYPE;
+ QTest::newRow("concept keyword") << 29 << 22 << 29 << 28 << C_KEYWORD;
}
void CppHighlighterTest::test()
diff --git a/src/plugins/cppeditor/cppincludesfilter.cpp b/src/plugins/cppeditor/cppincludesfilter.cpp
index 9f637f6582b..44f21045d24 100644
--- a/src/plugins/cppeditor/cppincludesfilter.cpp
+++ b/src/plugins/cppeditor/cppincludesfilter.cpp
@@ -7,12 +7,17 @@
#include "cppeditortr.h"
#include "cppmodelmanager.h"
-#include <cplusplus/CppDocument.h>
#include <coreplugin/editormanager/documentmodel.h>
+
+#include <cplusplus/CppDocument.h>
+
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorer.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/session.h>
+#include <utils/tasktree.h>
+
using namespace Core;
using namespace ProjectExplorer;
using namespace Utils;
@@ -45,8 +50,6 @@ private:
FilePath m_currentPath;
};
-
-
void CppIncludesIterator::toFront()
{
m_queuedPaths = m_paths;
@@ -104,22 +107,23 @@ CppIncludesFilter::CppIncludesFilter()
"\"+<number>\" or \":<number>\" to jump to the column number as well."));
setDefaultShortcutString("ai");
setDefaultIncludedByDefault(true);
+ setRefreshRecipe(Tasking::Sync([this] { invalidateCache(); return true; }));
setPriority(ILocatorFilter::Low);
connect(ProjectExplorerPlugin::instance(), &ProjectExplorerPlugin::fileListChanged,
- this, &CppIncludesFilter::markOutdated);
+ this, &CppIncludesFilter::invalidateCache);
connect(CppModelManager::instance(), &CppModelManager::documentUpdated,
- this, &CppIncludesFilter::markOutdated);
+ this, &CppIncludesFilter::invalidateCache);
connect(CppModelManager::instance(), &CppModelManager::aboutToRemoveFiles,
- this, &CppIncludesFilter::markOutdated);
+ this, &CppIncludesFilter::invalidateCache);
connect(DocumentModel::model(), &QAbstractItemModel::rowsInserted,
- this, &CppIncludesFilter::markOutdated);
+ this, &CppIncludesFilter::invalidateCache);
connect(DocumentModel::model(), &QAbstractItemModel::rowsRemoved,
- this, &CppIncludesFilter::markOutdated);
+ this, &CppIncludesFilter::invalidateCache);
connect(DocumentModel::model(), &QAbstractItemModel::dataChanged,
- this, &CppIncludesFilter::markOutdated);
+ this, &CppIncludesFilter::invalidateCache);
connect(DocumentModel::model(), &QAbstractItemModel::modelReset,
- this, &CppIncludesFilter::markOutdated);
+ this, &CppIncludesFilter::invalidateCache);
}
void CppIncludesFilter::prepareSearch(const QString &entry)
@@ -128,7 +132,7 @@ void CppIncludesFilter::prepareSearch(const QString &entry)
if (m_needsUpdate) {
m_needsUpdate = false;
QSet<FilePath> seedPaths;
- for (Project *project : SessionManager::projects()) {
+ for (Project *project : ProjectManager::projects()) {
const FilePaths allFiles = project->files(Project::SourceFiles);
for (const FilePath &filePath : allFiles )
seedPaths.insert(filePath);
@@ -144,13 +148,7 @@ void CppIncludesFilter::prepareSearch(const QString &entry)
BaseFileFilter::prepareSearch(entry);
}
-void CppIncludesFilter::refresh(QFutureInterface<void> &future)
-{
- Q_UNUSED(future)
- QMetaObject::invokeMethod(this, &CppIncludesFilter::markOutdated, Qt::QueuedConnection);
-}
-
-void CppIncludesFilter::markOutdated()
+void CppIncludesFilter::invalidateCache()
{
m_needsUpdate = true;
setFileIterator(nullptr); // clean up
diff --git a/src/plugins/cppeditor/cppincludesfilter.h b/src/plugins/cppeditor/cppincludesfilter.h
index 6ae5dbb91c4..303de152d60 100644
--- a/src/plugins/cppeditor/cppincludesfilter.h
+++ b/src/plugins/cppeditor/cppincludesfilter.h
@@ -15,10 +15,9 @@ public:
// ILocatorFilter interface
public:
void prepareSearch(const QString &entry) override;
- void refresh(QFutureInterface<void> &future) override;
private:
- void markOutdated();
+ void invalidateCache();
bool m_needsUpdate = true;
};
diff --git a/src/plugins/cppeditor/cppindexingsupport.cpp b/src/plugins/cppeditor/cppindexingsupport.cpp
index 2d0e2b55194..4b415058862 100644
--- a/src/plugins/cppeditor/cppindexingsupport.cpp
+++ b/src/plugins/cppeditor/cppindexingsupport.cpp
@@ -15,8 +15,8 @@
#include <cplusplus/LookupContext.h>
+#include <utils/asynctask.h>
#include <utils/filepath.h>
-#include <utils/runextensions.h>
#include <utils/stringutils.h>
#include <utils/temporarydirectory.h>
@@ -116,8 +116,7 @@ void classifyFiles(const QSet<QString> &files, QStringList *headers, QStringList
}
}
-void indexFindErrors(QFutureInterface<void> &indexingFuture,
- const ParseParams params)
+void indexFindErrors(QPromise<void> &promise, const ParseParams params)
{
QStringList sources, headers;
classifyFiles(params.sourceFiles, &headers, &sources);
@@ -130,7 +129,7 @@ void indexFindErrors(QFutureInterface<void> &indexingFuture,
timer.start();
for (int i = 0, end = files.size(); i < end ; ++i) {
- if (indexingFuture.isCanceled())
+ if (promise.isCanceled())
break;
const QString file = files.at(i);
@@ -153,15 +152,14 @@ void indexFindErrors(QFutureInterface<void> &indexingFuture,
document->releaseSourceAndAST();
- indexingFuture.setProgressValue(i + 1);
+ promise.setProgressValue(i + 1);
}
const QString elapsedTime = Utils::formatElapsedTime(timer.elapsed());
qDebug("FindErrorsIndexing: %s", qPrintable(elapsedTime));
}
-void index(QFutureInterface<void> &indexingFuture,
- const ParseParams params)
+void index(QPromise<void> &promise, const ParseParams params)
{
QScopedPointer<Internal::CppSourceProcessor> sourceProcessor(CppModelManager::createSourceProcessor());
sourceProcessor->setFileSizeLimitInMb(params.indexerFileSizeLimitInMb);
@@ -190,7 +188,7 @@ void index(QFutureInterface<void> &indexingFuture,
qCDebug(indexerLog) << "About to index" << files.size() << "files.";
for (int i = 0; i < files.size(); ++i) {
- if (indexingFuture.isCanceled())
+ if (promise.isCanceled())
break;
const QString fileName = files.at(i);
@@ -216,7 +214,7 @@ void index(QFutureInterface<void> &indexingFuture,
sourceProcessor->setHeaderPaths(headerPaths);
sourceProcessor->run(FilePath::fromString(fileName));
- indexingFuture.setProgressValue(files.size() - sourceProcessor->todo().size());
+ promise.setProgressValue(files.size() - sourceProcessor->todo().size());
if (isSourceFile)
sourceProcessor->resetEnvironment();
@@ -224,29 +222,29 @@ void index(QFutureInterface<void> &indexingFuture,
qCDebug(indexerLog) << "Indexing finished.";
}
-void parse(QFutureInterface<void> &indexingFuture, const ParseParams params)
+void parse(QPromise<void> &promise, const ParseParams params)
{
const QSet<QString> &files = params.sourceFiles;
if (files.isEmpty())
return;
- indexingFuture.setProgressRange(0, files.size());
+ promise.setProgressRange(0, files.size());
if (CppIndexingSupport::isFindErrorsIndexingActive())
- indexFindErrors(indexingFuture, params);
+ indexFindErrors(promise, params);
else
- index(indexingFuture, params);
+ index(promise, params);
- indexingFuture.setProgressValue(files.size());
+ promise.setProgressValue(files.size());
CppModelManager::instance()->finishedRefreshingSourceFiles(files);
}
} // anonymous namespace
-void SymbolSearcher::runSearch(QFutureInterface<Core::SearchResultItem> &future)
+void SymbolSearcher::runSearch(QPromise<Core::SearchResultItem> &promise)
{
- future.setProgressRange(0, m_snapshot.size());
- future.setProgressValue(0);
+ promise.setProgressRange(0, m_snapshot.size());
+ promise.setProgressValue(0);
int progress = 0;
SearchSymbols search;
@@ -262,9 +260,8 @@ void SymbolSearcher::runSearch(QFutureInterface<Core::SearchResultItem> &future)
: QRegularExpression::CaseInsensitiveOption));
matcher.optimize();
while (it != m_snapshot.end()) {
- if (future.isPaused())
- future.waitForResume();
- if (future.isCanceled())
+ promise.suspendIfRequested();
+ if (promise.isCanceled())
break;
if (m_fileNames.isEmpty() || m_fileNames.contains(it.value()->filePath().path())) {
QVector<Core::SearchResultItem> resultItems;
@@ -291,15 +288,14 @@ void SymbolSearcher::runSearch(QFutureInterface<Core::SearchResultItem> &future)
return IndexItem::Recurse;
};
search(it.value())->visitAllChildren(filter);
- if (!resultItems.isEmpty())
- future.reportResults(resultItems);
+ for (const Core::SearchResultItem &item : std::as_const(resultItems))
+ promise.addResult(item);
}
++it;
++progress;
- future.setProgressValue(progress);
+ promise.setProgressValue(progress);
}
- if (future.isPaused())
- future.waitForResume();
+ promise.suspendIfRequested();
}
CppIndexingSupport::CppIndexingSupport()
@@ -325,7 +321,7 @@ QFuture<void> CppIndexingSupport::refreshSourceFiles(const QSet<QString> &source
params.workingCopy = mgr->workingCopy();
params.sourceFiles = sourceFiles;
- QFuture<void> result = Utils::runAsync(mgr->sharedThreadPool(), parse, params);
+ QFuture<void> result = Utils::asyncRun(mgr->sharedThreadPool(), parse, params);
m_synchronizer.addFuture(result);
if (mode == CppModelManager::ForcedProgressNotification || sourceFiles.count() > 1) {
diff --git a/src/plugins/cppeditor/cppindexingsupport.h b/src/plugins/cppeditor/cppindexingsupport.h
index 584632a4f00..690c1be9b42 100644
--- a/src/plugins/cppeditor/cppindexingsupport.h
+++ b/src/plugins/cppeditor/cppindexingsupport.h
@@ -44,7 +44,7 @@ public:
};
SymbolSearcher(const SymbolSearcher::Parameters &parameters, const QSet<QString> &fileNames);
- void runSearch(QFutureInterface<Core::SearchResultItem> &future);
+ void runSearch(QPromise<Core::SearchResultItem> &promise);
private:
const CPlusPlus::Snapshot m_snapshot;
diff --git a/src/plugins/cppeditor/cpplocatorfilter.cpp b/src/plugins/cppeditor/cpplocatorfilter.cpp
index 083c5df1b2f..b72bf261c98 100644
--- a/src/plugins/cppeditor/cpplocatorfilter.cpp
+++ b/src/plugins/cppeditor/cpplocatorfilter.cpp
@@ -6,13 +6,11 @@
#include "cppeditorconstants.h"
#include "cppeditortr.h"
-#include <coreplugin/editormanager/editormanager.h>
#include <utils/algorithm.h>
#include <QRegularExpression>
-#include <algorithm>
-#include <numeric>
+using namespace Core;
namespace CppEditor {
@@ -27,10 +25,11 @@ CppLocatorFilter::CppLocatorFilter(CppLocatorData *locatorData)
CppLocatorFilter::~CppLocatorFilter() = default;
-Core::LocatorFilterEntry CppLocatorFilter::filterEntryFromIndexItem(IndexItem::Ptr info)
+LocatorFilterEntry CppLocatorFilter::filterEntryFromIndexItem(IndexItem::Ptr info)
{
const QVariant id = QVariant::fromValue(info);
- Core::LocatorFilterEntry filterEntry(this, info->scopedSymbolName(), id, info->icon());
+ LocatorFilterEntry filterEntry(this, info->scopedSymbolName(), id, info->icon());
+ filterEntry.linkForEditor = {info->filePath(), info->line(), info->column()};
if (info->type() == IndexItem::Class || info->type() == IndexItem::Enum)
filterEntry.extraInfo = info->shortNativeFilePath();
else
@@ -39,10 +38,10 @@ Core::LocatorFilterEntry CppLocatorFilter::filterEntryFromIndexItem(IndexItem::P
return filterEntry;
}
-QList<Core::LocatorFilterEntry> CppLocatorFilter::matchesFor(
- QFutureInterface<Core::LocatorFilterEntry> &future, const QString &entry)
+QList<LocatorFilterEntry> CppLocatorFilter::matchesFor(
+ QFutureInterface<LocatorFilterEntry> &future, const QString &entry)
{
- QList<Core::LocatorFilterEntry> entries[int(MatchLevel::Count)];
+ QList<LocatorFilterEntry> entries[int(MatchLevel::Count)];
const Qt::CaseSensitivity caseSensitivityForPrefix = caseSensitivity(entry);
const IndexItem::ItemType wanted = matchTypes();
@@ -70,7 +69,7 @@ QList<Core::LocatorFilterEntry> CppLocatorFilter::matchesFor(
}
if (match.hasMatch()) {
- Core::LocatorFilterEntry filterEntry = filterEntryFromIndexItem(info);
+ LocatorFilterEntry filterEntry = filterEntryFromIndexItem(info);
// Highlight the matched characters, therefore it may be necessary
// to update the match if the displayName is different from matchString
@@ -82,7 +81,7 @@ QList<Core::LocatorFilterEntry> CppLocatorFilter::matchesFor(
if (matchInParameterList && filterEntry.highlightInfo.startsDisplay.isEmpty()) {
match = regexp.match(filterEntry.extraInfo);
filterEntry.highlightInfo
- = highlightInfo(match, Core::LocatorFilterEntry::HighlightInfo::ExtraInfo);
+ = highlightInfo(match, LocatorFilterEntry::HighlightInfo::ExtraInfo);
} else if (matchOffset > 0) {
for (int &start : filterEntry.highlightInfo.startsDisplay)
start -= matchOffset;
@@ -107,22 +106,10 @@ QList<Core::LocatorFilterEntry> CppLocatorFilter::matchesFor(
for (auto &entry : entries) {
if (entry.size() < 1000)
- Utils::sort(entry, Core::LocatorFilterEntry::compareLexigraphically);
+ Utils::sort(entry, LocatorFilterEntry::compareLexigraphically);
}
- return std::accumulate(std::begin(entries), std::end(entries), QList<Core::LocatorFilterEntry>());
-}
-
-void CppLocatorFilter::accept(const Core::LocatorFilterEntry &selection,
- QString *newText, int *selectionStart, int *selectionLength) const
-{
- Q_UNUSED(newText)
- Q_UNUSED(selectionStart)
- Q_UNUSED(selectionLength)
- IndexItem::Ptr info = qvariant_cast<IndexItem::Ptr>(selection.internalData);
- Core::EditorManager::openEditorAt({info->filePath(), info->line(), info->column()},
- {},
- Core::EditorManager::AllowExternalEditor);
+ return std::accumulate(std::begin(entries), std::end(entries), QList<LocatorFilterEntry>());
}
CppClassesFilter::CppClassesFilter(CppLocatorData *locatorData)
@@ -136,10 +123,11 @@ CppClassesFilter::CppClassesFilter(CppLocatorData *locatorData)
CppClassesFilter::~CppClassesFilter() = default;
-Core::LocatorFilterEntry CppClassesFilter::filterEntryFromIndexItem(IndexItem::Ptr info)
+LocatorFilterEntry CppClassesFilter::filterEntryFromIndexItem(IndexItem::Ptr info)
{
const QVariant id = QVariant::fromValue(info);
- Core::LocatorFilterEntry filterEntry(this, info->symbolName(), id, info->icon());
+ LocatorFilterEntry filterEntry(this, info->symbolName(), id, info->icon());
+ filterEntry.linkForEditor = {info->filePath(), info->line(), info->column()};
filterEntry.extraInfo = info->symbolScope().isEmpty()
? info->shortNativeFilePath()
: info->symbolScope();
@@ -158,7 +146,7 @@ CppFunctionsFilter::CppFunctionsFilter(CppLocatorData *locatorData)
CppFunctionsFilter::~CppFunctionsFilter() = default;
-Core::LocatorFilterEntry CppFunctionsFilter::filterEntryFromIndexItem(IndexItem::Ptr info)
+LocatorFilterEntry CppFunctionsFilter::filterEntryFromIndexItem(IndexItem::Ptr info)
{
const QVariant id = QVariant::fromValue(info);
@@ -171,7 +159,8 @@ Core::LocatorFilterEntry CppFunctionsFilter::filterEntryFromIndexItem(IndexItem:
extraInfo.append(" (" + info->filePath().fileName() + ')');
}
- Core::LocatorFilterEntry filterEntry(this, name + info->symbolType(), id, info->icon());
+ LocatorFilterEntry filterEntry(this, name + info->symbolType(), id, info->icon());
+ filterEntry.linkForEditor = {info->filePath(), info->line(), info->column()};
filterEntry.extraInfo = extraInfo;
return filterEntry;
diff --git a/src/plugins/cppeditor/cpplocatorfilter.h b/src/plugins/cppeditor/cpplocatorfilter.h
index 9770afe21b4..0bfa8a2d2da 100644
--- a/src/plugins/cppeditor/cpplocatorfilter.h
+++ b/src/plugins/cppeditor/cpplocatorfilter.h
@@ -5,7 +5,6 @@
#include "cppeditor_global.h"
#include "cpplocatordata.h"
-#include "searchsymbols.h"
#include <coreplugin/locator/ilocatorfilter.h>
@@ -21,9 +20,6 @@ public:
QList<Core::LocatorFilterEntry> matchesFor(QFutureInterface<Core::LocatorFilterEntry> &future,
const QString &entry) override;
- void accept(const Core::LocatorFilterEntry &selection,
- QString *newText, int *selectionStart, int *selectionLength) const override;
-
protected:
virtual IndexItem::ItemType matchTypes() const { return IndexItem::All; }
virtual Core::LocatorFilterEntry filterEntryFromIndexItem(IndexItem::Ptr info);
diff --git a/src/plugins/cppeditor/cpplocatorfilter_test.cpp b/src/plugins/cppeditor/cpplocatorfilter_test.cpp
index 598a6e85640..5b443ad42ce 100644
--- a/src/plugins/cppeditor/cpplocatorfilter_test.cpp
+++ b/src/plugins/cppeditor/cpplocatorfilter_test.cpp
@@ -319,7 +319,6 @@ void LocatorFilterTest::testCurrentDocumentFilter()
ResultData("functionDeclaredOnly()", "MyClass"),
ResultData("functionDefinedInClass(bool, int)", "MyClass"),
ResultData("functionDefinedOutSideClass(char)", "MyClass"),
- ResultData("functionDefinedOutSideClass(char)", "MyClass"),
ResultData("int myVariable", "MyNamespace"),
ResultData("myFunction(bool, int)", "MyNamespace"),
ResultData("MyEnum", "MyNamespace"),
@@ -332,9 +331,6 @@ void LocatorFilterTest::testCurrentDocumentFilter()
ResultData("functionDefinedOutSideClass(char)", "MyNamespace::MyClass"),
ResultData("functionDefinedOutSideClassAndNamespace(float)",
"MyNamespace::MyClass"),
- ResultData("functionDefinedOutSideClass(char)", "MyNamespace::MyClass"),
- ResultData("functionDefinedOutSideClassAndNamespace(float)",
- "MyNamespace::MyClass"),
ResultData("int myVariable", "<anonymous namespace>"),
ResultData("myFunction(bool, int)", "<anonymous namespace>"),
ResultData("MyEnum", "<anonymous namespace>"),
@@ -345,7 +341,6 @@ void LocatorFilterTest::testCurrentDocumentFilter()
ResultData("functionDeclaredOnly()", "<anonymous namespace>::MyClass"),
ResultData("functionDefinedInClass(bool, int)", "<anonymous namespace>::MyClass"),
ResultData("functionDefinedOutSideClass(char)", "<anonymous namespace>::MyClass"),
- ResultData("functionDefinedOutSideClass(char)", "<anonymous namespace>::MyClass"),
ResultData("main()", ""),
};
diff --git a/src/plugins/cppeditor/cppmodelmanager.cpp b/src/plugins/cppeditor/cppmodelmanager.cpp
index 8fc44622ab2..4f482e6c6d1 100644
--- a/src/plugins/cppeditor/cppmodelmanager.cpp
+++ b/src/plugins/cppeditor/cppmodelmanager.cpp
@@ -37,9 +37,11 @@
#include <coreplugin/progressmanager/futureprogress.h>
#include <coreplugin/progressmanager/progressmanager.h>
#include <coreplugin/vcsmanager.h>
+
#include <cplusplus/ASTPath.h>
#include <cplusplus/ExpressionUnderCursor.h>
#include <cplusplus/TypeOfExpression.h>
+
#include <extensionsystem/pluginmanager.h>
#include <projectexplorer/buildconfiguration.h>
@@ -49,6 +51,7 @@
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectexplorerconstants.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/projectmacro.h>
#include <projectexplorer/projectnodes.h>
#include <projectexplorer/projecttree.h>
@@ -90,6 +93,7 @@
#include <sstream>
#endif
+using namespace Core;
using namespace CPlusPlus;
using namespace ProjectExplorer;
using namespace Utils;
@@ -155,7 +159,7 @@ public:
class CppModelManagerPrivate
{
public:
- void setupWatcher(const QFuture<void> &future, ProjectExplorer::Project *project,
+ void setupWatcher(const QFuture<void> &future, Project *project,
ProjectData *projectData, CppModelManager *q);
// Snapshot
@@ -164,15 +168,15 @@ public:
// Project integration
QReadWriteLock m_projectLock;
- QHash<ProjectExplorer::Project *, ProjectData> m_projectData;
- QMap<Utils::FilePath, QList<ProjectPart::ConstPtr> > m_fileToProjectParts;
+ QHash<Project *, ProjectData> m_projectData;
+ QMap<FilePath, QList<ProjectPart::ConstPtr> > m_fileToProjectParts;
QMap<QString, ProjectPart::ConstPtr> m_projectPartIdToProjectProjectPart;
// The members below are cached/(re)calculated from the projects and/or their project parts
bool m_dirty;
- Utils::FilePaths m_projectFiles;
- ProjectExplorer::HeaderPaths m_headerPaths;
- ProjectExplorer::Macros m_definedMacros;
+ FilePaths m_projectFiles;
+ HeaderPaths m_headerPaths;
+ Macros m_definedMacros;
// Editor integration
mutable QMutex m_cppEditorDocumentsMutex;
@@ -201,12 +205,12 @@ public:
QTimer m_fallbackProjectPartTimer;
CppLocatorData m_locatorData;
- std::unique_ptr<Core::ILocatorFilter> m_locatorFilter;
- std::unique_ptr<Core::ILocatorFilter> m_classesFilter;
- std::unique_ptr<Core::ILocatorFilter> m_includesFilter;
- std::unique_ptr<Core::ILocatorFilter> m_functionsFilter;
- std::unique_ptr<Core::IFindFilter> m_symbolsFindFilter;
- std::unique_ptr<Core::ILocatorFilter> m_currentDocumentFilter;
+ std::unique_ptr<ILocatorFilter> m_locatorFilter;
+ std::unique_ptr<ILocatorFilter> m_classesFilter;
+ std::unique_ptr<ILocatorFilter> m_includesFilter;
+ std::unique_ptr<ILocatorFilter> m_functionsFilter;
+ std::unique_ptr<IFindFilter> m_symbolsFindFilter;
+ std::unique_ptr<ILocatorFilter> m_currentDocumentFilter;
QList<Document::DiagnosticMessage> m_diagnosticMessages;
};
@@ -338,7 +342,7 @@ void CppModelManager::findUsages(const CursorInEditor &data, Backend backend)
void CppModelManager::switchHeaderSource(bool inNextSplit, Backend backend)
{
- const Core::IDocument *currentDocument = Core::EditorManager::currentDocument();
+ const IDocument *currentDocument = EditorManager::currentDocument();
QTC_ASSERT(currentDocument, return);
instance()->modelManagerSupport(backend)->switchHeaderSource(currentDocument->filePath(),
inNextSplit);
@@ -346,16 +350,16 @@ void CppModelManager::switchHeaderSource(bool inNextSplit, Backend backend)
void CppModelManager::showPreprocessedFile(bool inNextSplit)
{
- const Core::IDocument *doc = Core::EditorManager::currentDocument();
+ const IDocument *doc = EditorManager::currentDocument();
QTC_ASSERT(doc, return);
static const auto showError = [](const QString &reason) {
- Core::MessageManager::writeFlashing(Tr::tr("Cannot show preprocessed file: %1")
- .arg(reason));
+ MessageManager::writeFlashing(Tr::tr("Cannot show preprocessed file: %1")
+ .arg(reason));
};
static const auto showFallbackWarning = [](const QString &reason) {
- Core::MessageManager::writeSilently(
- Tr::tr("Falling back to built-in preprocessor: %1").arg(reason));
+ MessageManager::writeSilently(Tr::tr("Falling back to built-in preprocessor: %1")
+ .arg(reason));
};
static const auto saveAndOpen = [](const FilePath &filePath, const QByteArray &contents,
bool inNextSplit) {
@@ -469,24 +473,24 @@ class FindUnusedActionsEnabledSwitcher
{
public:
FindUnusedActionsEnabledSwitcher()
- : actions{Core::ActionManager::command("CppTools.FindUnusedFunctions"),
- Core::ActionManager::command("CppTools.FindUnusedFunctionsInSubProject")}
+ : actions{ActionManager::command("CppTools.FindUnusedFunctions"),
+ ActionManager::command("CppTools.FindUnusedFunctionsInSubProject")}
{
- for (Core::Command * const action : actions)
+ for (Command * const action : actions)
action->action()->setEnabled(false);
}
~FindUnusedActionsEnabledSwitcher()
{
- for (Core::Command * const action : actions)
+ for (Command * const action : actions)
action->action()->setEnabled(true);
}
private:
- const QList<Core::Command *> actions;
+ const QList<Command *> actions;
};
using FindUnusedActionsEnabledSwitcherPtr = std::shared_ptr<FindUnusedActionsEnabledSwitcher>;
static void checkNextFunctionForUnused(
- const QPointer<Core::SearchResult> &search,
+ const QPointer<SearchResult> &search,
const std::shared_ptr<QFutureInterface<bool>> &findRefsFuture,
const FindUnusedActionsEnabledSwitcherPtr &actionsSwitcher)
{
@@ -531,30 +535,28 @@ void CppModelManager::findUnusedFunctions(const FilePath &folder)
const auto actionsSwitcher = std::make_shared<FindUnusedActionsEnabledSwitcher>();
// Step 1: Employ locator to find all functions
- Core::ILocatorFilter *const functionsFilter
- = Utils::findOrDefault(Core::ILocatorFilter::allLocatorFilters(),
- Utils::equal(&Core::ILocatorFilter::id,
+ ILocatorFilter *const functionsFilter
+ = Utils::findOrDefault(ILocatorFilter::allLocatorFilters(),
+ Utils::equal(&ILocatorFilter::id,
Id(Constants::FUNCTIONS_FILTER_ID)));
QTC_ASSERT(functionsFilter, return);
- const QPointer<Core::SearchResult> search
- = Core::SearchResultWindow::instance()
- ->startNewSearch(Tr::tr("Find Unused Functions"),
+ const QPointer<SearchResult> search
+ = SearchResultWindow::instance()->startNewSearch(Tr::tr("Find Unused Functions"),
{},
{},
- Core::SearchResultWindow::SearchOnly,
- Core::SearchResultWindow::PreserveCaseDisabled,
+ SearchResultWindow::SearchOnly,
+ SearchResultWindow::PreserveCaseDisabled,
"CppEditor");
- connect(search, &Core::SearchResult::activated, [](const Core::SearchResultItem &item) {
- Core::EditorManager::openEditorAtSearchResult(item);
+ connect(search, &SearchResult::activated, [](const SearchResultItem &item) {
+ EditorManager::openEditorAtSearchResult(item);
});
- Core::SearchResultWindow::instance()->popup(Core::IOutputPane::ModeSwitch
- | Core::IOutputPane::WithFocus);
- const auto locatorWatcher = new QFutureWatcher<Core::LocatorFilterEntry>(search);
+ SearchResultWindow::instance()->popup(IOutputPane::ModeSwitch | IOutputPane::WithFocus);
+ const auto locatorWatcher = new QFutureWatcher<LocatorFilterEntry>(search);
functionsFilter->prepareSearch({});
- connect(search, &Core::SearchResult::canceled, locatorWatcher, [locatorWatcher] {
+ connect(search, &SearchResult::canceled, locatorWatcher, [locatorWatcher] {
locatorWatcher->cancel();
});
- connect(locatorWatcher, &QFutureWatcher<Core::LocatorFilterEntry>::finished, search,
+ connect(locatorWatcher, &QFutureWatcher<LocatorFilterEntry>::finished, search,
[locatorWatcher, search, folder, actionsSwitcher] {
locatorWatcher->deleteLater();
if (locatorWatcher->isCanceled()) {
@@ -563,22 +565,19 @@ void CppModelManager::findUnusedFunctions(const FilePath &folder)
}
Links links;
for (int i = 0; i < locatorWatcher->future().resultCount(); ++i) {
- const Core::LocatorFilterEntry &entry = locatorWatcher->resultAt(i);
+ const LocatorFilterEntry &entry = locatorWatcher->resultAt(i);
static const QStringList prefixBlacklist{"main(", "~", "qHash(", "begin()", "end()",
"cbegin()", "cend()", "constBegin()", "constEnd()"};
if (Utils::anyOf(prefixBlacklist, [&entry](const QString &prefix) {
return entry.displayName.startsWith(prefix); })) {
continue;
}
- Link link;
- if (entry.internalData.canConvert<Link>())
- link = qvariant_cast<Link>(entry.internalData);
- else if (const auto item = qvariant_cast<IndexItem::Ptr>(entry.internalData))
- link = Link(item->filePath(), item->line(), item->column());
-
+ if (!entry.linkForEditor)
+ continue;
+ const Link link = *entry.linkForEditor;
if (link.hasValidTarget() && link.targetFilePath.isReadableFile()
&& (folder.isEmpty() || link.targetFilePath.isChildOf(folder))
- && SessionManager::projectForFile(link.targetFilePath)) {
+ && ProjectManager::projectForFile(link.targetFilePath)) {
links << link;
}
}
@@ -593,12 +592,11 @@ void CppModelManager::findUnusedFunctions(const FilePath &folder)
}));
search->setUserData(remainingAndActiveLinks);
const auto findRefsFuture = std::make_shared<QFutureInterface<bool>>();
- Core::FutureProgress *const progress
- = Core::ProgressManager::addTask(findRefsFuture->future(),
- Tr::tr("Finding Unused Functions"),
- "CppEditor.FindUnusedFunctions");
+ FutureProgress *const progress = ProgressManager::addTask(findRefsFuture->future(),
+ Tr::tr("Finding Unused Functions"),
+ "CppEditor.FindUnusedFunctions");
connect(progress,
- &Core::FutureProgress::canceled,
+ &FutureProgress::canceled,
search,
[search, future = std::weak_ptr<QFutureInterface<bool>>(findRefsFuture)] {
search->finishSearch(true);
@@ -608,7 +606,7 @@ void CppModelManager::findUnusedFunctions(const FilePath &folder)
}
});
findRefsFuture->setProgressRange(0, links.size());
- connect(search, &Core::SearchResult::canceled, [findRefsFuture] {
+ connect(search, &SearchResult::canceled, [findRefsFuture] {
findRefsFuture->cancel();
findRefsFuture->reportFinished();
});
@@ -620,12 +618,12 @@ void CppModelManager::findUnusedFunctions(const FilePath &folder)
checkNextFunctionForUnused(search, findRefsFuture, actionsSwitcher);
});
locatorWatcher->setFuture(
- Utils::runAsync([functionsFilter](QFutureInterface<Core::LocatorFilterEntry> &future) {
+ Utils::runAsync([functionsFilter](QFutureInterface<LocatorFilterEntry> &future) {
future.reportResults(functionsFilter->matchesFor(future, {}));
}));
}
-void CppModelManager::checkForUnusedSymbol(Core::SearchResult *search,
+void CppModelManager::checkForUnusedSymbol(SearchResult *search,
const Link &link,
CPlusPlus::Symbol *symbol,
const CPlusPlus::LookupContext &context,
@@ -785,62 +783,62 @@ static void setFilter(std::unique_ptr<FilterClass> &filter,
filter = std::move(newFilter);
}
-void CppModelManager::setLocatorFilter(std::unique_ptr<Core::ILocatorFilter> &&filter)
+void CppModelManager::setLocatorFilter(std::unique_ptr<ILocatorFilter> &&filter)
{
setFilter(d->m_locatorFilter, std::move(filter));
}
-void CppModelManager::setClassesFilter(std::unique_ptr<Core::ILocatorFilter> &&filter)
+void CppModelManager::setClassesFilter(std::unique_ptr<ILocatorFilter> &&filter)
{
setFilter(d->m_classesFilter, std::move(filter));
}
-void CppModelManager::setIncludesFilter(std::unique_ptr<Core::ILocatorFilter> &&filter)
+void CppModelManager::setIncludesFilter(std::unique_ptr<ILocatorFilter> &&filter)
{
setFilter(d->m_includesFilter, std::move(filter));
}
-void CppModelManager::setFunctionsFilter(std::unique_ptr<Core::ILocatorFilter> &&filter)
+void CppModelManager::setFunctionsFilter(std::unique_ptr<ILocatorFilter> &&filter)
{
setFilter(d->m_functionsFilter, std::move(filter));
}
-void CppModelManager::setSymbolsFindFilter(std::unique_ptr<Core::IFindFilter> &&filter)
+void CppModelManager::setSymbolsFindFilter(std::unique_ptr<IFindFilter> &&filter)
{
setFilter(d->m_symbolsFindFilter, std::move(filter));
}
-void CppModelManager::setCurrentDocumentFilter(std::unique_ptr<Core::ILocatorFilter> &&filter)
+void CppModelManager::setCurrentDocumentFilter(std::unique_ptr<ILocatorFilter> &&filter)
{
setFilter(d->m_currentDocumentFilter, std::move(filter));
}
-Core::ILocatorFilter *CppModelManager::locatorFilter() const
+ILocatorFilter *CppModelManager::locatorFilter() const
{
return d->m_locatorFilter.get();
}
-Core::ILocatorFilter *CppModelManager::classesFilter() const
+ILocatorFilter *CppModelManager::classesFilter() const
{
return d->m_classesFilter.get();
}
-Core::ILocatorFilter *CppModelManager::includesFilter() const
+ILocatorFilter *CppModelManager::includesFilter() const
{
return d->m_includesFilter.get();
}
-Core::ILocatorFilter *CppModelManager::functionsFilter() const
+ILocatorFilter *CppModelManager::functionsFilter() const
{
return d->m_functionsFilter.get();
}
-Core::IFindFilter *CppModelManager::symbolsFindFilter() const
+IFindFilter *CppModelManager::symbolsFindFilter() const
{
return d->m_symbolsFindFilter.get();
}
-Core::ILocatorFilter *CppModelManager::currentDocumentFilter() const
+ILocatorFilter *CppModelManager::currentDocumentFilter() const
{
return d->m_currentDocumentFilter.get();
}
@@ -880,7 +878,7 @@ CppModelManager *CppModelManager::instance()
void CppModelManager::registerJsExtension()
{
- Core::JsExpander::registerGlobalObject("Cpp", [this] {
+ JsExpander::registerGlobalObject("Cpp", [this] {
return new CppToolsJsExtension(&d->m_locatorData);
});
}
@@ -888,9 +886,9 @@ void CppModelManager::registerJsExtension()
void CppModelManager::initCppTools()
{
// Objects
- connect(Core::VcsManager::instance(), &Core::VcsManager::repositoryChanged,
+ connect(VcsManager::instance(), &VcsManager::repositoryChanged,
this, &CppModelManager::updateModifiedSourceFiles);
- connect(Core::DocumentManager::instance(), &Core::DocumentManager::filesChangedInternally,
+ connect(DocumentManager::instance(), &DocumentManager::filesChangedInternally,
[this](const FilePaths &filePaths) {
updateSourceFiles(toSet(filePaths));
});
@@ -924,7 +922,7 @@ CppModelManager::CppModelManager()
d->m_enableGC = true;
// Visual C++ has 1MiB, macOSX has 512KiB
- if (Utils::HostOsInfo::isWindowsHost() || Utils::HostOsInfo::isMacHost())
+ if (HostOsInfo::isWindowsHost() || HostOsInfo::isMacHost())
d->m_threadPool.setStackSize(2 * 1024 * 1024);
qRegisterMetaType<QSet<QString> >();
@@ -940,23 +938,23 @@ CppModelManager::CppModelManager()
d->m_delayedGcTimer.setSingleShot(true);
connect(&d->m_delayedGcTimer, &QTimer::timeout, this, &CppModelManager::GC);
- auto sessionManager = ProjectExplorer::SessionManager::instance();
- connect(sessionManager, &ProjectExplorer::SessionManager::projectAdded,
+ auto projectManager = ProjectManager::instance();
+ connect(projectManager, &ProjectManager::projectAdded,
this, &CppModelManager::onProjectAdded);
- connect(sessionManager, &ProjectExplorer::SessionManager::aboutToRemoveProject,
+ connect(projectManager, &ProjectManager::aboutToRemoveProject,
this, &CppModelManager::onAboutToRemoveProject);
- connect(sessionManager, &ProjectExplorer::SessionManager::aboutToLoadSession,
+ connect(SessionManager::instance(), &SessionManager::aboutToLoadSession,
this, &CppModelManager::onAboutToLoadSession);
- connect(sessionManager, &ProjectExplorer::SessionManager::startupProjectChanged,
+ connect(projectManager, &ProjectManager::startupProjectChanged,
this, &CppModelManager::onActiveProjectChanged);
- connect(Core::EditorManager::instance(), &Core::EditorManager::currentEditorChanged,
+ connect(EditorManager::instance(), &EditorManager::currentEditorChanged,
this, &CppModelManager::onCurrentEditorChanged);
- connect(Core::DocumentManager::instance(), &Core::DocumentManager::allDocumentsRenamed,
+ connect(DocumentManager::instance(), &DocumentManager::allDocumentsRenamed,
this, &CppModelManager::renameIncludes);
- connect(Core::ICore::instance(), &Core::ICore::coreAboutToClose,
+ connect(ICore::instance(), &ICore::coreAboutToClose,
this, &CppModelManager::onCoreAboutToClose);
d->m_fallbackProjectPartTimer.setSingleShot(true);
@@ -1002,7 +1000,7 @@ Document::Ptr CppModelManager::document(const FilePath &filePath) const
/// Replace the document in the snapshot.
///
-/// \returns true if successful, false if the new document is out-dated.
+/// Returns true if successful, false if the new document is out-dated.
bool CppModelManager::replaceDocument(Document::Ptr newDoc)
{
QMutexLocker locker(&d->m_snapshotMutex);
@@ -1041,13 +1039,13 @@ FilePaths CppModelManager::internalProjectFiles() const
return files;
}
-ProjectExplorer::HeaderPaths CppModelManager::internalHeaderPaths() const
+HeaderPaths CppModelManager::internalHeaderPaths() const
{
- ProjectExplorer::HeaderPaths headerPaths;
+ HeaderPaths headerPaths;
for (const ProjectData &projectData: std::as_const(d->m_projectData)) {
for (const ProjectPart::ConstPtr &part : projectData.projectInfo->projectParts()) {
- for (const ProjectExplorer::HeaderPath &path : part->headerPaths) {
- ProjectExplorer::HeaderPath hp(QDir::cleanPath(path.path), path.type);
+ for (const HeaderPath &path : part->headerPaths) {
+ HeaderPath hp(QDir::cleanPath(path.path), path.type);
if (!headerPaths.contains(hp))
headerPaths.push_back(std::move(hp));
}
@@ -1056,8 +1054,7 @@ ProjectExplorer::HeaderPaths CppModelManager::internalHeaderPaths() const
return headerPaths;
}
-static void addUnique(const ProjectExplorer::Macros &newMacros,
- ProjectExplorer::Macros &macros,
+static void addUnique(const Macros &newMacros, Macros &macros,
QSet<ProjectExplorer::Macro> &alreadyIn)
{
for (const ProjectExplorer::Macro &macro : newMacros) {
@@ -1068,9 +1065,9 @@ static void addUnique(const ProjectExplorer::Macros &newMacros,
}
}
-ProjectExplorer::Macros CppModelManager::internalDefinedMacros() const
+Macros CppModelManager::internalDefinedMacros() const
{
- ProjectExplorer::Macros macros;
+ Macros macros;
QSet<ProjectExplorer::Macro> alreadyIn;
for (const ProjectData &projectData : std::as_const(d->m_projectData)) {
for (const ProjectPart::ConstPtr &part : projectData.projectInfo->projectParts()) {
@@ -1093,7 +1090,7 @@ void CppModelManager::dumpModelManagerConfiguration(const QString &logFileId)
dumper.dumpSnapshot(globalSnapshot, globalSnapshotTitle, /*isGlobalSnapshot=*/ true);
dumper.dumpWorkingCopy(workingCopy());
dumper.dumpMergedEntities(headerPaths(),
- ProjectExplorer:: Macro::toByteArray(definedMacros()));
+ ProjectExplorer::Macro::toByteArray(definedMacros()));
}
QSet<AbstractEditorSupport *> CppModelManager::abstractEditorSupports() const
@@ -1268,8 +1265,8 @@ static QSet<QString> filteredFilesRemoved(const QSet<QString> &files, int fileSi
const QString msg = Tr::tr("C++ Indexer: Skipping file \"%1\" "
"because its path matches the ignore pattern.")
.arg(filePath.displayName());
- QMetaObject::invokeMethod(Core::MessageManager::instance(),
- [msg]() { Core::MessageManager::writeSilently(msg); });
+ QMetaObject::invokeMethod(MessageManager::instance(),
+ [msg] { MessageManager::writeSilently(msg); });
skip = true;
break;
}
@@ -1304,7 +1301,7 @@ ProjectInfoList CppModelManager::projectInfos() const
[](const ProjectData &d) { return d.projectInfo; });
}
-ProjectInfo::ConstPtr CppModelManager::projectInfo(ProjectExplorer::Project *project) const
+ProjectInfo::ConstPtr CppModelManager::projectInfo(Project *project) const
{
QReadLocker locker(&d->m_projectLock);
return d->m_projectData.value(project).projectInfo;
@@ -1424,8 +1421,7 @@ void CppModelManager::recalculateProjectPartMappings()
d->m_symbolFinder.clearCache();
}
-void CppModelManagerPrivate::setupWatcher(const QFuture<void> &future,
- ProjectExplorer::Project *project,
+void CppModelManagerPrivate::setupWatcher(const QFuture<void> &future, Project *project,
ProjectData *projectData, CppModelManager *q)
{
projectData->indexer = new QFutureWatcher<void>(q);
@@ -1446,10 +1442,10 @@ void CppModelManagerPrivate::setupWatcher(const QFuture<void> &future,
void CppModelManager::updateCppEditorDocuments(bool projectsUpdated) const
{
// Refresh visible documents
- QSet<Core::IDocument *> visibleCppEditorDocuments;
- const QList<Core::IEditor *> editors = Core::EditorManager::visibleEditors();
- for (Core::IEditor *editor: editors) {
- if (Core::IDocument *document = editor->document()) {
+ QSet<IDocument *> visibleCppEditorDocuments;
+ const QList<IEditor *> editors = EditorManager::visibleEditors();
+ for (IEditor *editor: editors) {
+ if (IDocument *document = editor->document()) {
const FilePath filePath = document->filePath();
if (CppEditorDocumentHandle *theCppEditorDocument = cppEditorDocument(filePath)) {
visibleCppEditorDocuments.insert(document);
@@ -1459,10 +1455,10 @@ void CppModelManager::updateCppEditorDocuments(bool projectsUpdated) const
}
// Mark invisible documents dirty
- QSet<Core::IDocument *> invisibleCppEditorDocuments
- = Utils::toSet(Core::DocumentModel::openedDocuments());
+ QSet<IDocument *> invisibleCppEditorDocuments
+ = Utils::toSet(DocumentModel::openedDocuments());
invisibleCppEditorDocuments.subtract(visibleCppEditorDocuments);
- for (Core::IDocument *document : std::as_const(invisibleCppEditorDocuments)) {
+ for (IDocument *document : std::as_const(invisibleCppEditorDocuments)) {
const FilePath filePath = document->filePath();
if (CppEditorDocumentHandle *theCppEditorDocument = cppEditorDocument(filePath)) {
const CppEditorDocumentHandle::RefreshReason refreshReason = projectsUpdated
@@ -1483,7 +1479,7 @@ QFuture<void> CppModelManager::updateProjectInfo(const ProjectInfo::ConstPtr &ne
QStringList removedProjectParts;
bool filesRemoved = false;
- ProjectExplorer::Project * const project = projectForProjectInfo(*newProjectInfo);
+ Project * const project = projectForProjectInfo(*newProjectInfo);
if (!project)
return {};
@@ -1589,20 +1585,20 @@ ProjectPart::ConstPtr CppModelManager::projectPartForId(const QString &projectPa
return d->m_projectPartIdToProjectProjectPart.value(projectPartId);
}
-QList<ProjectPart::ConstPtr> CppModelManager::projectPart(const Utils::FilePath &fileName) const
+QList<ProjectPart::ConstPtr> CppModelManager::projectPart(const FilePath &fileName) const
{
QReadLocker locker(&d->m_projectLock);
return d->m_fileToProjectParts.value(fileName.canonicalPath());
}
QList<ProjectPart::ConstPtr> CppModelManager::projectPartFromDependencies(
- const Utils::FilePath &fileName) const
+ const FilePath &fileName) const
{
QSet<ProjectPart::ConstPtr> parts;
- const Utils::FilePaths deps = snapshot().filesDependingOn(fileName);
+ const FilePaths deps = snapshot().filesDependingOn(fileName);
QReadLocker locker(&d->m_projectLock);
- for (const Utils::FilePath &dep : deps)
+ for (const FilePath &dep : deps)
parts.unite(Utils::toSet(d->m_fileToProjectParts.value(dep.canonicalPath())));
return parts.values();
@@ -1614,7 +1610,7 @@ ProjectPart::ConstPtr CppModelManager::fallbackProjectPart()
return d->m_fallbackProjectPart;
}
-bool CppModelManager::isCppEditor(Core::IEditor *editor)
+bool CppModelManager::isCppEditor(IEditor *editor)
{
return editor->context().contains(ProjectExplorer::Constants::CXX_LANGUAGE_ID);
}
@@ -1647,7 +1643,7 @@ void CppModelManager::emitAbstractEditorSupportRemoved(const QString &filePath)
emit abstractEditorSupportRemoved(filePath);
}
-void CppModelManager::onProjectAdded(ProjectExplorer::Project *)
+void CppModelManager::onProjectAdded(Project *)
{
QWriteLocker locker(&d->m_projectLock);
d->m_dirty = true;
@@ -1667,7 +1663,7 @@ static QStringList removedProjectParts(const QStringList &before, const QStringL
return Utils::toList(b);
}
-void CppModelManager::onAboutToRemoveProject(ProjectExplorer::Project *project)
+void CppModelManager::onAboutToRemoveProject(Project *project)
{
QStringList idsOfRemovedProjectParts;
@@ -1689,7 +1685,7 @@ void CppModelManager::onAboutToRemoveProject(ProjectExplorer::Project *project)
delayedGC();
}
-void CppModelManager::onActiveProjectChanged(ProjectExplorer::Project *project)
+void CppModelManager::onActiveProjectChanged(Project *project)
{
if (!project)
return; // Last project closed.
@@ -1711,7 +1707,7 @@ void CppModelManager::onSourceFilesRefreshed() const
}
}
-void CppModelManager::onCurrentEditorChanged(Core::IEditor *editor)
+void CppModelManager::onCurrentEditorChanged(IEditor *editor)
{
if (!editor || !editor->document())
return;
@@ -1752,7 +1748,7 @@ QSet<QString> CppModelManager::dependingInternalTargets(const FilePath &file) co
return result;
}
-QSet<QString> CppModelManager::internalTargets(const Utils::FilePath &filePath) const
+QSet<QString> CppModelManager::internalTargets(const FilePath &filePath) const
{
const QList<ProjectPart::ConstPtr> projectParts = projectPart(filePath);
// if we have no project parts it's most likely a header with declarations only and CMake based
@@ -1761,14 +1757,13 @@ QSet<QString> CppModelManager::internalTargets(const Utils::FilePath &filePath)
QSet<QString> targets;
for (const ProjectPart::ConstPtr &part : projectParts) {
targets.insert(part->buildSystemTarget);
- if (part->buildTargetType != ProjectExplorer::BuildTargetType::Executable)
+ if (part->buildTargetType != BuildTargetType::Executable)
targets.unite(dependingInternalTargets(filePath));
}
return targets;
}
-void CppModelManager::renameIncludes(const Utils::FilePath &oldFilePath,
- const Utils::FilePath &newFilePath)
+void CppModelManager::renameIncludes(const FilePath &oldFilePath, const FilePath &newFilePath)
{
if (oldFilePath.isEmpty() || newFilePath.isEmpty())
return;
@@ -1815,7 +1810,7 @@ void CppModelManager::renameIncludes(const Utils::FilePath &oldFilePath,
const QTextBlock &block = file->document()->findBlockByNumber(loc.second - 1);
const int replaceStart = block.text().indexOf(oldFileName);
if (replaceStart > -1) {
- Utils::ChangeSet changeSet;
+ ChangeSet changeSet;
changeSet.replace(block.position() + replaceStart,
block.position() + replaceStart + oldFileName.length(),
newFileName);
@@ -1843,13 +1838,13 @@ static const char *belongingClassName(const Function *function)
return nullptr;
}
-QSet<QString> CppModelManager::symbolsInFiles(const QSet<Utils::FilePath> &files) const
+QSet<QString> CppModelManager::symbolsInFiles(const QSet<FilePath> &files) const
{
QSet<QString> uniqueSymbols;
const Snapshot cppSnapShot = snapshot();
// Iterate over the files and get interesting symbols
- for (const Utils::FilePath &file : files) {
+ for (const FilePath &file : files) {
// Add symbols from the C++ code model
const CPlusPlus::Document::Ptr doc = cppSnapShot.document(file);
if (!doc.isNull() && doc->control()) {
@@ -1881,7 +1876,7 @@ QSet<QString> CppModelManager::symbolsInFiles(const QSet<Utils::FilePath> &files
void CppModelManager::onCoreAboutToClose()
{
- Core::ProgressManager::cancelTasks(Constants::TASK_INDEX);
+ ProgressManager::cancelTasks(Constants::TASK_INDEX);
d->m_enableGC = false;
}
@@ -1891,27 +1886,27 @@ void CppModelManager::setupFallbackProjectPart()
RawProjectPart rpp;
rpp.setMacros(definedMacros());
rpp.setHeaderPaths(headerPaths());
- rpp.setQtVersion(Utils::QtMajorVersion::Qt5);
+ rpp.setQtVersion(QtMajorVersion::Qt5);
// Do not activate ObjectiveCExtensions since this will lead to the
// "objective-c++" language option for a project-less *.cpp file.
- Utils::LanguageExtensions langExtensions = Utils::LanguageExtension::All;
- langExtensions &= ~Utils::LanguageExtensions(Utils::LanguageExtension::ObjectiveC);
+ LanguageExtensions langExtensions = LanguageExtension::All;
+ langExtensions &= ~LanguageExtensions(LanguageExtension::ObjectiveC);
// TODO: Use different fallback toolchain for different kinds of files?
const Kit * const defaultKit = KitManager::isLoaded() ? KitManager::defaultKit() : nullptr;
const ToolChain * const defaultTc = defaultKit
? ToolChainKitAspect::cxxToolChain(defaultKit) : nullptr;
if (defaultKit && defaultTc) {
- Utils::FilePath sysroot = SysRootKitAspect::sysRoot(defaultKit);
+ FilePath sysroot = SysRootKitAspect::sysRoot(defaultKit);
if (sysroot.isEmpty())
- sysroot = Utils::FilePath::fromString(defaultTc->sysRoot());
+ sysroot = FilePath::fromString(defaultTc->sysRoot());
Utils::Environment env = defaultKit->buildEnvironment();
tcInfo = ToolChainInfo(defaultTc, sysroot, env);
const auto macroInspectionWrapper = [runner = tcInfo.macroInspectionRunner](
const QStringList &flags) {
ToolChain::MacroInspectionReport report = runner(flags);
- report.languageVersion = Utils::LanguageVersion::LatestCxx;
+ report.languageVersion = LanguageVersion::LatestCxx;
return report;
};
tcInfo.macroInspectionRunner = macroInspectionWrapper;
@@ -1941,7 +1936,7 @@ void CppModelManager::GC()
filesInEditorSupports << abstractEditorSupport->filePath();
Snapshot currentSnapshot = snapshot();
- QSet<Utils::FilePath> reachableFiles;
+ QSet<FilePath> reachableFiles;
// The configuration file is part of the project files, which is just fine.
// If single files are open, without any project, then there is no need to
// keep the configuration file around.
@@ -1964,7 +1959,7 @@ void CppModelManager::GC()
QStringList notReachableFiles;
Snapshot newSnapshot;
for (Snapshot::const_iterator it = currentSnapshot.begin(); it != currentSnapshot.end(); ++it) {
- const Utils::FilePath &fileName = it.key();
+ const FilePath &fileName = it.key();
if (reachableFiles.contains(fileName))
newSnapshot.insert(it.value());
@@ -2001,7 +1996,7 @@ TextEditor::BaseHoverHandler *CppModelManager::createHoverHandler() const
}
void CppModelManager::followSymbol(const CursorInEditor &data,
- const Utils::LinkHandler &processLinkCallback,
+ const LinkHandler &processLinkCallback,
bool resolveTarget, bool inNextSplit, Backend backend)
{
instance()->modelManagerSupport(backend)->followSymbol(data, processLinkCallback,
@@ -2009,7 +2004,7 @@ void CppModelManager::followSymbol(const CursorInEditor &data,
}
void CppModelManager::followSymbolToType(const CursorInEditor &data,
- const Utils::LinkHandler &processLinkCallback,
+ const LinkHandler &processLinkCallback,
bool inNextSplit, Backend backend)
{
instance()->modelManagerSupport(backend)->followSymbolToType(data, processLinkCallback,
@@ -2017,13 +2012,13 @@ void CppModelManager::followSymbolToType(const CursorInEditor &data,
}
void CppModelManager::switchDeclDef(const CursorInEditor &data,
- const Utils::LinkHandler &processLinkCallback,
+ const LinkHandler &processLinkCallback,
Backend backend)
{
instance()->modelManagerSupport(backend)->switchDeclDef(data, processLinkCallback);
}
-Core::ILocatorFilter *CppModelManager::createAuxiliaryCurrentDocumentFilter()
+ILocatorFilter *CppModelManager::createAuxiliaryCurrentDocumentFilter()
{
const auto filter = new Internal::CppCurrentDocumentFilter(instance());
filter->makeAuxiliary();
@@ -2049,7 +2044,7 @@ FilePaths CppModelManager::projectFiles()
return d->m_projectFiles;
}
-ProjectExplorer::HeaderPaths CppModelManager::headerPaths()
+HeaderPaths CppModelManager::headerPaths()
{
QWriteLocker locker(&d->m_projectLock);
ensureUpdated();
@@ -2057,13 +2052,13 @@ ProjectExplorer::HeaderPaths CppModelManager::headerPaths()
return d->m_headerPaths;
}
-void CppModelManager::setHeaderPaths(const ProjectExplorer::HeaderPaths &headerPaths)
+void CppModelManager::setHeaderPaths(const HeaderPaths &headerPaths)
{
QWriteLocker locker(&d->m_projectLock);
d->m_headerPaths = headerPaths;
}
-ProjectExplorer::Macros CppModelManager::definedMacros()
+Macros CppModelManager::definedMacros()
{
QWriteLocker locker(&d->m_projectLock);
ensureUpdated();
diff --git a/src/plugins/cppeditor/cppmodelmanager_test.cpp b/src/plugins/cppeditor/cppmodelmanager_test.cpp
index 97d13d0704c..eaa2ef142d8 100644
--- a/src/plugins/cppeditor/cppmodelmanager_test.cpp
+++ b/src/plugins/cppeditor/cppmodelmanager_test.cpp
@@ -18,7 +18,7 @@
#include <cplusplus/LookupContext.h>
#include <projectexplorer/projectexplorer.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <utils/executeondestruction.h>
#include <utils/hostosinfo.h>
diff --git a/src/plugins/cppeditor/cppoutlinemodel.cpp b/src/plugins/cppeditor/cppoutlinemodel.cpp
index d1875b6bb3b..2ce6f7dad0e 100644
--- a/src/plugins/cppeditor/cppoutlinemodel.cpp
+++ b/src/plugins/cppeditor/cppoutlinemodel.cpp
@@ -11,6 +11,7 @@
#include <utils/linecolumn.h>
#include <utils/link.h>
+#include <utils/theme/theme.h>
#include <QTimer>
@@ -103,6 +104,24 @@ public:
return name;
}
+ case Qt::ForegroundRole: {
+ const auto isFwdDecl = [&] {
+ const FullySpecifiedType type = symbol->type();
+ if (type->asForwardClassDeclarationType())
+ return true;
+ if (const Template * const tmpl = type->asTemplateType())
+ return tmpl->declaration() && tmpl->declaration()->asForwardClassDeclaration();
+ if (type->asObjCForwardClassDeclarationType())
+ return true;
+ if (type->asObjCForwardProtocolDeclarationType())
+ return true;
+ return false;
+ };
+ if (isFwdDecl())
+ return Utils::creatorTheme()->color(Utils::Theme::TextColorDisabled);
+ return TreeItem::data(column, role);
+ }
+
case Qt::DecorationRole:
return Icons::iconForSymbol(symbol);
diff --git a/src/plugins/cppeditor/cppprojectinfogenerator.cpp b/src/plugins/cppeditor/cppprojectinfogenerator.cpp
index fe077bd3771..5c4c03de4cc 100644
--- a/src/plugins/cppeditor/cppprojectinfogenerator.cpp
+++ b/src/plugins/cppeditor/cppprojectinfogenerator.cpp
@@ -10,30 +10,24 @@
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/taskhub.h>
-#include <utils/qtcassert.h>
-
+#include <QPromise>
#include <QTimer>
-#include <set>
-
using namespace ProjectExplorer;
using namespace Utils;
namespace CppEditor::Internal {
-ProjectInfoGenerator::ProjectInfoGenerator(
- const QFutureInterface<ProjectInfo::ConstPtr> &futureInterface,
- const ProjectUpdateInfo &projectUpdateInfo)
- : m_futureInterface(futureInterface)
- , m_projectUpdateInfo(projectUpdateInfo)
+ProjectInfoGenerator::ProjectInfoGenerator(const ProjectUpdateInfo &projectUpdateInfo)
+ : m_projectUpdateInfo(projectUpdateInfo)
{
}
-ProjectInfo::ConstPtr ProjectInfoGenerator::generate()
+ProjectInfo::ConstPtr ProjectInfoGenerator::generate(const QPromise<ProjectInfo::ConstPtr> &promise)
{
QVector<ProjectPart::ConstPtr> projectParts;
for (const RawProjectPart &rpp : m_projectUpdateInfo.rawProjectParts) {
- if (m_futureInterface.isCanceled())
+ if (promise.isCanceled())
return {};
for (const ProjectPart::ConstPtr &part : createProjectParts(
rpp, m_projectUpdateInfo.projectFilePath)) {
diff --git a/src/plugins/cppeditor/cppprojectinfogenerator.h b/src/plugins/cppeditor/cppprojectinfogenerator.h
index c090974c042..8720d3e1cd2 100644
--- a/src/plugins/cppeditor/cppprojectinfogenerator.h
+++ b/src/plugins/cppeditor/cppprojectinfogenerator.h
@@ -5,17 +5,19 @@
#include "projectinfo.h"
-#include <QFutureInterface>
+QT_BEGIN_NAMESPACE
+template <class T>
+class QPromise;
+QT_END_NAMESPACE
namespace CppEditor::Internal {
class ProjectInfoGenerator
{
public:
- ProjectInfoGenerator(const QFutureInterface<ProjectInfo::ConstPtr> &futureInterface,
- const ProjectExplorer::ProjectUpdateInfo &projectUpdateInfo);
+ ProjectInfoGenerator(const ProjectExplorer::ProjectUpdateInfo &projectUpdateInfo);
- ProjectInfo::ConstPtr generate();
+ ProjectInfo::ConstPtr generate(const QPromise<ProjectInfo::ConstPtr> &promise);
private:
const QVector<ProjectPart::ConstPtr> createProjectParts(
@@ -29,7 +31,6 @@ private:
Utils::LanguageExtensions languageExtensions);
private:
- const QFutureInterface<ProjectInfo::ConstPtr> m_futureInterface;
const ProjectExplorer::ProjectUpdateInfo &m_projectUpdateInfo;
bool m_cToolchainMissing = false;
bool m_cxxToolchainMissing = false;
diff --git a/src/plugins/cppeditor/cppprojectupdater.cpp b/src/plugins/cppeditor/cppprojectupdater.cpp
index a784d3bb794..cb64ea61bc8 100644
--- a/src/plugins/cppeditor/cppprojectupdater.cpp
+++ b/src/plugins/cppeditor/cppprojectupdater.cpp
@@ -49,12 +49,12 @@ void CppProjectUpdater::update(const ProjectUpdateInfo &projectUpdateInfo,
using namespace ProjectExplorer;
// Run the project info generator in a worker thread and continue if that one is finished.
- const auto infoGenerator = [=](QFutureInterface<ProjectInfo::ConstPtr> &futureInterface) {
+ const auto infoGenerator = [=](QPromise<ProjectInfo::ConstPtr> &promise) {
ProjectUpdateInfo fullProjectUpdateInfo = projectUpdateInfo;
if (fullProjectUpdateInfo.rppGenerator)
fullProjectUpdateInfo.rawProjectParts = fullProjectUpdateInfo.rppGenerator();
- Internal::ProjectInfoGenerator generator(futureInterface, fullProjectUpdateInfo);
- futureInterface.reportResult(generator.generate());
+ Internal::ProjectInfoGenerator generator(fullProjectUpdateInfo);
+ promise.addResult(generator.generate(promise));
};
using namespace Tasking;
@@ -63,7 +63,7 @@ void CppProjectUpdater::update(const ProjectUpdateInfo &projectUpdateInfo,
};
const TreeStorage<UpdateStorage> storage;
const auto setupInfoGenerator = [=](AsyncTask<ProjectInfo::ConstPtr> &async) {
- async.setAsyncCallData(infoGenerator);
+ async.setConcurrentCallData(infoGenerator);
async.setFutureSynchronizer(&m_futureSynchronizer);
};
const auto onInfoGeneratorDone = [=](const AsyncTask<ProjectInfo::ConstPtr> &async) {
diff --git a/src/plugins/cppeditor/cppquickfixes.cpp b/src/plugins/cppeditor/cppquickfixes.cpp
index 3dae225cea3..24e62a65d2c 100644
--- a/src/plugins/cppeditor/cppquickfixes.cpp
+++ b/src/plugins/cppeditor/cppquickfixes.cpp
@@ -33,7 +33,7 @@
#include <projectexplorer/projectnodes.h>
#include <projectexplorer/projecttree.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <utils/algorithm.h>
#include <utils/basetreeview.h>
@@ -2006,6 +2006,7 @@ bool matchName(const Name *name, QList<Core::LocatorFilterEntry> *matches, QStri
} else {
simpleName = oo.prettyName(name);
*className = simpleName;
+ classesFilter->prepareSearch(*className);
*matches = classesFilter->matchesFor(dummy, *className);
if (matches->empty()) {
if (const Name *name = qualifiedName->base()) {
@@ -2022,8 +2023,10 @@ bool matchName(const Name *name, QList<Core::LocatorFilterEntry> *matches, QStri
*className = oo.prettyName(name);
}
- if (matches->empty())
+ if (matches->empty()) {
+ classesFilter->prepareSearch(*className);
*matches = classesFilter->matchesFor(dummy, *className);
+ }
if (matches->empty() && !simpleName.isEmpty())
*className = simpleName;
}
@@ -7986,7 +7989,7 @@ private:
}
};
- if (const Project *project = SessionManager::projectForFile(filePath())) {
+ if (const Project *project = ProjectManager::projectForFile(filePath())) {
const FilePaths files = project->files(ProjectExplorer::Project::SourceFiles);
QSet<FilePath> projectFiles(files.begin(), files.end());
for (const auto &file : files) {
diff --git a/src/plugins/cppeditor/cppquickfixsettingspage.h b/src/plugins/cppeditor/cppquickfixsettingspage.h
index 3e14fde864a..0fea07f91a1 100644
--- a/src/plugins/cppeditor/cppquickfixsettingspage.h
+++ b/src/plugins/cppeditor/cppquickfixsettingspage.h
@@ -2,7 +2,9 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
-#include "coreplugin/dialogs/ioptionspage.h"
+
+#include <coreplugin/dialogs/ioptionspage.h>
+
#include <QPointer>
namespace CppEditor {
diff --git a/src/plugins/cppeditor/cppsemanticinfoupdater.cpp b/src/plugins/cppeditor/cppsemanticinfoupdater.cpp
index 3e2438dbf80..349c722fb36 100644
--- a/src/plugins/cppeditor/cppsemanticinfoupdater.cpp
+++ b/src/plugins/cppeditor/cppsemanticinfoupdater.cpp
@@ -3,11 +3,10 @@
#include "cppsemanticinfoupdater.h"
-#include "cpplocalsymbols.h"
#include "cppmodelmanager.h"
+#include <utils/asynctask.h>
#include <utils/qtcassert.h>
-#include <utils/runextensions.h>
#include <cplusplus/Control.h>
#include <cplusplus/CppDocument.h>
@@ -29,11 +28,11 @@ public:
class FuturizedTopLevelDeclarationProcessor: public TopLevelDeclarationProcessor
{
public:
- explicit FuturizedTopLevelDeclarationProcessor(QFutureInterface<void> &future): m_future(future) {}
+ explicit FuturizedTopLevelDeclarationProcessor(QPromise<void> &promise): m_promise(promise) {}
bool processDeclaration(DeclarationAST *) override { return !isCanceled(); }
- bool isCanceled() { return m_future.isCanceled(); }
+ bool isCanceled() { return m_promise.isCanceled(); }
private:
- QFutureInterface<void> m_future;
+ QPromise<void> &m_promise;
};
public:
@@ -49,7 +48,7 @@ public:
bool reuseCurrentSemanticInfo(const SemanticInfo::Source &source, bool emitSignalWhenFinished);
- void update_helper(QFutureInterface<void> &future, const SemanticInfo::Source &source);
+ void update_helper(QPromise<void> &promise, const SemanticInfo::Source &source);
public:
SemanticInfoUpdater *q;
@@ -136,10 +135,10 @@ bool SemanticInfoUpdaterPrivate::reuseCurrentSemanticInfo(const SemanticInfo::So
return false;
}
-void SemanticInfoUpdaterPrivate::update_helper(QFutureInterface<void> &future,
+void SemanticInfoUpdaterPrivate::update_helper(QPromise<void> &promise,
const SemanticInfo::Source &source)
{
- FuturizedTopLevelDeclarationProcessor processor(future);
+ FuturizedTopLevelDeclarationProcessor processor(promise);
update(source, true, &processor);
}
@@ -179,7 +178,7 @@ void SemanticInfoUpdater::updateDetached(const SemanticInfo::Source &source)
return;
}
- d->m_future = Utils::runAsync(CppModelManager::instance()->sharedThreadPool(),
+ d->m_future = Utils::asyncRun(CppModelManager::instance()->sharedThreadPool(),
&SemanticInfoUpdaterPrivate::update_helper, d.data(), source);
}
diff --git a/src/plugins/cppeditor/cpptoolsjsextension.cpp b/src/plugins/cppeditor/cpptoolsjsextension.cpp
index a3cbfe12041..b70328601ee 100644
--- a/src/plugins/cppeditor/cpptoolsjsextension.cpp
+++ b/src/plugins/cppeditor/cpptoolsjsextension.cpp
@@ -10,12 +10,13 @@
#include <coreplugin/icore.h>
#include <projectexplorer/project.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/projectnodes.h>
-#include <projectexplorer/session.h>
#include <cplusplus/AST.h>
#include <cplusplus/ASTPath.h>
#include <cplusplus/Overview.h>
+
#include <utils/codegeneration.h>
#include <utils/fileutils.h>
@@ -234,7 +235,7 @@ QString CppToolsJsExtension::includeStatement(
}
return false;
};
- for (const Project * const p : SessionManager::projects()) {
+ for (const Project * const p : ProjectManager::projects()) {
const Node *theNode = p->rootProjectNode()->findNode(nodeMatchesFileName);
if (theNode) {
const bool sameDir = pathOfIncludingFile == theNode->filePath().toFileInfo().path();
diff --git a/src/plugins/cppeditor/cpptoolsreuse.cpp b/src/plugins/cppeditor/cpptoolsreuse.cpp
index 7d17e30e4bc..912902c0c9c 100644
--- a/src/plugins/cppeditor/cpptoolsreuse.cpp
+++ b/src/plugins/cppeditor/cpptoolsreuse.cpp
@@ -21,7 +21,9 @@
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/idocument.h>
#include <coreplugin/messagemanager.h>
-#include <projectexplorer/session.h>
+
+#include <projectexplorer/projectmanager.h>
+
#include <texteditor/codeassist/assistinterface.h>
#include <texteditor/textdocument.h>
@@ -29,6 +31,7 @@
#include <cplusplus/LookupContext.h>
#include <cplusplus/Overview.h>
#include <cplusplus/SimpleLexer.h>
+
#include <utils/algorithm.h>
#include <utils/textutils.h>
#include <utils/qtcassert.h>
@@ -596,12 +599,12 @@ NamespaceAST *NSCheckerVisitor::currentNamespace()
ProjectExplorer::Project *projectForProjectPart(const ProjectPart &part)
{
- return ProjectExplorer::SessionManager::projectWithProjectFilePath(part.topLevelProject);
+ return ProjectExplorer::ProjectManager::projectWithProjectFilePath(part.topLevelProject);
}
ProjectExplorer::Project *projectForProjectInfo(const ProjectInfo &info)
{
- return ProjectExplorer::SessionManager::projectWithProjectFilePath(info.projectFilePath());
+ return ProjectExplorer::ProjectManager::projectWithProjectFilePath(info.projectFilePath());
}
void openEditor(const Utils::FilePath &filePath, bool inNextSplit, Utils::Id editorId)
diff --git a/src/plugins/cppeditor/cpptoolstestcase.cpp b/src/plugins/cppeditor/cpptoolstestcase.cpp
index b7124b01ca4..2d149b3ff5d 100644
--- a/src/plugins/cppeditor/cpptoolstestcase.cpp
+++ b/src/plugins/cppeditor/cpptoolstestcase.cpp
@@ -18,7 +18,7 @@
#include <projectexplorer/buildsystem.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorer.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <texteditor/texteditor.h>
#include <texteditor/codeassist/iassistproposal.h>
@@ -348,8 +348,8 @@ bool TestCase::waitUntilProjectIsFullyOpened(Project *project, int timeOutInMs)
return QTest::qWaitFor(
[project]() {
- return SessionManager::startupBuildSystem()
- && !SessionManager::startupBuildSystem()->isParsing()
+ return ProjectManager::startupBuildSystem()
+ && !ProjectManager::startupBuildSystem()->isParsing()
&& CppModelManager::instance()->projectInfo(project);
},
timeOutInMs);
@@ -367,7 +367,7 @@ bool TestCase::writeFile(const FilePath &filePath, const QByteArray &contents)
ProjectOpenerAndCloser::ProjectOpenerAndCloser()
{
- QVERIFY(!SessionManager::hasProjects());
+ QVERIFY(!ProjectManager::hasProjects());
}
ProjectOpenerAndCloser::~ProjectOpenerAndCloser()
diff --git a/src/plugins/cppeditor/cpptypehierarchy.cpp b/src/plugins/cppeditor/cpptypehierarchy.cpp
index c7a9e472ca5..27ef5751d10 100644
--- a/src/plugins/cppeditor/cpptypehierarchy.cpp
+++ b/src/plugins/cppeditor/cpptypehierarchy.cpp
@@ -183,7 +183,8 @@ void CppTypeHierarchyWidget::perform()
m_futureWatcher.setFuture(QFuture<void>(m_future));
m_synchronizer.addFuture(m_future);
- Core::ProgressManager::addTask(m_future, Tr::tr("Evaluating Type Hierarchy"), "TypeHierarchy");
+ Core::ProgressManager::addTimedTask(m_futureWatcher.future(),
+ Tr::tr("Evaluating Type Hierarchy"), "TypeHierarchy", 2);
}
void CppTypeHierarchyWidget::performFromExpression(const QString &expression, const FilePath &filePath)
diff --git a/src/plugins/cppeditor/generatedcodemodelsupport.cpp b/src/plugins/cppeditor/generatedcodemodelsupport.cpp
index 400e77fb378..22a9b2735eb 100644
--- a/src/plugins/cppeditor/generatedcodemodelsupport.cpp
+++ b/src/plugins/cppeditor/generatedcodemodelsupport.cpp
@@ -13,7 +13,7 @@
#include <projectexplorer/extracompiler.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorer.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <utils/algorithm.h>
diff --git a/src/plugins/cppeditor/generatedcodemodelsupport.h b/src/plugins/cppeditor/generatedcodemodelsupport.h
index cd7591a3b00..d37177d9910 100644
--- a/src/plugins/cppeditor/generatedcodemodelsupport.h
+++ b/src/plugins/cppeditor/generatedcodemodelsupport.h
@@ -20,7 +20,7 @@ public:
const Utils::FilePath &generatedFile);
~GeneratedCodeModelSupport() override;
- /// \returns the contents encoded in UTF-8.
+ /// Returns the contents encoded in UTF-8.
QByteArray contents() const override;
Utils::FilePath filePath() const override; // The generated file
Utils::FilePath sourceFilePath() const override;
diff --git a/src/plugins/cppeditor/indexitem.cpp b/src/plugins/cppeditor/indexitem.cpp
index 3094c9d4945..879625263e2 100644
--- a/src/plugins/cppeditor/indexitem.cpp
+++ b/src/plugins/cppeditor/indexitem.cpp
@@ -9,7 +9,8 @@ namespace CppEditor {
IndexItem::Ptr IndexItem::create(const QString &symbolName, const QString &symbolType,
const QString &symbolScope, IndexItem::ItemType type,
- const QString &fileName, int line, int column, const QIcon &icon)
+ const QString &fileName, int line, int column, const QIcon &icon,
+ bool isFunctionDefinition)
{
Ptr ptr(new IndexItem);
@@ -21,6 +22,7 @@ IndexItem::Ptr IndexItem::create(const QString &symbolName, const QString &symbo
ptr->m_line = line;
ptr->m_column = column;
ptr->m_icon = icon;
+ ptr->m_isFuncDef = isFunctionDefinition;
return ptr;
}
diff --git a/src/plugins/cppeditor/indexitem.h b/src/plugins/cppeditor/indexitem.h
index eda023394df..135bd60adcd 100644
--- a/src/plugins/cppeditor/indexitem.h
+++ b/src/plugins/cppeditor/indexitem.h
@@ -40,7 +40,8 @@ public:
const QString &fileName,
int line,
int column,
- const QIcon &icon);
+ const QIcon &icon,
+ bool isFunctionDefinition);
static Ptr create(const QString &fileName, int sizeHint);
QString scopedSymbolName() const
@@ -64,6 +65,7 @@ public:
ItemType type() const { return m_type; }
int line() const { return m_line; }
int column() const { return m_column; }
+ bool isFunctionDefinition() const { return m_isFuncDef; }
void addChild(IndexItem::Ptr childItem) { m_children.append(childItem); }
void squeeze();
@@ -106,6 +108,7 @@ private:
ItemType m_type = All;
int m_line = 0;
int m_column = 0;
+ bool m_isFuncDef = false;
QVector<IndexItem::Ptr> m_children;
};
diff --git a/src/plugins/cppeditor/insertionpointlocator.h b/src/plugins/cppeditor/insertionpointlocator.h
index c4091d5f9dc..d6fd5d735cc 100644
--- a/src/plugins/cppeditor/insertionpointlocator.h
+++ b/src/plugins/cppeditor/insertionpointlocator.h
@@ -23,27 +23,21 @@ public:
InsertionLocation(const Utils::FilePath &filePath, const QString &prefix,
const QString &suffix, int line, int column);
- const Utils::FilePath &filePath() const
- { return m_filePath; }
+ const Utils::FilePath &filePath() const { return m_filePath; }
- /// \returns The prefix to insert before any other text.
- QString prefix() const
- { return m_prefix; }
+ /// Returns the prefix to insert before any other text.
+ QString prefix() const { return m_prefix; }
- /// \returns The suffix to insert after the other inserted text.
- QString suffix() const
- { return m_suffix; }
+ /// Returns the suffix to insert after the other inserted text.
+ QString suffix() const { return m_suffix; }
- /// \returns The line where to insert. The line number is 1-based.
- int line() const
- { return m_line; }
+ /// Returns the line where to insert. The line number is 1-based.
+ int line() const { return m_line; }
- /// \returns The column where to insert. The column number is 1-based.
- int column() const
- { return m_column; }
+ /// Returns the column where to insert. The column number is 1-based.
+ int column() const { return m_column; }
- bool isValid() const
- { return !m_filePath.isEmpty() && m_line > 0 && m_column > 0; }
+ bool isValid() const { return !m_filePath.isEmpty() && m_line > 0 && m_column > 0; }
private:
Utils::FilePath m_filePath;
diff --git a/src/plugins/cppeditor/modelmanagertesthelper.cpp b/src/plugins/cppeditor/modelmanagertesthelper.cpp
index f4114c33b27..a133ac7fef2 100644
--- a/src/plugins/cppeditor/modelmanagertesthelper.cpp
+++ b/src/plugins/cppeditor/modelmanagertesthelper.cpp
@@ -6,7 +6,7 @@
#include "cpptoolstestcase.h"
#include "projectinfo.h"
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <utils/algorithm.h>
@@ -59,7 +59,7 @@ void ModelManagerTestHelper::cleanup()
CppModelManager *mm = CppModelManager::instance();
QList<ProjectInfo::ConstPtr> pies = mm->projectInfos();
for (Project * const p : std::as_const(m_projects)) {
- ProjectExplorer::SessionManager::removeProject(p);
+ ProjectExplorer::ProjectManager::removeProject(p);
emit aboutToRemoveProject(p);
}
@@ -72,7 +72,7 @@ ModelManagerTestHelper::Project *ModelManagerTestHelper::createProject(
{
auto tp = new TestProject(name, this, filePath);
m_projects.push_back(tp);
- ProjectExplorer::SessionManager::addProject(tp);
+ ProjectExplorer::ProjectManager::addProject(tp);
emit projectAdded(tp);
return tp;
}
diff --git a/src/plugins/cppeditor/projectinfo_test.cpp b/src/plugins/cppeditor/projectinfo_test.cpp
index 73254647f5e..88115e2ea4f 100644
--- a/src/plugins/cppeditor/projectinfo_test.cpp
+++ b/src/plugins/cppeditor/projectinfo_test.cpp
@@ -362,12 +362,12 @@ public:
ProjectInfo::ConstPtr generate()
{
- QFutureInterface<ProjectInfo::ConstPtr> fi;
+ QPromise<ProjectInfo::ConstPtr> promise;
projectUpdateInfo.rawProjectParts += rawProjectPart;
- ProjectInfoGenerator generator(fi, projectUpdateInfo);
+ ProjectInfoGenerator generator(projectUpdateInfo);
- return generator.generate();
+ return generator.generate(promise);
}
ProjectUpdateInfo projectUpdateInfo;
diff --git a/src/plugins/cppeditor/searchsymbols.cpp b/src/plugins/cppeditor/searchsymbols.cpp
index 22e0c281975..2aea7f8e1bf 100644
--- a/src/plugins/cppeditor/searchsymbols.cpp
+++ b/src/plugins/cppeditor/searchsymbols.cpp
@@ -285,7 +285,8 @@ IndexItem::Ptr SearchSymbols::addChildItem(const QString &symbolName, const QStr
StringTable::insert(path),
symbol->line(),
symbol->column() - 1, // 1-based vs 0-based column
- icon);
+ icon,
+ symbol->asFunction());
_parent->addChild(newItem);
return newItem;
}
diff --git a/src/plugins/cppeditor/symbolsearcher_test.cpp b/src/plugins/cppeditor/symbolsearcher_test.cpp
index 9f700d99726..04b14b1002d 100644
--- a/src/plugins/cppeditor/symbolsearcher_test.cpp
+++ b/src/plugins/cppeditor/symbolsearcher_test.cpp
@@ -4,13 +4,13 @@
#include "symbolsearcher_test.h"
#include "cppindexingsupport.h"
-#include "cppmodelmanager.h"
#include "cpptoolstestcase.h"
#include "searchsymbols.h"
#include <coreplugin/testdatadir.h>
#include <coreplugin/find/searchresultwindow.h>
-#include <utils/runextensions.h>
+
+#include <utils/asynctask.h>
#include <QtTest>
@@ -78,7 +78,7 @@ public:
const QScopedPointer<SymbolSearcher> symbolSearcher(
new SymbolSearcher(searchParameters, QSet<QString>{testFile}));
QFuture<Core::SearchResultItem> search
- = Utils::runAsync(&SymbolSearcher::runSearch, symbolSearcher.data());
+ = Utils::asyncRun(&SymbolSearcher::runSearch, symbolSearcher.data());
search.waitForFinished();
ResultDataList results = ResultData::fromSearchResultList(search.results());
QCOMPARE(results, expectedResults);
diff --git a/src/plugins/cppeditor/symbolsfindfilter.cpp b/src/plugins/cppeditor/symbolsfindfilter.cpp
index 3b901715b2c..acfbdaf298b 100644
--- a/src/plugins/cppeditor/symbolsfindfilter.cpp
+++ b/src/plugins/cppeditor/symbolsfindfilter.cpp
@@ -12,18 +12,19 @@
#include <coreplugin/progressmanager/progressmanager.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/find/searchresultwindow.h>
+
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorer.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <utils/algorithm.h>
-#include <utils/runextensions.h>
+#include <utils/asynctask.h>
#include <utils/qtcassert.h>
-#include <QSet>
+#include <QButtonGroup>
#include <QGridLayout>
#include <QLabel>
-#include <QButtonGroup>
+#include <QSet>
using namespace Core;
using namespace Utils;
@@ -108,7 +109,7 @@ void SymbolsFindFilter::startSearch(SearchResult *search)
SymbolSearcher::Parameters parameters = search->userData().value<SymbolSearcher::Parameters>();
QSet<QString> projectFileNames;
if (parameters.scope == SymbolSearcher::SearchProjectsOnly) {
- for (ProjectExplorer::Project *project : ProjectExplorer::SessionManager::projects())
+ for (ProjectExplorer::Project *project : ProjectExplorer::ProjectManager::projects())
projectFileNames += Utils::transform<QSet>(project->files(ProjectExplorer::Project::AllFiles), &Utils::FilePath::toString);
}
@@ -120,7 +121,7 @@ void SymbolsFindFilter::startSearch(SearchResult *search)
SymbolSearcher *symbolSearcher = new SymbolSearcher(parameters, projectFileNames);
connect(watcher, &QFutureWatcherBase::finished,
symbolSearcher, &QObject::deleteLater);
- watcher->setFuture(Utils::runAsync(m_manager->sharedThreadPool(),
+ watcher->setFuture(Utils::asyncRun(m_manager->sharedThreadPool(),
&SymbolSearcher::runSearch, symbolSearcher));
FutureProgress *progress = ProgressManager::addTask(watcher->future(), Tr::tr("Searching for Symbol"),
Core::Constants::TASK_SEARCH);
diff --git a/src/plugins/cppeditor/testcases/highlightingtestcase.cpp b/src/plugins/cppeditor/testcases/highlightingtestcase.cpp
index 20fa2526467..770b22e553e 100644
--- a/src/plugins/cppeditor/testcases/highlightingtestcase.cpp
+++ b/src/plugins/cppeditor/testcases/highlightingtestcase.cpp
@@ -25,3 +25,5 @@ template<int n = 5> class C;
struct ConversionFunction {
operator int();
};
+
+template<typename T> concept NoConstraint = true;
diff --git a/src/plugins/cppeditor/typehierarchybuilder.cpp b/src/plugins/cppeditor/typehierarchybuilder.cpp
index 5f3aae00e79..889d0eacfc0 100644
--- a/src/plugins/cppeditor/typehierarchybuilder.cpp
+++ b/src/plugins/cppeditor/typehierarchybuilder.cpp
@@ -108,20 +108,12 @@ const QList<TypeHierarchy> &TypeHierarchy::hierarchy() const
}
TypeHierarchy TypeHierarchyBuilder::buildDerivedTypeHierarchy(Symbol *symbol,
- const Snapshot &snapshot)
-{
- QFutureInterfaceBase dummy;
- return TypeHierarchyBuilder::buildDerivedTypeHierarchy(dummy, symbol, snapshot);
-}
-
-TypeHierarchy TypeHierarchyBuilder::buildDerivedTypeHierarchy(QFutureInterfaceBase &futureInterface,
- Symbol *symbol,
- const Snapshot &snapshot)
+ const Snapshot &snapshot, const std::optional<QFuture<void>> &future)
{
TypeHierarchy hierarchy(symbol);
TypeHierarchyBuilder builder;
QHash<QString, QHash<QString, QString>> cache;
- builder.buildDerived(futureInterface, &hierarchy, snapshot, cache);
+ builder.buildDerived(future, &hierarchy, snapshot, cache);
return hierarchy;
}
@@ -172,11 +164,10 @@ static FilePaths filesDependingOn(const Snapshot &snapshot, Symbol *symbol)
return FilePaths{file} + snapshot.filesDependingOn(file);
}
-void TypeHierarchyBuilder::buildDerived(QFutureInterfaceBase &futureInterface,
+void TypeHierarchyBuilder::buildDerived(const std::optional<QFuture<void>> &future,
TypeHierarchy *typeHierarchy,
const Snapshot &snapshot,
- QHash<QString, QHash<QString, QString>> &cache,
- int depth)
+ QHash<QString, QHash<QString, QString>> &cache)
{
Symbol *symbol = typeHierarchy->_symbol;
if (_visited.contains(symbol))
@@ -188,15 +179,10 @@ void TypeHierarchyBuilder::buildDerived(QFutureInterfaceBase &futureInterface,
DerivedHierarchyVisitor visitor(symbolName, cache);
const FilePaths dependingFiles = filesDependingOn(snapshot, symbol);
- if (depth == 0)
- futureInterface.setProgressRange(0, dependingFiles.size());
- int i = -1;
for (const FilePath &fileName : dependingFiles) {
- if (futureInterface.isCanceled())
+ if (future && future->isCanceled())
return;
- if (depth == 0)
- futureInterface.setProgressValue(++i);
Document::Ptr doc = snapshot.document(fileName);
if ((_candidates.contains(fileName) && !_candidates.value(fileName).contains(symbolName))
|| !doc->control()->findIdentifier(symbol->identifier()->chars(),
@@ -210,8 +196,8 @@ void TypeHierarchyBuilder::buildDerived(QFutureInterfaceBase &futureInterface,
const QList<Symbol *> &derived = visitor.derived();
for (Symbol *s : derived) {
TypeHierarchy derivedHierarchy(s);
- buildDerived(futureInterface, &derivedHierarchy, snapshot, cache, depth + 1);
- if (futureInterface.isCanceled())
+ buildDerived(future, &derivedHierarchy, snapshot, cache);
+ if (future && future->isCanceled())
return;
typeHierarchy->_hierarchy.append(derivedHierarchy);
}
diff --git a/src/plugins/cppeditor/typehierarchybuilder.h b/src/plugins/cppeditor/typehierarchybuilder.h
index 07556126dd8..c6ee8a56bf6 100644
--- a/src/plugins/cppeditor/typehierarchybuilder.h
+++ b/src/plugins/cppeditor/typehierarchybuilder.h
@@ -6,7 +6,7 @@
#include <cplusplus/CppDocument.h>
#include <cplusplus/Overview.h>
-#include <QFutureInterface>
+#include <QFuture>
#include <QList>
#include <QSet>
@@ -44,19 +44,17 @@ class TypeHierarchyBuilder
{
public:
static TypeHierarchy buildDerivedTypeHierarchy(CPlusPlus::Symbol *symbol,
- const CPlusPlus::Snapshot &snapshot);
- static TypeHierarchy buildDerivedTypeHierarchy(QFutureInterfaceBase &futureInterface,
- CPlusPlus::Symbol *symbol,
- const CPlusPlus::Snapshot &snapshot);
+ const CPlusPlus::Snapshot &snapshot,
+ const std::optional<QFuture<void>> &future = {});
static CPlusPlus::LookupItem followTypedef(const CPlusPlus::LookupContext &context,
const CPlusPlus::Name *symbolName,
CPlusPlus::Scope *enclosingScope,
std::set<const CPlusPlus::Symbol *> typedefs = {});
private:
TypeHierarchyBuilder() = default;
- void buildDerived(QFutureInterfaceBase &futureInterface, TypeHierarchy *typeHierarchy,
+ void buildDerived(const std::optional<QFuture<void>> &future, TypeHierarchy *typeHierarchy,
const CPlusPlus::Snapshot &snapshot,
- QHash<QString, QHash<QString, QString> > &cache, int depth = 0);
+ QHash<QString, QHash<QString, QString> > &cache);
QSet<CPlusPlus::Symbol *> _visited;
QHash<Utils::FilePath, QSet<QString> > _candidates;
diff --git a/src/plugins/debugger/breakhandler.cpp b/src/plugins/debugger/breakhandler.cpp
index bcc50a4b0d0..f13ac94d89b 100644
--- a/src/plugins/debugger/breakhandler.cpp
+++ b/src/plugins/debugger/breakhandler.cpp
@@ -1126,9 +1126,9 @@ void BreakpointItem::addToCommand(DebuggerCommand *cmd) const
cmd->arg("expression", requested.expression);
}
-void BreakpointItem::updateFromGdbOutput(const GdbMi &bkpt)
+void BreakpointItem::updateFromGdbOutput(const GdbMi &bkpt, const FilePath &fileRoot)
{
- m_parameters.updateFromGdbOutput(bkpt);
+ m_parameters.updateFromGdbOutput(bkpt, fileRoot);
adjustMarker();
}
diff --git a/src/plugins/debugger/breakhandler.h b/src/plugins/debugger/breakhandler.h
index 1024783db38..0b939a011fc 100644
--- a/src/plugins/debugger/breakhandler.h
+++ b/src/plugins/debugger/breakhandler.h
@@ -107,7 +107,7 @@ public:
const BreakpointParameters &requestedParameters() const;
void addToCommand(DebuggerCommand *cmd) const;
- void updateFromGdbOutput(const GdbMi &bkpt);
+ void updateFromGdbOutput(const GdbMi &bkpt, const Utils::FilePath &fileRoot);
int modelId() const;
QString responseId() const { return m_responseId; }
diff --git a/src/plugins/debugger/breakpoint.cpp b/src/plugins/debugger/breakpoint.cpp
index 8d456ef67f6..79e2badf7b5 100644
--- a/src/plugins/debugger/breakpoint.cpp
+++ b/src/plugins/debugger/breakpoint.cpp
@@ -265,7 +265,8 @@ static QString cleanupFullName(const QString &fileName)
return cleanFilePath;
}
-void BreakpointParameters::updateFromGdbOutput(const GdbMi &bkpt)
+
+void BreakpointParameters::updateFromGdbOutput(const GdbMi &bkpt, const Utils::FilePath &fileRoot)
{
QTC_ASSERT(bkpt.isValid(), return);
@@ -358,7 +359,7 @@ void BreakpointParameters::updateFromGdbOutput(const GdbMi &bkpt)
QString name;
if (!fullName.isEmpty()) {
name = cleanupFullName(fullName);
- fileName = Utils::FilePath::fromString(name);
+ fileName = fileRoot.withNewPath(name);
//if (data->markerFileName().isEmpty())
// data->setMarkerFileName(name);
} else {
@@ -367,7 +368,7 @@ void BreakpointParameters::updateFromGdbOutput(const GdbMi &bkpt)
// gdb's own. No point in assigning markerFileName for now.
}
if (!name.isEmpty())
- fileName = Utils::FilePath::fromString(name);
+ fileName = fileRoot.withNewPath(name);
if (fileName.isEmpty())
updateLocation(originalLocation);
diff --git a/src/plugins/debugger/breakpoint.h b/src/plugins/debugger/breakpoint.h
index 52a150f15e2..b5ea0628da7 100644
--- a/src/plugins/debugger/breakpoint.h
+++ b/src/plugins/debugger/breakpoint.h
@@ -128,7 +128,7 @@ public:
bool isQmlFileAndLineBreakpoint() const;
QString toString() const;
void updateLocation(const QString &location); // file.cpp:42
- void updateFromGdbOutput(const GdbMi &bkpt);
+ void updateFromGdbOutput(const GdbMi &bkpt, const Utils::FilePath &fileRoot);
bool operator==(const BreakpointParameters &p) const { return equals(p); }
bool operator!=(const BreakpointParameters &p) const { return !equals(p); }
diff --git a/src/plugins/debugger/cdb/cdbengine.cpp b/src/plugins/debugger/cdb/cdbengine.cpp
index b95d1c93d52..ac382c4005d 100644
--- a/src/plugins/debugger/cdb/cdbengine.cpp
+++ b/src/plugins/debugger/cdb/cdbengine.cpp
@@ -1441,15 +1441,16 @@ void CdbEngine::reloadModules()
runCommand({"modules", ExtensionCommand, CB(handleModules)});
}
-void CdbEngine::loadSymbols(const QString & /* moduleName */)
+void CdbEngine::loadSymbols(const FilePath &moduleName)
{
+ Q_UNUSED(moduleName)
}
void CdbEngine::loadAllSymbols()
{
}
-void CdbEngine::requestModuleSymbols(const QString &moduleName)
+void CdbEngine::requestModuleSymbols(const FilePath &moduleName)
{
Q_UNUSED(moduleName)
}
@@ -1487,12 +1488,13 @@ void CdbEngine::handleModules(const DebuggerResponse &response)
{
if (response.resultClass == ResultDone) {
if (response.data.type() == GdbMi::List) {
+ const FilePath inferior = runParameters().inferior.command.executable();
ModulesHandler *handler = modulesHandler();
handler->beginUpdateAll();
for (const GdbMi &gdbmiModule : response.data) {
Module module;
module.moduleName = gdbmiModule["name"].data();
- module.modulePath = gdbmiModule["image"].data();
+ module.modulePath = inferior.withNewPath(gdbmiModule["image"].data());
module.startAddress = gdbmiModule["start"].data().toULongLong(nullptr, 0);
module.endAddress = gdbmiModule["end"].data().toULongLong(nullptr, 0);
if (gdbmiModule["deferred"].type() == GdbMi::Invalid)
diff --git a/src/plugins/debugger/cdb/cdbengine.h b/src/plugins/debugger/cdb/cdbengine.h
index 3bea692d5b5..a231ce39c23 100644
--- a/src/plugins/debugger/cdb/cdbengine.h
+++ b/src/plugins/debugger/cdb/cdbengine.h
@@ -65,9 +65,9 @@ public:
void changeMemory(MemoryAgent *, quint64 addr, const QByteArray &data) override;
void reloadModules() override;
- void loadSymbols(const QString &moduleName) override;
+ void loadSymbols(const Utils::FilePath &moduleName) override;
void loadAllSymbols() override;
- void requestModuleSymbols(const QString &moduleName) override;
+ void requestModuleSymbols(const Utils::FilePath &moduleName) override;
void reloadRegisters() override;
void reloadSourceFiles() override;
diff --git a/src/plugins/debugger/commonoptionspage.cpp b/src/plugins/debugger/commonoptionspage.cpp
index dc47fb79e08..f04eeb74b91 100644
--- a/src/plugins/debugger/commonoptionspage.cpp
+++ b/src/plugins/debugger/commonoptionspage.cpp
@@ -153,7 +153,8 @@ public:
Grid limits {
s.maximalStringLength, br,
- s.displayStringLimit
+ s.displayStringLimit, br,
+ s.defaultArraySize
};
Column {
diff --git a/src/plugins/debugger/debugger.qbs b/src/plugins/debugger/debugger.qbs
index 50f1eba24f9..8319bea822b 100644
--- a/src/plugins/debugger/debugger.qbs
+++ b/src/plugins/debugger/debugger.qbs
@@ -239,9 +239,7 @@ Project {
]
}
- Group {
- name: "Unit tests"
- condition: qtc.testsEnabled
+ QtcTestFiles {
files: [
"debuggerunittests.qrc",
]
diff --git a/src/plugins/debugger/debuggeractions.cpp b/src/plugins/debugger/debuggeractions.cpp
index a3a6187a417..208ca88d61e 100644
--- a/src/plugins/debugger/debuggeractions.cpp
+++ b/src/plugins/debugger/debuggeractions.cpp
@@ -531,6 +531,15 @@ DebuggerSettings::DebuggerSettings()
+ Tr::tr("The maximum length for strings in separated windows. "
"Longer strings are cut off and displayed with an ellipsis attached."));
+ defaultArraySize.setSettingsKey(debugModeGroup, "DefaultArraySize");
+ defaultArraySize.setDefaultValue(100);
+ defaultArraySize.setRange(10, 1000000000);
+ defaultArraySize.setSingleStep(100);
+ defaultArraySize.setLabelText(Tr::tr("Default array size:"));
+ defaultArraySize.setToolTip("<p>"
+ + Tr::tr("The number of array elements requested when expanding "
+ "entries in the Locals and Expressions views."));
+
expandStack.setLabelText(Tr::tr("Reload Full Stack"));
createFullBacktrace.setLabelText(Tr::tr("Create Full Backtrace"));
@@ -610,6 +619,7 @@ DebuggerSettings::DebuggerSettings()
page4.registerAspect(&showQObjectNames);
page4.registerAspect(&displayStringLimit);
page4.registerAspect(&maximalStringLength);
+ page4.registerAspect(&defaultArraySize);
// Page 5
page5.registerAspect(&cdbAdditionalArguments);
diff --git a/src/plugins/debugger/debuggeractions.h b/src/plugins/debugger/debuggeractions.h
index 69cc76a1f1b..f01ef74aa99 100644
--- a/src/plugins/debugger/debuggeractions.h
+++ b/src/plugins/debugger/debuggeractions.h
@@ -146,6 +146,7 @@ public:
Utils::BoolAspect autoDerefPointers;
Utils::IntegerAspect maximalStringLength;
Utils::IntegerAspect displayStringLimit;
+ Utils::IntegerAspect defaultArraySize;
Utils::BoolAspect sortStructMembers;
Utils::BoolAspect useToolTipsInLocalsView;
diff --git a/src/plugins/debugger/debuggerdialogs.cpp b/src/plugins/debugger/debuggerdialogs.cpp
index ece2c226692..c382e91c175 100644
--- a/src/plugins/debugger/debuggerdialogs.cpp
+++ b/src/plugins/debugger/debuggerdialogs.cpp
@@ -212,6 +212,7 @@ StartApplicationDialog::StartApplicationDialog(QWidget *parent)
d->localExecutablePathChooser->setHistoryCompleter("LocalExecutable");
d->arguments = new FancyLineEdit(this);
+ d->arguments->setClearButtonEnabled(true);
d->arguments->setHistoryCompleter("CommandlineArguments");
d->workingDirectory = new PathChooser(this);
diff --git a/src/plugins/debugger/debuggerengine.cpp b/src/plugins/debugger/debuggerengine.cpp
index 14cab127508..eeba3bfe9ed 100644
--- a/src/plugins/debugger/debuggerengine.cpp
+++ b/src/plugins/debugger/debuggerengine.cpp
@@ -13,15 +13,16 @@
#include "debuggertr.h"
#include "breakhandler.h"
+#include "debuggermainwindow.h"
#include "disassembleragent.h"
+#include "enginemanager.h"
#include "localsandexpressionswindow.h"
#include "logwindow.h"
-#include "debuggermainwindow.h"
-#include "enginemanager.h"
#include "memoryagent.h"
#include "moduleshandler.h"
#include "registerhandler.h"
#include "peripheralregisterhandler.h"
+#include "shared/peutils.h"
#include "sourcefileshandler.h"
#include "sourceutils.h"
#include "stackhandler.h"
@@ -31,7 +32,6 @@
#include "watchhandler.h"
#include "watchutils.h"
#include "watchwindow.h"
-#include "debugger/shared/peutils.h"
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/editormanager/editormanager.h>
@@ -471,7 +471,7 @@ public:
QString m_qtNamespace;
// Safety net to avoid infinite lookups.
- QSet<QString> m_lookupRequests; // FIXME: Integrate properly.
+ QHash<QString, int> m_lookupRequests; // FIXME: Integrate properly.
QPointer<QWidget> m_alertBox;
QPointer<BaseTreeView> m_breakView;
@@ -2083,7 +2083,7 @@ void DebuggerEngine::examineModules()
{
}
-void DebuggerEngine::loadSymbols(const QString &)
+void DebuggerEngine::loadSymbols(const FilePath &)
{
}
@@ -2095,11 +2095,11 @@ void DebuggerEngine::loadSymbolsForStack()
{
}
-void DebuggerEngine::requestModuleSymbols(const QString &)
+void DebuggerEngine::requestModuleSymbols(const FilePath &)
{
}
-void DebuggerEngine::requestModuleSections(const QString &)
+void DebuggerEngine::requestModuleSections(const FilePath &)
{
}
@@ -2360,9 +2360,10 @@ bool DebuggerEngine::canHandleToolTip(const DebuggerToolTipContext &context) con
void DebuggerEngine::updateItem(const QString &iname)
{
- if (d->m_lookupRequests.contains(iname)) {
+ WatchHandler *handler = watchHandler();
+ const int maxArrayCount = handler->maxArrayCount(iname);
+ if (d->m_lookupRequests.value(iname, -1) == maxArrayCount) {
showMessage(QString("IGNORING REPEATED REQUEST TO EXPAND " + iname));
- WatchHandler *handler = watchHandler();
WatchItem *item = handler->findItem(iname);
QTC_CHECK(item);
WatchModelBase *model = handler->model();
@@ -2382,7 +2383,7 @@ void DebuggerEngine::updateItem(const QString &iname)
}
// We could legitimately end up here after expanding + closing + re-expaning an item.
}
- d->m_lookupRequests.insert(iname);
+ d->m_lookupRequests[iname] = maxArrayCount;
UpdateParameters params;
params.partialVariable = iname;
@@ -2651,7 +2652,7 @@ static void createNewDock(QWidget *widget)
dockWidget->show();
}
-void DebuggerEngine::showModuleSymbols(const QString &moduleName, const Symbols &symbols)
+void DebuggerEngine::showModuleSymbols(const FilePath &moduleName, const Symbols &symbols)
{
auto w = new QTreeWidget;
w->setUniformRowHeights(true);
@@ -2659,7 +2660,7 @@ void DebuggerEngine::showModuleSymbols(const QString &moduleName, const Symbols
w->setRootIsDecorated(false);
w->setAlternatingRowColors(true);
w->setSortingEnabled(true);
- w->setObjectName("Symbols." + moduleName);
+ w->setObjectName("Symbols." + moduleName.toFSPathString());
QStringList header;
header.append(Tr::tr("Symbol"));
header.append(Tr::tr("Address"));
@@ -2667,7 +2668,7 @@ void DebuggerEngine::showModuleSymbols(const QString &moduleName, const Symbols
header.append(Tr::tr("Section"));
header.append(Tr::tr("Name"));
w->setHeaderLabels(header);
- w->setWindowTitle(Tr::tr("Symbols in \"%1\"").arg(moduleName));
+ w->setWindowTitle(Tr::tr("Symbols in \"%1\"").arg(moduleName.toUserOutput()));
for (const Symbol &s : symbols) {
auto it = new QTreeWidgetItem;
it->setData(0, Qt::DisplayRole, s.name);
@@ -2680,7 +2681,7 @@ void DebuggerEngine::showModuleSymbols(const QString &moduleName, const Symbols
createNewDock(w);
}
-void DebuggerEngine::showModuleSections(const QString &moduleName, const Sections &sections)
+void DebuggerEngine::showModuleSections(const FilePath &moduleName, const Sections &sections)
{
auto w = new QTreeWidget;
w->setUniformRowHeights(true);
@@ -2688,7 +2689,7 @@ void DebuggerEngine::showModuleSections(const QString &moduleName, const Section
w->setRootIsDecorated(false);
w->setAlternatingRowColors(true);
w->setSortingEnabled(true);
- w->setObjectName("Sections." + moduleName);
+ w->setObjectName("Sections." + moduleName.toFSPathString());
QStringList header;
header.append(Tr::tr("Name"));
header.append(Tr::tr("From"));
@@ -2696,7 +2697,7 @@ void DebuggerEngine::showModuleSections(const QString &moduleName, const Section
header.append(Tr::tr("Address"));
header.append(Tr::tr("Flags"));
w->setHeaderLabels(header);
- w->setWindowTitle(Tr::tr("Sections in \"%1\"").arg(moduleName));
+ w->setWindowTitle(Tr::tr("Sections in \"%1\"").arg(moduleName.toUserOutput()));
for (const Section &s : sections) {
auto it = new QTreeWidgetItem;
it->setData(0, Qt::DisplayRole, s.name);
diff --git a/src/plugins/debugger/debuggerengine.h b/src/plugins/debugger/debuggerengine.h
index 422d4eb788c..215cb3081dd 100644
--- a/src/plugins/debugger/debuggerengine.h
+++ b/src/plugins/debugger/debuggerengine.h
@@ -7,12 +7,14 @@
#include "debuggerconstants.h"
#include "debuggerprotocol.h"
#include "breakhandler.h"
-#include "projectexplorer/abi.h"
#include "threadshandler.h"
#include <coreplugin/icontext.h>
+
+#include <projectexplorer/abi.h>
#include <projectexplorer/devicesupport/idevicefwd.h>
#include <projectexplorer/runcontrol.h>
+
#include <texteditor/textmark.h>
#include <utils/filepath.h>
@@ -303,11 +305,11 @@ public:
virtual void reloadModules();
virtual void examineModules();
- virtual void loadSymbols(const QString &moduleName);
+ virtual void loadSymbols(const Utils::FilePath &moduleName);
virtual void loadSymbolsForStack();
virtual void loadAllSymbols();
- virtual void requestModuleSymbols(const QString &moduleName);
- virtual void requestModuleSections(const QString &moduleName);
+ virtual void requestModuleSymbols(const Utils::FilePath &moduleName);
+ virtual void requestModuleSections(const Utils::FilePath &moduleName);
virtual void reloadRegisters();
virtual void reloadPeripheralRegisters();
@@ -452,8 +454,8 @@ public:
void openMemoryEditor();
- static void showModuleSymbols(const QString &moduleName, const QVector<Symbol> &symbols);
- static void showModuleSections(const QString &moduleName, const QVector<Section> &sections);
+ static void showModuleSymbols(const Utils::FilePath &moduleName, const QVector<Symbol> &symbols);
+ static void showModuleSections(const Utils::FilePath &moduleName, const QVector<Section> &sections);
void handleExecDetach();
void handleExecContinue();
diff --git a/src/plugins/debugger/debuggeritem.cpp b/src/plugins/debugger/debuggeritem.cpp
index a7205d34a1c..0769d293dee 100644
--- a/src/plugins/debugger/debuggeritem.cpp
+++ b/src/plugins/debugger/debuggeritem.cpp
@@ -116,6 +116,9 @@ void DebuggerItem::createId()
void DebuggerItem::reinitializeFromFile(QString *error, Utils::Environment *customEnv)
{
+ if (isGeneric())
+ return;
+
// CDB only understands the single-dash -version, whereas GDB and LLDB are
// happy with both -version and --version. So use the "working" -version
// except for the experimental LLDB-MI which insists on --version.
@@ -282,6 +285,16 @@ QString DebuggerItem::engineTypeName() const
}
}
+void DebuggerItem::setGeneric(bool on)
+{
+ m_detectionSource = on ? QLatin1String("Generic") : QLatin1String();
+}
+
+bool DebuggerItem::isGeneric() const
+{
+ return m_detectionSource == "Generic";
+}
+
QStringList DebuggerItem::abiNames() const
{
QStringList list;
@@ -297,13 +310,15 @@ QDateTime DebuggerItem::lastModified() const
QIcon DebuggerItem::decoration() const
{
+ if (isGeneric())
+ return {};
if (m_engineType == NoEngineType)
return Icons::CRITICAL.icon();
if (!m_command.isExecutableFile())
return Icons::WARNING.icon();
if (!m_workingDirectory.isEmpty() && !m_workingDirectory.isDir())
return Icons::WARNING.icon();
- return QIcon();
+ return {};
}
QString DebuggerItem::validityMessage() const
diff --git a/src/plugins/debugger/debuggeritem.h b/src/plugins/debugger/debuggeritem.h
index 1edaf283ca4..95993665b67 100644
--- a/src/plugins/debugger/debuggeritem.h
+++ b/src/plugins/debugger/debuggeritem.h
@@ -8,7 +8,7 @@
#include <projectexplorer/abi.h>
-#include <utils/fileutils.h>
+#include <utils/filepath.h>
#include <utils/environment.h>
#include <QDateTime>
@@ -84,6 +84,9 @@ public:
QString detectionSource() const { return m_detectionSource; }
void setDetectionSource(const QString &source) { m_detectionSource = source; }
+ bool isGeneric() const;
+ void setGeneric(bool on);
+
static bool addAndroidLldbPythonEnv(const Utils::FilePath &lldbCmd, Utils::Environment &env);
private:
diff --git a/src/plugins/debugger/debuggeritemmanager.cpp b/src/plugins/debugger/debuggeritemmanager.cpp
index b3792554168..a74ac5071fc 100644
--- a/src/plugins/debugger/debuggeritemmanager.cpp
+++ b/src/plugins/debugger/debuggeritemmanager.cpp
@@ -92,7 +92,8 @@ static DebuggerItemManagerPrivate *d = nullptr;
class DebuggerItemConfigWidget : public QWidget
{
public:
- explicit DebuggerItemConfigWidget();
+ DebuggerItemConfigWidget();
+
void load(const DebuggerItem *item);
void store() const;
@@ -104,13 +105,18 @@ private:
QLineEdit *m_displayNameLineEdit;
QLineEdit *m_typeLineEdit;
QLabel *m_cdbLabel;
- QLineEdit *m_versionLabel;
PathChooser *m_binaryChooser;
- PathChooser *m_workingDirectoryChooser;
- QLineEdit *m_abis;
bool m_autodetected = false;
+ bool m_generic = false;
DebuggerEngineType m_engineType = NoEngineType;
QVariant m_id;
+
+ QLabel *m_abisLabel;
+ QLineEdit *m_abis;
+ QLabel *m_versionLabel;
+ QLineEdit *m_version;
+ QLabel *m_workingDirectoryLabel;
+ PathChooser *m_workingDirectoryChooser;
};
// --------------------------------------------------------------------------
@@ -170,10 +176,11 @@ class DebuggerItemModel : public TreeModel<TreeItem, StaticTreeItem, DebuggerTre
{
public:
DebuggerItemModel();
+ enum { Generic, AutoDetected, Manual };
QModelIndex lastIndex() const;
void setCurrentIndex(const QModelIndex &index);
- void addDebugger(const DebuggerItem &item, bool changed = false);
+ DebuggerTreeItem *addDebugger(const DebuggerItem &item, bool changed = false);
void updateDebugger(const DebuggerItem &item);
void apply();
void cancel();
@@ -202,17 +209,40 @@ const DebuggerItem *findDebugger(const Predicate &pred)
DebuggerItemModel::DebuggerItemModel()
{
setHeader({Tr::tr("Name"), Tr::tr("Path"), Tr::tr("Type")});
- rootItem()->appendChild(
- new StaticTreeItem({ProjectExplorer::Constants::msgAutoDetected()},
- {ProjectExplorer::Constants::msgAutoDetectedToolTip()}));
+
+ auto generic = new StaticTreeItem(Tr::tr("Generic"));
+ auto autoDetected = new StaticTreeItem({ProjectExplorer::Constants::msgAutoDetected()},
+ {ProjectExplorer::Constants::msgAutoDetectedToolTip()});
+ rootItem()->appendChild(generic);
+ rootItem()->appendChild(autoDetected);
rootItem()->appendChild(new StaticTreeItem(ProjectExplorer::Constants::msgManual()));
+
+ DebuggerItem genericGdb(QVariant("gdb"));
+ genericGdb.setAutoDetected(true);
+ genericGdb.setGeneric(true);
+ genericGdb.setEngineType(GdbEngineType);
+ genericGdb.setAbi(Abi());
+ genericGdb.setCommand("gdb");
+ genericGdb.setUnexpandedDisplayName(Tr::tr("%1 from PATH on Build Device").arg("GDB"));
+ generic->appendChild(new DebuggerTreeItem(genericGdb, false));
+
+ DebuggerItem genericLldb(QVariant("lldb"));
+ genericLldb.setAutoDetected(true);
+ genericLldb.setEngineType(LldbEngineType);
+ genericLldb.setGeneric(true);
+ genericLldb.setAbi(Abi());
+ genericLldb.setCommand("lldb");
+ genericLldb.setUnexpandedDisplayName(Tr::tr("%1 from PATH on Build Device").arg("LLDB"));
+ generic->appendChild(new DebuggerTreeItem(genericLldb, false));
}
-void DebuggerItemModel::addDebugger(const DebuggerItem &item, bool changed)
+DebuggerTreeItem *DebuggerItemModel::addDebugger(const DebuggerItem &item, bool changed)
{
- QTC_ASSERT(item.id().isValid(), return);
- int group = item.isAutoDetected() ? 0 : 1;
- rootItem()->childAt(group)->appendChild(new DebuggerTreeItem(item, changed));
+ QTC_ASSERT(item.id().isValid(), return {});
+ int group = item.isGeneric() ? Generic : (item.isAutoDetected() ? AutoDetected : Manual);
+ auto treeItem = new DebuggerTreeItem(item, changed);
+ rootItem()->childAt(group)->appendChild(treeItem);
+ return treeItem;
}
void DebuggerItemModel::updateDebugger(const DebuggerItem &item)
@@ -301,6 +331,7 @@ DebuggerItemConfigWidget::DebuggerItemConfigWidget()
});
m_binaryChooser->setAllowPathFromDevice(true);
+ m_workingDirectoryLabel = new QLabel(Tr::tr("ABIs:"));
m_workingDirectoryChooser = new PathChooser(this);
m_workingDirectoryChooser->setExpectedKind(PathChooser::Directory);
m_workingDirectoryChooser->setMinimumWidth(400);
@@ -310,10 +341,12 @@ DebuggerItemConfigWidget::DebuggerItemConfigWidget()
m_cdbLabel->setTextInteractionFlags(Qt::TextBrowserInteraction);
m_cdbLabel->setOpenExternalLinks(true);
- m_versionLabel = new QLineEdit(this);
- m_versionLabel->setPlaceholderText(Tr::tr("Unknown"));
- m_versionLabel->setEnabled(false);
+ m_versionLabel = new QLabel(Tr::tr("Version:"));
+ m_version = new QLineEdit(this);
+ m_version->setPlaceholderText(Tr::tr("Unknown"));
+ m_version->setEnabled(false);
+ m_abisLabel = new QLabel(Tr::tr("Working directory:"));
m_abis = new QLineEdit(this);
m_abis->setEnabled(false);
@@ -323,9 +356,9 @@ DebuggerItemConfigWidget::DebuggerItemConfigWidget()
formLayout->addRow(m_cdbLabel);
formLayout->addRow(new QLabel(Tr::tr("Path:")), m_binaryChooser);
formLayout->addRow(new QLabel(Tr::tr("Type:")), m_typeLineEdit);
- formLayout->addRow(new QLabel(Tr::tr("ABIs:")), m_abis);
- formLayout->addRow(new QLabel(Tr::tr("Version:")), m_versionLabel);
- formLayout->addRow(new QLabel(Tr::tr("Working directory:")), m_workingDirectoryChooser);
+ formLayout->addRow(m_abisLabel, m_abis);
+ formLayout->addRow(m_versionLabel, m_version);
+ formLayout->addRow(m_workingDirectoryLabel, m_workingDirectoryChooser);
connect(m_binaryChooser, &PathChooser::textChanged,
this, &DebuggerItemConfigWidget::binaryPathHasChanged);
@@ -337,21 +370,24 @@ DebuggerItemConfigWidget::DebuggerItemConfigWidget()
DebuggerItem DebuggerItemConfigWidget::item() const
{
+ static const QRegularExpression noAbi("[^A-Za-z0-9-_]+");
+
DebuggerItem item(m_id);
item.setUnexpandedDisplayName(m_displayNameLineEdit->text());
item.setCommand(m_binaryChooser->filePath());
item.setWorkingDirectory(m_workingDirectoryChooser->filePath());
item.setAutoDetected(m_autodetected);
Abis abiList;
- const QStringList abis = m_abis->text().split(QRegularExpression("[^A-Za-z0-9-_]+"));
+ const QStringList abis = m_abis->text().split(noAbi);
for (const QString &a : abis) {
if (a.isNull())
continue;
abiList << Abi::fromString(a);
}
item.setAbis(abiList);
- item.setVersion(m_versionLabel->text());
+ item.setVersion(m_version->text());
item.setEngineType(m_engineType);
+ item.setGeneric(m_generic);
return item;
}
@@ -373,6 +409,7 @@ void DebuggerItemConfigWidget::load(const DebuggerItem *item)
return;
// Set values:
+ m_generic = item->isGeneric();
m_autodetected = item->isAutoDetected();
m_displayNameLineEdit->setEnabled(!item->isAutoDetected());
@@ -382,6 +419,15 @@ void DebuggerItemConfigWidget::load(const DebuggerItem *item)
m_binaryChooser->setReadOnly(item->isAutoDetected());
m_binaryChooser->setFilePath(item->command());
+ m_binaryChooser->setExpectedKind(m_generic ? PathChooser::Any : PathChooser::ExistingCommand);
+
+ m_abisLabel->setVisible(!m_generic);
+ m_abis->setVisible(!m_generic);
+ m_versionLabel->setVisible(!m_generic);
+ m_version->setVisible(!m_generic);
+ m_workingDirectoryLabel->setVisible(!m_generic);
+ m_workingDirectoryChooser->setVisible(!m_generic);
+
m_workingDirectoryChooser->setReadOnly(item->isAutoDetected());
m_workingDirectoryChooser->setFilePath(item->workingDirectory());
@@ -405,7 +451,7 @@ void DebuggerItemConfigWidget::load(const DebuggerItem *item)
m_cdbLabel->setText(text);
m_cdbLabel->setVisible(!text.isEmpty());
m_binaryChooser->setCommandVersionArguments(QStringList(versionCommand));
- m_versionLabel->setText(item->version());
+ m_version->setText(item->version());
setAbis(item->abiNames());
m_engineType = item->engineType();
m_id = item->id();
@@ -417,16 +463,18 @@ void DebuggerItemConfigWidget::binaryPathHasChanged()
if (!m_id.isValid())
return;
- DebuggerItem tmp;
- if (m_binaryChooser->filePath().isExecutableFile()) {
- tmp = item();
- tmp.reinitializeFromFile();
- }
+ if (!m_generic) {
+ DebuggerItem tmp;
+ if (m_binaryChooser->filePath().isExecutableFile()) {
+ tmp = item();
+ tmp.reinitializeFromFile();
+ }
- setAbis(tmp.abiNames());
- m_versionLabel->setText(tmp.version());
- m_engineType = tmp.engineType();
- m_typeLineEdit->setText(tmp.engineTypeName());
+ setAbis(tmp.abiNames());
+ m_version->setText(tmp.version());
+ m_engineType = tmp.engineType();
+ m_typeLineEdit->setText(tmp.engineTypeName());
+ }
store();
}
@@ -534,8 +582,10 @@ void DebuggerConfigWidget::cloneDebugger()
newItem.setUnexpandedDisplayName(d->uniqueDisplayName(Tr::tr("Clone of %1").arg(item->displayName())));
newItem.reinitializeFromFile();
newItem.setAutoDetected(false);
- d->m_model->addDebugger(newItem, true);
- m_debuggerView->setCurrentIndex(d->m_model->lastIndex());
+ newItem.setGeneric(item->isGeneric());
+ newItem.setEngineType(item->engineType());
+ auto addedItem = d->m_model->addDebugger(newItem, true);
+ m_debuggerView->setCurrentIndex(d->m_model->indexForItem(addedItem));
}
void DebuggerConfigWidget::addDebugger()
@@ -545,8 +595,8 @@ void DebuggerConfigWidget::addDebugger()
item.setEngineType(NoEngineType);
item.setUnexpandedDisplayName(d->uniqueDisplayName(Tr::tr("New Debugger")));
item.setAutoDetected(false);
- d->m_model->addDebugger(item, true);
- m_debuggerView->setCurrentIndex(d->m_model->lastIndex());
+ auto addedItem = d->m_model->addDebugger(item, true);
+ m_debuggerView->setCurrentIndex(d->m_model->indexForItem(addedItem));
}
void DebuggerConfigWidget::removeDebugger()
diff --git a/src/plugins/debugger/debuggerkitinformation.cpp b/src/plugins/debugger/debuggerkitinformation.cpp
index cac42d5a425..f3d01a1cc37 100644
--- a/src/plugins/debugger/debuggerkitinformation.cpp
+++ b/src/plugins/debugger/debuggerkitinformation.cpp
@@ -228,66 +228,6 @@ void DebuggerKitAspect::setup(Kit *k)
k->setValue(DebuggerKitAspect::id(), bestLevel != DebuggerItem::DoesNotMatch ? bestItem.id() : QVariant());
}
-
-// This handles the upgrade path from 2.8 to 3.0
-void DebuggerKitAspect::fix(Kit *k)
-{
- QTC_ASSERT(k, return);
-
- // This can be Id, binary path, but not "auto" anymore.
- const QVariant rawId = k->value(DebuggerKitAspect::id());
-
- if (rawId.toString().isEmpty()) // No debugger set, that is fine.
- return;
-
- if (rawId.type() == QVariant::String) {
- const DebuggerItem * const item = DebuggerItemManager::findById(rawId);
- if (!item) {
- qWarning("Unknown debugger id %s in kit %s",
- qPrintable(rawId.toString()), qPrintable(k->displayName()));
- k->setValue(DebuggerKitAspect::id(), QVariant());
- setup(k);
- return;
- }
-
- Abi kitAbi;
- if (ToolChainKitAspect::toolChains(k).isEmpty()) {
- if (DeviceTypeKitAspect::deviceTypeId(k)
- != ProjectExplorer::Constants::DESKTOP_DEVICE_TYPE) {
- return;
- }
- kitAbi = Abi(Abi::UnknownArchitecture, Abi::hostAbi().os());
- } else {
- kitAbi = ToolChainKitAspect::targetAbi(k);
- }
- if (item->matchTarget(kitAbi) != DebuggerItem::DoesNotMatch)
- return;
- k->setValue(DebuggerKitAspect::id(), QVariant());
- setup(k);
- return; // All fine (now).
- }
-
- QMap<QString, QVariant> map = rawId.toMap();
- QString binary = map.value("Binary").toString();
- if (binary == "auto") {
- // This should not happen as "auto" is handled by setup() already.
- QTC_CHECK(false);
- k->setValue(DebuggerKitAspect::id(), QVariant());
- return;
- }
-
- FilePath fileName = FilePath::fromUserInput(binary);
- const DebuggerItem *item = DebuggerItemManager::findByCommand(fileName);
- if (!item) {
- qWarning("Debugger command %s invalid in kit %s",
- qPrintable(binary), qPrintable(k->displayName()));
- k->setValue(DebuggerKitAspect::id(), QVariant());
- return;
- }
-
- k->setValue(DebuggerKitAspect::id(), item->id());
-}
-
// Check the configuration errors and return a flag mask. Provide a quick check and
// a verbose one with a list of errors.
@@ -299,15 +239,15 @@ DebuggerKitAspect::ConfigurationErrors DebuggerKitAspect::configurationErrors(co
if (!item)
return NoDebugger;
- if (item->command().isEmpty())
+ const FilePath debugger = item->command();
+ if (debugger.isEmpty())
return NoDebugger;
+ if (debugger.isRelativePath())
+ return NoConfigurationError;
+
ConfigurationErrors result = NoConfigurationError;
- const FilePath debugger = item->command();
- const bool found = debugger.exists() && !debugger.isDir();
- if (!found)
- result |= DebuggerNotFound;
- else if (!debugger.isExecutableFile())
+ if (!debugger.isExecutableFile())
result |= DebuggerNotExecutable;
const Abi tcAbi = ToolChainKitAspect::targetAbi(k);
@@ -318,16 +258,15 @@ DebuggerKitAspect::ConfigurationErrors DebuggerKitAspect::configurationErrors(co
result |= DebuggerDoesNotMatch;
}
- if (!found) {
- if (item->engineType() == NoEngineType)
- return NoDebugger;
+ if (item->engineType() == NoEngineType)
+ return NoDebugger;
- // We need an absolute path to be able to locate Python on Windows.
- if (item->engineType() == GdbEngineType) {
- if (tcAbi.os() == Abi::WindowsOS && !debugger.isAbsolutePath())
- result |= DebuggerNeedsAbsolutePath;
- }
+ // We need an absolute path to be able to locate Python on Windows.
+ if (item->engineType() == GdbEngineType) {
+ if (tcAbi.os() == Abi::WindowsOS && !debugger.isAbsolutePath())
+ result |= DebuggerNeedsAbsolutePath;
}
+
return result;
}
@@ -342,7 +281,12 @@ Runnable DebuggerKitAspect::runnable(const Kit *kit)
{
Runnable runnable;
if (const DebuggerItem *item = debugger(kit)) {
- runnable.command = CommandLine{item->command()};
+ FilePath cmd = item->command();
+ if (cmd.isRelativePath()) {
+ if (const IDeviceConstPtr buildDevice = BuildDeviceKitAspect::device(kit))
+ cmd = buildDevice->searchExecutableInPath(cmd.path());
+ }
+ runnable.command.setExecutable(cmd);
runnable.workingDirectory = item->workingDirectory();
runnable.environment = kit->runEnvironment();
runnable.environment.set("LC_NUMERIC", "C");
diff --git a/src/plugins/debugger/debuggerkitinformation.h b/src/plugins/debugger/debuggerkitinformation.h
index eb6ad1d58aa..548f76793bb 100644
--- a/src/plugins/debugger/debuggerkitinformation.h
+++ b/src/plugins/debugger/debuggerkitinformation.h
@@ -21,7 +21,6 @@ public:
{ return DebuggerKitAspect::validateDebugger(k); }
void setup(ProjectExplorer::Kit *k) override;
- void fix(ProjectExplorer::Kit *k) override;
static const DebuggerItem *debugger(const ProjectExplorer::Kit *kit);
static ProjectExplorer::Runnable runnable(const ProjectExplorer::Kit *kit);
diff --git a/src/plugins/debugger/debuggerplugin.cpp b/src/plugins/debugger/debuggerplugin.cpp
index 8ab67c0bc54..e8c14d4dccf 100644
--- a/src/plugins/debugger/debuggerplugin.cpp
+++ b/src/plugins/debugger/debuggerplugin.cpp
@@ -67,9 +67,10 @@
#include <projectexplorer/projectexplorericons.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/projectexplorersettings.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/projecttree.h>
#include <projectexplorer/runconfiguration.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <projectexplorer/taskhub.h>
#include <projectexplorer/toolchain.h>
@@ -1187,7 +1188,7 @@ DebuggerPluginPrivate::DebuggerPluginPrivate(const QStringList &arguments)
setInitialState();
- connect(SessionManager::instance(), &SessionManager::startupProjectChanged,
+ connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged,
this, &DebuggerPluginPrivate::onStartupProjectChanged);
connect(EngineManager::instance(), &EngineManager::engineStateChanged,
this, &DebuggerPluginPrivate::updatePresetState);
@@ -1394,8 +1395,8 @@ void DebuggerPluginPrivate::updatePresetState()
if (m_shuttingDown)
return;
- Project *startupProject = SessionManager::startupProject();
- RunConfiguration *startupRunConfig = SessionManager::startupRunConfiguration();
+ Project *startupProject = ProjectManager::startupProject();
+ RunConfiguration *startupRunConfig = ProjectManager::startupRunConfiguration();
DebuggerEngine *currentEngine = EngineManager::currentEngine();
QString whyNot;
@@ -1997,7 +1998,7 @@ void DebuggerPluginPrivate::aboutToShutdown()
{
m_shuttingDown = true;
- disconnect(SessionManager::instance(), &SessionManager::startupProjectChanged, this, nullptr);
+ disconnect(ProjectManager::instance(), &ProjectManager::startupProjectChanged, this, nullptr);
m_shutdownTimer.setInterval(0);
m_shutdownTimer.setSingleShot(true);
@@ -2165,7 +2166,7 @@ static bool buildTypeAccepted(QFlags<ToolMode> toolMode, BuildConfiguration::Bui
static BuildConfiguration::BuildType startupBuildType()
{
BuildConfiguration::BuildType buildType = BuildConfiguration::Unknown;
- if (RunConfiguration *runConfig = SessionManager::startupRunConfiguration()) {
+ if (RunConfiguration *runConfig = ProjectManager::startupRunConfiguration()) {
if (const BuildConfiguration *buildConfig = runConfig->target()->activeBuildConfiguration())
buildType = buildConfig->buildType();
}
@@ -2335,12 +2336,12 @@ void DebuggerUnitTests::testStateMachine()
QEventLoop loop;
connect(BuildManager::instance(), &BuildManager::buildQueueFinished,
&loop, &QEventLoop::quit);
- BuildManager::buildProjectWithDependencies(SessionManager::startupProject());
+ BuildManager::buildProjectWithDependencies(ProjectManager::startupProject());
loop.exec();
ExecuteOnDestruction guard([] { EditorManager::closeAllEditors(false); });
- RunConfiguration *rc = SessionManager::startupRunConfiguration();
+ RunConfiguration *rc = ProjectManager::startupRunConfiguration();
QVERIFY(rc);
auto runControl = new RunControl(ProjectExplorer::Constants::DEBUG_RUN_MODE);
diff --git a/src/plugins/debugger/debuggerruncontrol.cpp b/src/plugins/debugger/debuggerruncontrol.cpp
index 8b6ac29c995..d972413bbb0 100644
--- a/src/plugins/debugger/debuggerruncontrol.cpp
+++ b/src/plugins/debugger/debuggerruncontrol.cpp
@@ -23,8 +23,9 @@
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectexplorericons.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/runconfigurationaspects.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <projectexplorer/taskhub.h>
#include <projectexplorer/toolchain.h>
@@ -182,8 +183,8 @@ void DebuggerRunTool::setStartMode(DebuggerStartMode startMode)
// FIXME: This is horribly wrong.
// get files from all the projects in the session
- QList<Project *> projects = SessionManager::projects();
- if (Project *startupProject = SessionManager::startupProject()) {
+ QList<Project *> projects = ProjectManager::projects();
+ if (Project *startupProject = ProjectManager::startupProject()) {
// startup project first
projects.removeOne(startupProject);
projects.insert(0, startupProject);
@@ -900,6 +901,9 @@ DebuggerRunTool::DebuggerRunTool(RunControl *runControl, AllowTerminal allowTerm
if (Project *project = runControl->project()) {
m_runParameters.projectSourceDirectory = project->projectDirectory();
m_runParameters.projectSourceFiles = project->files(Project::SourceFiles);
+ } else {
+ m_runParameters.projectSourceDirectory = m_runParameters.debugger.command.executable().parentDir();
+ m_runParameters.projectSourceFiles.clear();
}
m_runParameters.toolChainAbi = ToolChainKitAspect::targetAbi(kit);
diff --git a/src/plugins/debugger/gdb/gdbengine.cpp b/src/plugins/debugger/gdb/gdbengine.cpp
index 9f8dbac6619..8a98c0e4337 100644
--- a/src/plugins/debugger/gdb/gdbengine.cpp
+++ b/src/plugins/debugger/gdb/gdbengine.cpp
@@ -470,7 +470,8 @@ void GdbEngine::handleAsyncOutput(const QStringView asyncClass, const GdbMi &res
module.startAddress = 0;
module.endAddress = 0;
module.hostPath = result["host-name"].data();
- module.modulePath = result["target-name"].data();
+ const QString target = result["target-name"].data();
+ module.modulePath = runParameters().inferior.command.executable().withNewPath(target);
module.moduleName = QFileInfo(module.hostPath).baseName();
modulesHandler()->updateModule(module);
} else if (asyncClass == u"library-unloaded") {
@@ -478,7 +479,8 @@ void GdbEngine::handleAsyncOutput(const QStringView asyncClass, const GdbMi &res
// target-name="/usr/lib/libdrm.so.2",
// host-name="/usr/lib/libdrm.so.2"
QString id = result["id"].data();
- modulesHandler()->removeModule(result["target-name"].data());
+ const QString target = result["target-name"].data();
+ modulesHandler()->removeModule(runParameters().inferior.command.executable().withNewPath(target));
progressPing();
showStatusMessage(Tr::tr("Library %1 unloaded.").arg(id), 1000);
} else if (asyncClass == u"thread-group-added") {
@@ -537,6 +539,7 @@ void GdbEngine::handleAsyncOutput(const QStringView asyncClass, const GdbMi &res
ba.remove(pos1, pos3 - pos1 + 1);
GdbMi res;
res.fromString(ba);
+ const FilePath &fileRoot = runParameters().projectSourceDirectory;
BreakHandler *handler = breakHandler();
Breakpoint bp;
for (const GdbMi &bkpt : res) {
@@ -545,13 +548,13 @@ void GdbEngine::handleAsyncOutput(const QStringView asyncClass, const GdbMi &res
// A sub-breakpoint.
QTC_ASSERT(bp, continue);
SubBreakpoint loc = bp->findOrCreateSubBreakpoint(nr);
- loc->params.updateFromGdbOutput(bkpt);
+ loc->params.updateFromGdbOutput(bkpt, fileRoot);
loc->params.type = bp->type();
} else {
// A primary breakpoint.
bp = handler->findBreakpointByResponseId(nr);
if (bp)
- bp->updateFromGdbOutput(bkpt);
+ bp->updateFromGdbOutput(bkpt, fileRoot);
}
}
if (bp)
@@ -567,7 +570,7 @@ void GdbEngine::handleAsyncOutput(const QStringView asyncClass, const GdbMi &res
const QString nr = bkpt["number"].data();
BreakpointParameters br;
br.type = BreakpointByFileAndLine;
- br.updateFromGdbOutput(bkpt);
+ br.updateFromGdbOutput(bkpt, runParameters().projectSourceDirectory);
handler->handleAlienBreakpoint(nr, br);
}
} else if (asyncClass == u"breakpoint-deleted") {
@@ -1026,7 +1029,7 @@ void GdbEngine::handleQuerySources(const DebuggerResponse &response)
{
m_sourcesListUpdating = false;
if (response.resultClass == ResultDone) {
- QMap<QString, QString> oldShortToFull = m_shortToFullName;
+ QMap<QString, FilePath> oldShortToFull = m_shortToFullName;
m_shortToFullName.clear();
m_fullToShortName.clear();
// "^done,files=[{file="../../../../bin/dumper/dumper.cpp",
@@ -1037,7 +1040,7 @@ void GdbEngine::handleQuerySources(const DebuggerResponse &response)
continue;
GdbMi fullName = item["fullname"];
QString file = fileName.data();
- QString full;
+ FilePath full;
if (fullName.isValid()) {
full = cleanupFullName(fullName.data());
m_fullToShortName[full] = file;
@@ -1102,9 +1105,9 @@ void GdbEngine::handleStopResponse(const GdbMi &data)
// Ignore trap on Windows terminals, which results in
// spurious "* stopped" message.
if (m_expectTerminalTrap) {
- m_expectTerminalTrap = false;
if ((!data.isValid() || !data["reason"].isValid())
&& Abi::hostAbi().os() == Abi::WindowsOS) {
+ m_expectTerminalTrap = false;
showMessage("IGNORING TERMINAL SIGTRAP", LogMisc);
return;
}
@@ -1181,7 +1184,7 @@ void GdbEngine::handleStopResponse(const GdbMi &data)
const QString nr = data["bkptno"].data();
int lineNumber = 0;
- QString fullName;
+ FilePath fullName;
QString function;
QString language;
if (frame.isValid()) {
@@ -1194,15 +1197,13 @@ void GdbEngine::handleStopResponse(const GdbMi &data)
lineNumber = lineNumberG.toInt();
fullName = cleanupFullName(frame["fullname"].data());
if (fullName.isEmpty())
- fullName = frame["file"].data();
+ fullName = runParameters().projectSourceDirectory.withNewPath(frame["file"].data());
} // found line number
} else {
showMessage("INVALID STOPPED REASON", LogWarning);
}
- const FilePath onDevicePath = FilePath::fromString(fullName).onDevice(
- runParameters().debugger.command.executable());
- const FilePath fileName = onDevicePath.localSource().value_or(onDevicePath);
+ const FilePath fileName = fullName.localSource().value_or(fullName);
if (!nr.isEmpty() && frame.isValid()) {
// Use opportunity to update the breakpoint marker position.
@@ -1420,14 +1421,19 @@ void GdbEngine::handleStop2(const GdbMi &data)
} else if (m_isQnxGdb && name == "0" && meaning == "Signal 0") {
showMessage("SIGNAL 0 CONSIDERED BOGUS.");
} else {
- showMessage("HANDLING SIGNAL " + name);
- if (debuggerSettings()->useMessageBoxForSignals.value() && !isStopperThread)
- if (!showStoppedBySignalMessageBox(meaning, name)) {
- showMessage("SIGNAL RECEIVED WHILE SHOWING SIGNAL MESSAGE");
- return;
- }
- if (!name.isEmpty() && !meaning.isEmpty())
- reasontr = msgStoppedBySignal(meaning, name);
+ if (terminal() && name == "SIGCONT" && m_expectTerminalTrap) {
+ continueInferior();
+ m_expectTerminalTrap = false;
+ } else {
+ showMessage("HANDLING SIGNAL " + name);
+ if (debuggerSettings()->useMessageBoxForSignals.value() && !isStopperThread)
+ if (!showStoppedBySignalMessageBox(meaning, name)) {
+ showMessage("SIGNAL RECEIVED WHILE SHOWING SIGNAL MESSAGE");
+ return;
+ }
+ if (!name.isEmpty() && !meaning.isEmpty())
+ reasontr = msgStoppedBySignal(meaning, name);
+ }
}
}
if (reason.isEmpty())
@@ -1558,50 +1564,47 @@ void GdbEngine::handleExecuteContinue(const DebuggerResponse &response)
}
}
-QString GdbEngine::fullName(const QString &fileName)
+FilePath GdbEngine::fullName(const QString &fileName)
{
if (fileName.isEmpty())
- return QString();
- QTC_ASSERT(!m_sourcesListUpdating, /* */);
- return m_shortToFullName.value(fileName, QString());
+ return {};
+ QTC_CHECK(!m_sourcesListUpdating);
+ return m_shortToFullName.value(fileName, {});
}
-QString GdbEngine::cleanupFullName(const QString &fileName)
+FilePath GdbEngine::cleanupFullName(const QString &fileName)
{
- QString cleanFilePath = fileName;
+ FilePath cleanFilePath =
+ runParameters().projectSourceDirectory.withNewPath(fileName).cleanPath();
// Gdb running on windows often delivers "fullnames" which
// (a) have no drive letter and (b) are not normalized.
if (Abi::hostAbi().os() == Abi::WindowsOS) {
if (fileName.isEmpty())
- return QString();
- QFileInfo fi(fileName);
- if (fi.isReadable())
- cleanFilePath = QDir::cleanPath(fi.absoluteFilePath());
+ return {};
}
if (!debuggerSettings()->autoEnrichParameters.value())
return cleanFilePath;
- const QString sysroot = runParameters().sysRoot.toString();
- if (QFileInfo(cleanFilePath).isReadable())
+ if (cleanFilePath.isReadableFile())
return cleanFilePath;
+
+ const FilePath sysroot = runParameters().sysRoot;
if (!sysroot.isEmpty() && fileName.startsWith('/')) {
- cleanFilePath = sysroot + fileName;
- if (QFileInfo(cleanFilePath).isReadable())
+ cleanFilePath = sysroot.pathAppended(fileName.mid(1));
+ if (cleanFilePath.isReadableFile())
return cleanFilePath;
}
if (m_baseNameToFullName.isEmpty()) {
- FilePath filePath = FilePath::fromString(sysroot + "/usr/src/debug");
+ FilePath filePath = sysroot.pathAppended("/usr/src/debug");
if (filePath.isDir()) {
filePath.iterateDirectory(
[this](const FilePath &filePath) {
QString name = filePath.fileName();
- if (!name.startsWith('.')) {
- QString path = filePath.path();
- m_baseNameToFullName.insert(name, path);
- }
+ if (!name.startsWith('.'))
+ m_baseNameToFullName.insert(name, filePath);
return IterationPolicy::Continue;
},
{{"*"}, QDir::NoFilter, QDirIterator::Subdirectories});
@@ -1611,7 +1614,7 @@ QString GdbEngine::cleanupFullName(const QString &fileName)
cleanFilePath.clear();
const QString base = FilePath::fromUserInput(fileName).fileName();
- QMultiMap<QString, QString>::const_iterator jt = m_baseNameToFullName.constFind(base);
+ auto jt = m_baseNameToFullName.constFind(base);
while (jt != m_baseNameToFullName.constEnd() && jt.key() == base) {
// FIXME: Use some heuristics to find the "best" match.
return jt.value();
@@ -1948,7 +1951,7 @@ void GdbEngine::executeRunToLine(const ContextData &data)
if (data.address)
loc = addressSpec(data.address);
else
- loc = '"' + breakLocation(data.fileName.toString()) + '"' + ':' + QString::number(data.lineNumber);
+ loc = '"' + breakLocation(data.fileName) + '"' + ':' + QString::number(data.lineNumber);
runCommand({"tbreak " + loc});
runCommand({"continue", NativeCommand|RunRequest, CB(handleExecuteRunToLine)});
@@ -1976,7 +1979,7 @@ void GdbEngine::executeJumpToLine(const ContextData &data)
if (data.address)
loc = addressSpec(data.address);
else
- loc = '"' + breakLocation(data.fileName.toString()) + '"' + ':' + QString::number(data.lineNumber);
+ loc = '"' + breakLocation(data.fileName) + '"' + ':' + QString::number(data.lineNumber);
runCommand({"tbreak " + loc});
notifyInferiorRunRequested();
@@ -2050,11 +2053,11 @@ void GdbEngine::setTokenBarrier()
//
//////////////////////////////////////////////////////////////////////
-QString GdbEngine::breakLocation(const QString &file) const
+QString GdbEngine::breakLocation(const FilePath &file) const
{
QString where = m_fullToShortName.value(file);
if (where.isEmpty())
- return FilePath::fromString(file).fileName();
+ return file.fileName();
return where;
}
@@ -2078,7 +2081,7 @@ QString GdbEngine::breakpointLocation(const BreakpointParameters &data)
usage = BreakpointUseShortPath;
const QString fileName = usage == BreakpointUseFullPath
- ? data.fileName.toString() : breakLocation(data.fileName.toString());
+ ? data.fileName.path() : breakLocation(data.fileName);
// The argument is simply a C-quoted version of the argument to the
// non-MI "break" command, including the "original" quoting it wants.
return "\"\\\"" + GdbMi::escapeCString(fileName) + "\\\":"
@@ -2092,7 +2095,7 @@ QString GdbEngine::breakpointLocation2(const BreakpointParameters &data)
usage = BreakpointUseShortPath;
const QString fileName = usage == BreakpointUseFullPath
- ? data.fileName.toString() : breakLocation(data.fileName.toString());
+ ? data.fileName.path() : breakLocation(data.fileName);
return GdbMi::escapeCString(fileName) + ':' + QString::number(data.lineNumber);
}
@@ -2105,7 +2108,7 @@ void GdbEngine::handleInsertInterpreterBreakpoint(const DebuggerResponse &respon
notifyBreakpointInsertOk(bp);
} else {
bp->setResponseId(response.data["number"].data());
- bp->updateFromGdbOutput(response.data);
+ bp->updateFromGdbOutput(response.data, runParameters().projectSourceDirectory);
notifyBreakpointInsertOk(bp);
}
}
@@ -2115,7 +2118,7 @@ void GdbEngine::handleInterpreterBreakpointModified(const GdbMi &data)
int modelId = data["modelid"].toInt();
Breakpoint bp = breakHandler()->findBreakpointByModelId(modelId);
QTC_ASSERT(bp, return);
- bp->updateFromGdbOutput(data);
+ bp->updateFromGdbOutput(data, runParameters().projectSourceDirectory);
}
void GdbEngine::handleWatchInsert(const DebuggerResponse &response, const Breakpoint &bp)
@@ -2165,7 +2168,7 @@ void GdbEngine::handleBkpt(const GdbMi &bkpt, const Breakpoint &bp)
// A sub-breakpoint.
SubBreakpoint sub = bp->findOrCreateSubBreakpoint(nr);
QTC_ASSERT(sub, return);
- sub->params.updateFromGdbOutput(bkpt);
+ sub->params.updateFromGdbOutput(bkpt, runParameters().projectSourceDirectory);
sub->params.type = bp->type();
if (usePseudoTracepoints && bp->isTracepoint()) {
sub->params.tracepoint = true;
@@ -2183,7 +2186,7 @@ void GdbEngine::handleBkpt(const GdbMi &bkpt, const Breakpoint &bp)
const QString subnr = location["number"].data();
SubBreakpoint sub = bp->findOrCreateSubBreakpoint(subnr);
QTC_ASSERT(sub, return);
- sub->params.updateFromGdbOutput(location);
+ sub->params.updateFromGdbOutput(location, runParameters().projectSourceDirectory);
sub->params.type = bp->type();
if (usePseudoTracepoints && bp->isTracepoint()) {
sub->params.tracepoint = true;
@@ -2194,7 +2197,7 @@ void GdbEngine::handleBkpt(const GdbMi &bkpt, const Breakpoint &bp)
// A (the?) primary breakpoint.
bp->setResponseId(nr);
- bp->updateFromGdbOutput(bkpt);
+ bp->updateFromGdbOutput(bkpt, runParameters().projectSourceDirectory);
if (usePseudoTracepoints && bp->isTracepoint())
bp->setMessage(bp->requestedParameters().message);
}
@@ -2501,7 +2504,7 @@ void GdbEngine::handleTracepointModified(const GdbMi &data)
// A sub-breakpoint.
QTC_ASSERT(bp, continue);
SubBreakpoint loc = bp->findOrCreateSubBreakpoint(nr);
- loc->params.updateFromGdbOutput(bkpt);
+ loc->params.updateFromGdbOutput(bkpt, runParameters().projectSourceDirectory);
loc->params.type = bp->type();
if (bp->isTracepoint()) {
loc->params.tracepoint = true;
@@ -2511,7 +2514,7 @@ void GdbEngine::handleTracepointModified(const GdbMi &data)
// A primary breakpoint.
bp = handler->findBreakpointByResponseId(nr);
if (bp)
- bp->updateFromGdbOutput(bkpt);
+ bp->updateFromGdbOutput(bkpt, runParameters().projectSourceDirectory);
}
}
QTC_ASSERT(bp, return);
@@ -2783,10 +2786,10 @@ static QString dotEscape(QString str)
return str;
}
-void GdbEngine::loadSymbols(const QString &modulePath)
+void GdbEngine::loadSymbols(const FilePath &modulePath)
{
// FIXME: gdb does not understand quoted names here (tested with 6.8)
- runCommand({"sharedlibrary " + dotEscape(modulePath)});
+ runCommand({"sharedlibrary " + dotEscape(modulePath.path())});
reloadModulesInternal();
reloadStack();
updateLocals();
@@ -2811,7 +2814,7 @@ void GdbEngine::loadSymbolsForStack()
for (const Module &module : modules) {
if (module.startAddress <= frame.address
&& frame.address < module.endAddress) {
- runCommand({"sharedlibrary " + dotEscape(module.modulePath)});
+ runCommand({"sharedlibrary " + dotEscape(module.modulePath.path())});
needUpdate = true;
}
}
@@ -2824,7 +2827,7 @@ void GdbEngine::loadSymbolsForStack()
}
static void handleShowModuleSymbols(const DebuggerResponse &response,
- const QString &modulePath, const QString &fileName)
+ const FilePath &modulePath, const QString &fileName)
{
if (response.resultClass == ResultDone) {
Symbols symbols;
@@ -2883,21 +2886,21 @@ static void handleShowModuleSymbols(const DebuggerResponse &response,
}
}
-void GdbEngine::requestModuleSymbols(const QString &modulePath)
+void GdbEngine::requestModuleSymbols(const FilePath &modulePath)
{
- Utils::TemporaryFile tf("gdbsymbols");
+ TemporaryFile tf("gdbsymbols");
if (!tf.open())
return;
QString fileName = tf.fileName();
tf.close();
- DebuggerCommand cmd("maint print msymbols \"" + fileName + "\" " + modulePath, NeedsTemporaryStop);
+ DebuggerCommand cmd("maint print msymbols \"" + fileName + "\" " + modulePath.path(), NeedsTemporaryStop);
cmd.callback = [modulePath, fileName](const DebuggerResponse &r) {
handleShowModuleSymbols(r, modulePath, fileName);
};
runCommand(cmd);
}
-void GdbEngine::requestModuleSections(const QString &moduleName)
+void GdbEngine::requestModuleSections(const FilePath &moduleName)
{
// There seems to be no way to get the symbols from a single .so.
DebuggerCommand cmd("maint info section ALLOBJ", NeedsTemporaryStop);
@@ -2908,14 +2911,14 @@ void GdbEngine::requestModuleSections(const QString &moduleName)
}
void GdbEngine::handleShowModuleSections(const DebuggerResponse &response,
- const QString &moduleName)
+ const FilePath &moduleName)
{
// ~" Object file: /usr/lib/i386-linux-gnu/libffi.so.6\n"
// ~" 0xb44a6114->0xb44a6138 at 0x00000114: .note.gnu.build-id ALLOC LOAD READONLY DATA HAS_CONTENTS\n"
if (response.resultClass == ResultDone) {
const QStringList lines = response.consoleStreamOutput.split('\n');
const QString prefix = " Object file: ";
- const QString needle = prefix + moduleName;
+ const QString needle = prefix + moduleName.path();
Sections sections;
bool active = false;
for (const QString &line : std::as_const(lines)) {
@@ -2956,11 +2959,6 @@ void GdbEngine::reloadModulesInternal()
runCommand({"info shared", NeedsTemporaryStop, CB(handleModulesList)});
}
-static QString nameFromPath(const QString &path)
-{
- return QFileInfo(path).baseName();
-}
-
void GdbEngine::handleModulesList(const DebuggerResponse &response)
{
if (response.resultClass == ResultDone) {
@@ -2971,14 +2969,15 @@ void GdbEngine::handleModulesList(const DebuggerResponse &response)
QString data = response.consoleStreamOutput;
QTextStream ts(&data, QIODevice::ReadOnly);
bool found = false;
+ const FilePath inferior = runParameters().inferior.command.executable();
while (!ts.atEnd()) {
QString line = ts.readLine();
QString symbolsRead;
QTextStream ts(&line, QIODevice::ReadOnly);
if (line.startsWith("0x")) {
ts >> module.startAddress >> module.endAddress >> symbolsRead;
- module.modulePath = ts.readLine().trimmed();
- module.moduleName = nameFromPath(module.modulePath);
+ module.modulePath = inferior.withNewPath(ts.readLine().trimmed());
+ module.moduleName = module.modulePath.baseName();
module.symbolsRead =
(symbolsRead == "Yes" ? Module::ReadOk : Module::ReadFailed);
handler->updateModule(module);
@@ -2989,8 +2988,8 @@ void GdbEngine::handleModulesList(const DebuggerResponse &response)
QTC_ASSERT(symbolsRead == "No", continue);
module.startAddress = 0;
module.endAddress = 0;
- module.modulePath = ts.readLine().trimmed();
- module.moduleName = nameFromPath(module.modulePath);
+ module.modulePath = inferior.withNewPath(ts.readLine().trimmed());
+ module.moduleName = module.modulePath.baseName();
handler->updateModule(module);
found = true;
}
@@ -3002,8 +3001,8 @@ void GdbEngine::handleModulesList(const DebuggerResponse &response)
// loaded_addr="0x8fe00000",slide="0x0",prefix="__dyld_"},
// shlib-info={...}...
for (const GdbMi &item : response.data) {
- module.modulePath = item["path"].data();
- module.moduleName = nameFromPath(module.modulePath);
+ module.modulePath = inferior.withNewPath(item["path"].data());
+ module.moduleName = module.modulePath.baseName();
module.symbolsRead = (item["state"].data() == "Y")
? Module::ReadOk : Module::ReadFailed;
module.startAddress =
@@ -3038,7 +3037,7 @@ void GdbEngine::reloadSourceFiles()
cmd.callback = [this](const DebuggerResponse &response) {
m_sourcesListUpdating = false;
if (response.resultClass == ResultDone) {
- QMap<QString, QString> oldShortToFull = m_shortToFullName;
+ QMap<QString, FilePath> oldShortToFull = m_shortToFullName;
m_shortToFullName.clear();
m_fullToShortName.clear();
// "^done,files=[{file="../../../../bin/dumper/dumper.cpp",
@@ -3049,7 +3048,7 @@ void GdbEngine::reloadSourceFiles()
continue;
GdbMi fullName = item["fullname"];
QString file = fileName.data();
- QString full;
+ FilePath full;
if (fullName.isValid()) {
full = cleanupFullName(fullName.data());
m_fullToShortName[full] = file;
@@ -3922,7 +3921,7 @@ void GdbEngine::handleGdbStarted()
Module module;
module.startAddress = 0;
module.endAddress = 0;
- module.modulePath = rp.inferior.command.executable().toString();
+ module.modulePath = rp.inferior.command.executable();
module.moduleName = "<executable>";
modulesHandler()->updateModule(module);
@@ -4369,7 +4368,7 @@ void GdbEngine::setupInferior()
setLinuxOsAbi();
QString symbolFile;
if (!rp.symbolFile.isEmpty())
- symbolFile = rp.symbolFile.toFileInfo().absoluteFilePath();
+ symbolFile = rp.symbolFile.absoluteFilePath().path();
//const QByteArray sysroot = sp.sysroot.toLocal8Bit();
//const QByteArray remoteArch = sp.remoteArchitecture.toLatin1();
@@ -4558,7 +4557,13 @@ void GdbEngine::handleLocalAttach(const DebuggerResponse &response)
switch (response.resultClass) {
case ResultDone:
case ResultRunning:
+ {
showMessage("INFERIOR ATTACHED");
+
+ QString commands = expand(debuggerSettings()->gdbPostAttachCommands.value());
+ if (!commands.isEmpty())
+ runCommand({commands, NativeCommand});
+
if (state() == EngineRunRequested) {
// Happens e.g. for "Attach to unstarted application"
// We will get a '*stopped' later that we'll interpret as 'spontaneous'
@@ -4578,6 +4583,7 @@ void GdbEngine::handleLocalAttach(const DebuggerResponse &response)
updateAll();
}
break;
+ }
case ResultError:
if (response.data["msg"].data() == "ptrace: Operation not permitted.") {
QString msg = msgPtraceError(runParameters().startMode);
@@ -4732,6 +4738,13 @@ void GdbEngine::handleExecRun(const DebuggerResponse &response)
CHECK_STATE(EngineRunRequested);
if (response.resultClass == ResultRunning) {
+
+ if (isLocalRunEngine()) {
+ QString commands = expand(debuggerSettings()->gdbPostAttachCommands.value());
+ if (!commands.isEmpty())
+ runCommand({commands, NativeCommand});
+ }
+
notifyEngineRunAndInferiorRunOk();
showMessage("INFERIOR STARTED");
showMessage(msgInferiorSetupOk(), StatusBar);
diff --git a/src/plugins/debugger/gdb/gdbengine.h b/src/plugins/debugger/gdb/gdbengine.h
index cd3a21ffbde..5e84366b649 100644
--- a/src/plugins/debugger/gdb/gdbengine.h
+++ b/src/plugins/debugger/gdb/gdbengine.h
@@ -210,23 +210,23 @@ private: ////////// General Interface //////////
void handleBkpt(const GdbMi &bkpt, const Breakpoint &bp);
QString breakpointLocation(const BreakpointParameters &data); // For gdb/MI.
QString breakpointLocation2(const BreakpointParameters &data); // For gdb/CLI fallback.
- QString breakLocation(const QString &file) const;
+ QString breakLocation(const Utils::FilePath &file) const;
void updateTracepointCaptures(const Breakpoint &bp);
//
// Modules specific stuff
//
- void loadSymbols(const QString &moduleName) final;
+ void loadSymbols(const Utils::FilePath &moduleName) final;
void loadAllSymbols() final;
void loadSymbolsForStack() final;
- void requestModuleSymbols(const QString &moduleName) final;
- void requestModuleSections(const QString &moduleName) final;
+ void requestModuleSymbols(const Utils::FilePath &moduleName) final;
+ void requestModuleSections(const Utils::FilePath &moduleName) final;
void reloadModules() final;
void examineModules() final;
void reloadModulesInternal();
void handleModulesList(const DebuggerResponse &response);
- void handleShowModuleSections(const DebuggerResponse &response, const QString &moduleName);
+ void handleShowModuleSections(const DebuggerResponse &response, const Utils::FilePath &moduleName);
//
// Snapshot specific stuff
@@ -265,13 +265,13 @@ private: ////////// General Interface //////////
void reloadSourceFilesInternal();
void handleQuerySources(const DebuggerResponse &response);
- QString fullName(const QString &fileName);
- QString cleanupFullName(const QString &fileName);
+ Utils::FilePath fullName(const QString &fileName);
+ Utils::FilePath cleanupFullName(const QString &fileName);
// awful hack to keep track of used files
- QMap<QString, QString> m_shortToFullName;
- QMap<QString, QString> m_fullToShortName;
- QMultiMap<QString, QString> m_baseNameToFullName;
+ QMap<QString, Utils::FilePath> m_shortToFullName;
+ QMap<Utils::FilePath, QString> m_fullToShortName;
+ QMultiMap<QString, Utils::FilePath> m_baseNameToFullName;
bool m_sourcesListUpdating = false;
diff --git a/src/plugins/debugger/lldb/lldbengine.cpp b/src/plugins/debugger/lldb/lldbengine.cpp
index 3283d5ff7d5..f2c7c23744c 100644
--- a/src/plugins/debugger/lldb/lldbengine.cpp
+++ b/src/plugins/debugger/lldb/lldbengine.cpp
@@ -267,7 +267,8 @@ void LldbEngine::handleLldbStarted()
cmd2.arg("nativemixed", isNativeMixedActive());
cmd2.arg("workingdirectory", rp.inferior.workingDirectory.path());
cmd2.arg("environment", rp.inferior.environment.toStringList());
- cmd2.arg("processargs", toHex(ProcessArgs::splitArgs(rp.inferior.command.arguments()).join(QChar(0))));
+ cmd2.arg("processargs", toHex(ProcessArgs::splitArgs(rp.inferior.command.arguments(),
+ HostOsInfo::hostOs()).join(QChar(0))));
cmd2.arg("platform", rp.platform);
cmd2.arg("symbolfile", rp.symbolFile.path());
@@ -278,8 +279,8 @@ void LldbEngine::handleLldbStarted()
? QString::fromLatin1("Attaching to %1 (%2)").arg(attachedPID).arg(attachedMainThreadID)
: QString::fromLatin1("Attaching to %1").arg(attachedPID);
showMessage(msg, LogMisc);
+ cmd2.arg("startmode", DebuggerStartMode::AttachToLocalProcess);
cmd2.arg("attachpid", attachedPID);
-
} else {
cmd2.arg("startmode", rp.startMode);
// it is better not to check the start mode on the python sid (as we would have to duplicate the
@@ -629,7 +630,7 @@ void LldbEngine::handleInterpreterBreakpointModified(const GdbMi &bpItem)
updateBreakpointData(bp, bpItem, false);
}
-void LldbEngine::loadSymbols(const QString &moduleName)
+void LldbEngine::loadSymbols(const FilePath &moduleName)
{
Q_UNUSED(moduleName)
}
@@ -642,12 +643,13 @@ void LldbEngine::reloadModules()
{
DebuggerCommand cmd("fetchModules");
cmd.callback = [this](const DebuggerResponse &response) {
+ const FilePath inferior = runParameters().inferior.command.executable();
const GdbMi &modules = response.data["modules"];
ModulesHandler *handler = modulesHandler();
handler->beginUpdateAll();
for (const GdbMi &item : modules) {
Module module;
- module.modulePath = item["file"].data();
+ module.modulePath = inferior.withNewPath(item["file"].data());
module.moduleName = item["name"].data();
module.symbolsRead = Module::UnknownReadState;
module.startAddress = item["loaded_addr"].toAddress();
@@ -659,13 +661,13 @@ void LldbEngine::reloadModules()
runCommand(cmd);
}
-void LldbEngine::requestModuleSymbols(const QString &moduleName)
+void LldbEngine::requestModuleSymbols(const FilePath &moduleName)
{
DebuggerCommand cmd("fetchSymbols");
- cmd.arg("module", moduleName);
+ cmd.arg("module", moduleName.path());
cmd.callback = [moduleName](const DebuggerResponse &response) {
const GdbMi &symbols = response.data["symbols"];
- QString moduleName = response.data["module"].data();
+ const QString module = response.data["module"].data();
Symbols syms;
for (const GdbMi &item : symbols) {
Symbol symbol;
@@ -676,7 +678,7 @@ void LldbEngine::requestModuleSymbols(const QString &moduleName)
symbol.demangled = item["demangled"].data();
syms.append(symbol);
}
- showModuleSymbols(moduleName, syms);
+ showModuleSymbols(moduleName.withNewPath(module), syms);
};
runCommand(cmd);
}
diff --git a/src/plugins/debugger/lldb/lldbengine.h b/src/plugins/debugger/lldb/lldbengine.h
index 0a0d9adfb55..8c0396ba509 100644
--- a/src/plugins/debugger/lldb/lldbengine.h
+++ b/src/plugins/debugger/lldb/lldbengine.h
@@ -68,9 +68,9 @@ private:
void assignValueInDebugger(WatchItem *item, const QString &expr, const QVariant &value) override;
void executeDebuggerCommand(const QString &command) override;
- void loadSymbols(const QString &moduleName) override;
+ void loadSymbols(const Utils::FilePath &moduleName) override;
void loadAllSymbols() override;
- void requestModuleSymbols(const QString &moduleName) override;
+ void requestModuleSymbols(const Utils::FilePath &moduleName) override;
void reloadModules() override;
void reloadRegisters() override;
void reloadSourceFiles() override {}
diff --git a/src/plugins/debugger/loadcoredialog.cpp b/src/plugins/debugger/loadcoredialog.cpp
index 86c9c738dc3..85a81a9418d 100644
--- a/src/plugins/debugger/loadcoredialog.cpp
+++ b/src/plugins/debugger/loadcoredialog.cpp
@@ -243,18 +243,18 @@ void AttachCoreDialog::accepted()
using ResultType = expected_str<FilePath>;
- const auto copyFileAsync = [=](QFutureInterface<ResultType> &fi, const FilePath &srcPath) {
- fi.reportResult(copyFile(srcPath));
+ const auto copyFileAsync = [=](QPromise<ResultType> &promise, const FilePath &srcPath) {
+ promise.addResult(copyFile(srcPath));
};
const Group root = {
parallel,
Async<ResultType>{[=](auto &task) {
- task.setAsyncCallData(copyFileAsync, this->coreFile());
+ task.setConcurrentCallData(copyFileAsync, this->coreFile());
},
[=](const auto &task) { d->coreFileResult = task.result(); }},
Async<ResultType>{[=](auto &task) {
- task.setAsyncCallData(copyFileAsync, this->symbolFile());
+ task.setConcurrentCallData(copyFileAsync, this->symbolFile());
},
[=](const auto &task) { d->symbolFileResult = task.result(); }},
};
diff --git a/src/plugins/debugger/moduleshandler.cpp b/src/plugins/debugger/moduleshandler.cpp
index 3451b9b5725..6e7496bd37d 100644
--- a/src/plugins/debugger/moduleshandler.cpp
+++ b/src/plugins/debugger/moduleshandler.cpp
@@ -48,7 +48,7 @@ QVariant ModuleItem::data(int column, int role) const
break;
case 1:
if (role == Qt::DisplayRole)
- return module.modulePath;
+ return module.modulePath.toUserOutput();
if (role == Qt::ToolTipRole) {
QString msg;
if (!module.elfData.buildId.isEmpty())
@@ -140,6 +140,12 @@ public:
DebuggerEngine *engine;
};
+static bool dependsCanBeFound()
+{
+ static bool dependsInPath = Environment::systemEnvironment().searchInPath("depends").isEmpty();
+ return dependsInPath;
+}
+
bool ModulesModel::contextMenuEvent(const ItemViewEvent &ev)
{
ModuleItem *item = itemForIndexAtLevel<1>(ev.sourceModelIndex());
@@ -150,7 +156,7 @@ bool ModulesModel::contextMenuEvent(const ItemViewEvent &ev)
const bool canShowSymbols = engine->hasCapability(ShowModuleSymbolsCapability);
const bool moduleNameValid = item && !item->module.moduleName.isEmpty();
const QString moduleName = item ? item->module.moduleName : QString();
- const QString modulePath = item ? item->module.modulePath : QString();
+ const FilePath modulePath = item ? item->module.modulePath : FilePath();
auto menu = new QMenu;
@@ -163,11 +169,13 @@ bool ModulesModel::contextMenuEvent(const ItemViewEvent &ev)
moduleNameValid && enabled && canReload,
[this, modulePath] { engine->loadSymbols(modulePath); });
- // FIXME: Dependencies only available on Windows, when "depends" is installed.
addAction(this, menu, Tr::tr("Show Dependencies of \"%1\"").arg(moduleName),
Tr::tr("Show Dependencies"),
- moduleNameValid && !moduleName.isEmpty() && HostOsInfo::isWindowsHost(),
- [modulePath] { QtcProcess::startDetached({{"depends"}, {modulePath}}); });
+ moduleNameValid && !modulePath.needsDevice() && modulePath.exists()
+ && dependsCanBeFound(),
+ [modulePath] {
+ QtcProcess::startDetached({{"depends"}, {modulePath.toString()}});
+ });
addAction(this, menu, Tr::tr("Load Symbols for All Modules"),
enabled && canLoadSymbols,
@@ -185,7 +193,7 @@ bool ModulesModel::contextMenuEvent(const ItemViewEvent &ev)
addAction(this, menu, Tr::tr("Edit File \"%1\"").arg(moduleName),
Tr::tr("Edit File"),
moduleNameValid,
- [this, modulePath] { engine->gotoLocation(FilePath::fromString(modulePath)); });
+ [this, modulePath] { engine->gotoLocation(modulePath); });
addAction(this, menu, Tr::tr("Show Symbols in File \"%1\"").arg(moduleName),
Tr::tr("Show Symbols"),
@@ -239,7 +247,7 @@ QAbstractItemModel *ModulesHandler::model() const
return m_proxyModel;
}
-ModuleItem *ModulesHandler::moduleFromPath(const QString &modulePath) const
+ModuleItem *ModulesHandler::moduleFromPath(const FilePath &modulePath) const
{
// Recent modules are more likely to be unloaded first.
return m_model->findItemAtLevel<1>([modulePath](ModuleItem *item) {
@@ -259,7 +267,7 @@ const Modules ModulesHandler::modules() const
return mods;
}
-void ModulesHandler::removeModule(const QString &modulePath)
+void ModulesHandler::removeModule(const FilePath &modulePath)
{
if (ModuleItem *item = moduleFromPath(modulePath))
m_model->destroyItem(item);
@@ -267,7 +275,7 @@ void ModulesHandler::removeModule(const QString &modulePath)
void ModulesHandler::updateModule(const Module &module)
{
- const QString path = module.modulePath;
+ const FilePath path = module.modulePath;
if (path.isEmpty())
return;
@@ -281,12 +289,12 @@ void ModulesHandler::updateModule(const Module &module)
}
try { // MinGW occasionallly throws std::bad_alloc.
- ElfReader reader(FilePath::fromUserInput(path));
+ ElfReader reader(path);
item->module.elfData = reader.readHeaders();
item->update();
} catch(...) {
qWarning("%s: An exception occurred while reading module '%s'",
- Q_FUNC_INFO, qPrintable(module.modulePath));
+ Q_FUNC_INFO, qPrintable(module.modulePath.toUserOutput()));
}
item->updated = true;
}
diff --git a/src/plugins/debugger/moduleshandler.h b/src/plugins/debugger/moduleshandler.h
index 082a66f2ca0..03434edab28 100644
--- a/src/plugins/debugger/moduleshandler.h
+++ b/src/plugins/debugger/moduleshandler.h
@@ -70,7 +70,7 @@ public:
ReadOk // Dwarf index available.
};
QString moduleName;
- QString modulePath;
+ Utils::FilePath modulePath;
QString hostPath;
SymbolReadState symbolsRead = UnknownReadState;
quint64 startAddress = 0;
@@ -99,7 +99,7 @@ public:
QAbstractItemModel *model() const;
- void removeModule(const QString &modulePath);
+ void removeModule(const Utils::FilePath &modulePath);
void updateModule(const Module &module);
void beginUpdateAll();
@@ -109,7 +109,7 @@ public:
const Modules modules() const;
private:
- ModuleItem *moduleFromPath(const QString &modulePath) const;
+ ModuleItem *moduleFromPath(const Utils::FilePath &modulePath) const;
ModulesModel *m_model;
QSortFilterProxyModel *m_proxyModel;
diff --git a/src/plugins/debugger/pdb/pdbengine.cpp b/src/plugins/debugger/pdb/pdbengine.cpp
index fd749a2d0ce..4435409506b 100644
--- a/src/plugins/debugger/pdb/pdbengine.cpp
+++ b/src/plugins/debugger/pdb/pdbengine.cpp
@@ -263,7 +263,7 @@ void PdbEngine::removeBreakpoint(const Breakpoint &bp)
notifyBreakpointRemoveOk(bp);
}
-void PdbEngine::loadSymbols(const QString &moduleName)
+void PdbEngine::loadSymbols(const FilePath &moduleName)
{
Q_UNUSED(moduleName)
}
@@ -294,16 +294,16 @@ void PdbEngine::refreshModules(const GdbMi &modules)
&& path.endsWith("' (built-in)>")) {
path = "(builtin)";
}
- module.modulePath = path;
+ module.modulePath = FilePath::fromString(path);
handler->updateModule(module);
}
handler->endUpdateAll();
}
-void PdbEngine::requestModuleSymbols(const QString &moduleName)
+void PdbEngine::requestModuleSymbols(const FilePath &moduleName)
{
DebuggerCommand cmd("listSymbols");
- cmd.arg("module", moduleName);
+ cmd.arg("module", moduleName.path());
runCommand(cmd);
}
@@ -341,7 +341,7 @@ void PdbEngine::refreshSymbols(const GdbMi &symbols)
symbol.name = item["name"].data();
syms.append(symbol);
}
- showModuleSymbols(moduleName, syms);
+ showModuleSymbols(runParameters().inferior.command.executable().withNewPath(moduleName), syms);
}
bool PdbEngine::canHandleToolTip(const DebuggerToolTipContext &) const
diff --git a/src/plugins/debugger/pdb/pdbengine.h b/src/plugins/debugger/pdb/pdbengine.h
index d65f0e33b1c..c4003884578 100644
--- a/src/plugins/debugger/pdb/pdbengine.h
+++ b/src/plugins/debugger/pdb/pdbengine.h
@@ -52,9 +52,9 @@ private:
const QString &expr, const QVariant &value) override;
void executeDebuggerCommand(const QString &command) override;
- void loadSymbols(const QString &moduleName) override;
+ void loadSymbols(const Utils::FilePath &moduleName) override;
void loadAllSymbols() override;
- void requestModuleSymbols(const QString &moduleName) override;
+ void requestModuleSymbols(const Utils::FilePath &moduleName) override;
void reloadModules() override;
void reloadRegisters() override {}
void reloadSourceFiles() override {}
diff --git a/src/plugins/debugger/qml/qmlengine.cpp b/src/plugins/debugger/qml/qmlengine.cpp
index 204d6c4437c..0730caaf78f 100644
--- a/src/plugins/debugger/qml/qmlengine.cpp
+++ b/src/plugins/debugger/qml/qmlengine.cpp
@@ -736,7 +736,7 @@ bool QmlEngine::acceptsBreakpoint(const BreakpointParameters &bp) const
return bp.isQmlFileAndLineBreakpoint();
}
-void QmlEngine::loadSymbols(const QString &moduleName)
+void QmlEngine::loadSymbols(const FilePath &moduleName)
{
Q_UNUSED(moduleName)
}
@@ -759,7 +759,7 @@ void QmlEngine::updateAll()
d->updateLocals();
}
-void QmlEngine::requestModuleSymbols(const QString &moduleName)
+void QmlEngine::requestModuleSymbols(const FilePath &moduleName)
{
Q_UNUSED(moduleName)
}
@@ -1802,10 +1802,10 @@ void QmlEnginePrivate::messageReceived(const QByteArray &data)
updateScriptSource(name, lineOffset, columnOffset, source);
}
- QMap<QString,QString> files;
+ QMap<QString, FilePath> files;
for (const QString &file : std::as_const(sourceFiles)) {
QString shortName = file;
- QString fullName = engine->toFileInProject(file);
+ FilePath fullName = engine->toFileInProject(file);
files.insert(shortName, fullName);
}
@@ -1915,7 +1915,7 @@ void QmlEnginePrivate::messageReceived(const QByteArray &data)
const QVariantMap script = body.value("script").toMap();
QUrl fileUrl(script.value(NAME).toString());
- QString filePath = engine->toFileInProject(fileUrl);
+ FilePath filePath = engine->toFileInProject(fileUrl);
const QVariantMap exception = body.value("exception").toMap();
QString errorMessage = exception.value("text").toString();
@@ -2045,8 +2045,7 @@ StackFrame QmlEnginePrivate::extractStackFrame(const QVariant &bodyVal)
stackFrame.function = extractString(body.value("func"));
if (stackFrame.function.isEmpty())
stackFrame.function = Tr::tr("Anonymous Function");
- stackFrame.file = FilePath::fromString(
- engine->toFileInProject(extractString(body.value("script"))));
+ stackFrame.file = engine->toFileInProject(extractString(body.value("script")));
stackFrame.usable = stackFrame.file.isReadableFile();
stackFrame.receiver = extractString(body.value("receiver"));
stackFrame.line = body.value("line").toInt() + 1;
@@ -2321,7 +2320,6 @@ void QmlEnginePrivate::insertSubItems(WatchItem *parent, const QVariantList &pro
QTC_ASSERT(parent, return);
LookupItems itemsToLookup;
- const QSet<QString> expandedINames = engine->watchHandler()->expandedINames();
for (const QVariant &property : properties) {
QmlV8ObjectData propertyData = extractData(property);
std::unique_ptr<WatchItem> item(new WatchItem);
@@ -2343,7 +2341,7 @@ void QmlEnginePrivate::insertSubItems(WatchItem *parent, const QVariantList &pro
item->id = propertyData.handle;
item->type = propertyData.type;
item->value = propertyData.value.toString();
- if (item->type.isEmpty() || expandedINames.contains(item->iname))
+ if (item->type.isEmpty() || engine->watchHandler()->isExpandedIName(item->iname))
itemsToLookup.insert(propertyData.handle, {item->iname, item->name, item->exp});
setWatchItemHasChildren(item.get(), propertyData.hasChildren());
parent->appendChild(item.release());
@@ -2445,7 +2443,7 @@ void QmlEnginePrivate::flushSendBuffer()
sendBuffer.clear();
}
-QString QmlEngine::toFileInProject(const QUrl &fileUrl)
+FilePath QmlEngine::toFileInProject(const QUrl &fileUrl)
{
// make sure file finder is properly initialized
const DebuggerRunParameters &rp = runParameters();
@@ -2454,7 +2452,7 @@ QString QmlEngine::toFileInProject(const QUrl &fileUrl)
d->fileFinder.setAdditionalSearchDirectories(rp.additionalSearchDirectories);
d->fileFinder.setSysroot(rp.sysRoot);
- return d->fileFinder.findFile(fileUrl).constFirst().toString();
+ return d->fileFinder.findFile(fileUrl).constFirst();
}
DebuggerEngine *createQmlEngine()
diff --git a/src/plugins/debugger/qml/qmlengine.h b/src/plugins/debugger/qml/qmlengine.h
index 331176d4269..2006ce081ff 100644
--- a/src/plugins/debugger/qml/qmlengine.h
+++ b/src/plugins/debugger/qml/qmlengine.h
@@ -25,7 +25,7 @@ public:
void logServiceActivity(const QString &service, const QString &logMessage);
void expressionEvaluated(quint32 queryId, const QVariant &result);
- QString toFileInProject(const QUrl &fileUrl);
+ Utils::FilePath toFileInProject(const QUrl &fileUrl);
private:
void disconnected();
@@ -75,9 +75,9 @@ private:
void assignValueInDebugger(WatchItem *item,
const QString &expr, const QVariant &value) override;
- void loadSymbols(const QString &moduleName) override;
+ void loadSymbols(const Utils::FilePath &moduleName) override;
void loadAllSymbols() override;
- void requestModuleSymbols(const QString &moduleName) override;
+ void requestModuleSymbols(const Utils::FilePath &moduleName) override;
void reloadModules() override;
void reloadRegisters() override {}
void reloadSourceFiles() override;
diff --git a/src/plugins/debugger/qml/qmlengineutils.cpp b/src/plugins/debugger/qml/qmlengineutils.cpp
index 7e304d3c921..80e0ac98f17 100644
--- a/src/plugins/debugger/qml/qmlengineutils.cpp
+++ b/src/plugins/debugger/qml/qmlengineutils.cpp
@@ -20,6 +20,7 @@ using namespace QmlDebug;
using namespace QmlJS;
using namespace QmlJS::AST;
using namespace TextEditor;
+using namespace Utils;
namespace Debugger::Internal {
@@ -218,11 +219,10 @@ void clearExceptionSelection()
}
}
-QStringList highlightExceptionCode(int lineNumber, const QString &filePath, const QString &errorMessage)
+QStringList highlightExceptionCode(int lineNumber, const FilePath &filePath, const QString &errorMessage)
{
QStringList messages;
- const QList<IEditor *> editors = DocumentModel::editorsForFilePath(
- Utils::FilePath::fromString(filePath));
+ const QList<IEditor *> editors = DocumentModel::editorsForFilePath(filePath);
const TextEditor::FontSettings &fontSettings = TextEditor::TextEditorSettings::fontSettings();
QTextCharFormat errorFormat = fontSettings.toTextCharFormat(TextEditor::C_ERROR);
@@ -251,7 +251,7 @@ QStringList highlightExceptionCode(int lineNumber, const QString &filePath, cons
selections.append(sel);
ed->setExtraSelections(TextEditorWidget::DebuggerExceptionSelection, selections);
- messages.append(QString::fromLatin1("%1: %2: %3").arg(filePath).arg(lineNumber).arg(errorMessage));
+ messages.append(QString::fromLatin1("%1: %2: %3").arg(filePath.toUserOutput()).arg(lineNumber).arg(errorMessage));
}
return messages;
}
diff --git a/src/plugins/debugger/qml/qmlengineutils.h b/src/plugins/debugger/qml/qmlengineutils.h
index 7cf55067b66..325c8f28e93 100644
--- a/src/plugins/debugger/qml/qmlengineutils.h
+++ b/src/plugins/debugger/qml/qmlengineutils.h
@@ -6,11 +6,13 @@
#include <qmldebug/qdebugmessageclient.h>
#include <qmldebug/qmloutputparser.h>
+namespace Utils { class FilePath; }
+
namespace Debugger::Internal {
void appendDebugOutput(QtMsgType type, const QString &message, const QmlDebug::QDebugContextInfo &info);
void clearExceptionSelection();
-QStringList highlightExceptionCode(int lineNumber, const QString &filePath, const QString &errorMessage);
+QStringList highlightExceptionCode(int lineNumber, const Utils::FilePath &filePath, const QString &errorMessage);
} // Debugger::Internal
diff --git a/src/plugins/debugger/qml/qmlinspectoragent.cpp b/src/plugins/debugger/qml/qmlinspectoragent.cpp
index 983e38adc0d..4b632c0f0db 100644
--- a/src/plugins/debugger/qml/qmlinspectoragent.cpp
+++ b/src/plugins/debugger/qml/qmlinspectoragent.cpp
@@ -32,6 +32,7 @@
using namespace QmlDebug;
using namespace QmlDebug::Constants;
+using namespace Utils;
namespace Debugger::Internal {
@@ -541,8 +542,8 @@ void QmlInspectorAgent::buildDebugIdHashRecursive(const ObjectReference &ref)
lineNum += match.captured(3).toInt() - 1;
}
- const QString filePath = m_qmlEngine->toFileInProject(fileUrl);
- m_debugIdLocations.insert(ref.debugId(), FileReference(filePath, lineNum, colNum));
+ const FilePath filePath = m_qmlEngine->toFileInProject(fileUrl);
+ m_debugIdLocations.insert(ref.debugId(), FileReference(filePath.toFSPathString(), lineNum, colNum));
const auto children = ref.children();
for (const ObjectReference &it : children)
@@ -735,7 +736,7 @@ void QmlInspectorAgent::onShowAppOnTopChanged(bool checked)
void QmlInspectorAgent::jumpToObjectDefinitionInEditor(const FileReference &objSource)
{
- const auto filePath = Utils::FilePath::fromString(m_qmlEngine->toFileInProject(objSource.url()));
+ const FilePath filePath = m_qmlEngine->toFileInProject(objSource.url());
Core::EditorManager::openEditorAt({filePath, objSource.lineNumber()});
}
diff --git a/src/plugins/debugger/sourcefileshandler.cpp b/src/plugins/debugger/sourcefileshandler.cpp
index 412f550482a..bec03054173 100644
--- a/src/plugins/debugger/sourcefileshandler.cpp
+++ b/src/plugins/debugger/sourcefileshandler.cpp
@@ -55,8 +55,8 @@ Qt::ItemFlags SourceFilesHandler::flags(const QModelIndex &index) const
{
if (index.row() >= m_fullNames.size())
return {};
- QFileInfo fi(m_fullNames.at(index.row()));
- return fi.isReadable() ? QAbstractItemModel::flags(index) : Qt::ItemFlags({});
+ FilePath filePath = m_fullNames.at(index.row());
+ return filePath.isReadableFile() ? QAbstractItemModel::flags(index) : Qt::ItemFlags({});
}
QVariant SourceFilesHandler::data(const QModelIndex &index, int role) const
@@ -75,7 +75,7 @@ QVariant SourceFilesHandler::data(const QModelIndex &index, int role) const
break;
case 1:
if (role == Qt::DisplayRole)
- return m_fullNames.at(row);
+ return m_fullNames.at(row).toUserOutput();
//if (role == Qt::DecorationRole)
// return module.symbolsRead ? icon2 : icon;
break;
@@ -123,13 +123,13 @@ bool SourceFilesHandler::setData(const QModelIndex &idx, const QVariant &data, i
return false;
}
-void SourceFilesHandler::setSourceFiles(const QMap<QString, QString> &sourceFiles)
+void SourceFilesHandler::setSourceFiles(const QMap<QString, FilePath> &sourceFiles)
{
beginResetModel();
m_shortNames.clear();
m_fullNames.clear();
- QMap<QString, QString>::ConstIterator it = sourceFiles.begin();
- QMap<QString, QString>::ConstIterator et = sourceFiles.end();
+ auto it = sourceFiles.begin();
+ const auto et = sourceFiles.end();
for (; it != et; ++it) {
m_shortNames.append(it.key());
m_fullNames.append(it.value());
@@ -139,7 +139,7 @@ void SourceFilesHandler::setSourceFiles(const QMap<QString, QString> &sourceFile
void SourceFilesHandler::removeAll()
{
- setSourceFiles(QMap<QString, QString>());
+ setSourceFiles({});
//header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
}
diff --git a/src/plugins/debugger/sourcefileshandler.h b/src/plugins/debugger/sourcefileshandler.h
index dabbdbdad0d..d714eda2e36 100644
--- a/src/plugins/debugger/sourcefileshandler.h
+++ b/src/plugins/debugger/sourcefileshandler.h
@@ -3,6 +3,8 @@
#pragma once
+#include <utils/filepath.h>
+
#include <QAbstractItemModel>
#include <QStringList>
@@ -29,7 +31,7 @@ public:
void clearModel();
- void setSourceFiles(const QMap<QString, QString> &sourceFiles);
+ void setSourceFiles(const QMap<QString, Utils::FilePath> &sourceFiles);
void removeAll();
QAbstractItemModel *model() { return m_proxyModel; }
@@ -37,7 +39,7 @@ public:
private:
DebuggerEngine *m_engine;
QStringList m_shortNames;
- QStringList m_fullNames;
+ Utils::FilePaths m_fullNames;
QAbstractItemModel *m_proxyModel;
};
diff --git a/src/plugins/debugger/terminal.cpp b/src/plugins/debugger/terminal.cpp
index 6707137e853..49048ec1797 100644
--- a/src/plugins/debugger/terminal.cpp
+++ b/src/plugins/debugger/terminal.cpp
@@ -175,8 +175,7 @@ void TerminalRunner::start()
Runnable stub = m_stubRunnable();
m_stubProc = new QtcProcess(this);
- m_stubProc->setTerminalMode(HostOsInfo::isWindowsHost()
- ? TerminalMode::Suspend : TerminalMode::Debug);
+ m_stubProc->setTerminalMode(TerminalMode::Debug);
connect(m_stubProc, &QtcProcess::started,
this, &TerminalRunner::stubStarted);
diff --git a/src/plugins/debugger/uvsc/uvscengine.cpp b/src/plugins/debugger/uvsc/uvscengine.cpp
index 2bb0bef8e76..06450651de2 100644
--- a/src/plugins/debugger/uvsc/uvscengine.cpp
+++ b/src/plugins/debugger/uvsc/uvscengine.cpp
@@ -600,7 +600,7 @@ void UvscEngine::handleProjectClosed()
Module module;
module.startAddress = 0;
module.endAddress = 0;
- module.modulePath = rp.inferior.command.executable().toString();
+ module.modulePath = rp.inferior.command.executable();
module.moduleName = "<executable>";
modulesHandler()->updateModule(module);
diff --git a/src/plugins/debugger/watchdata.cpp b/src/plugins/debugger/watchdata.cpp
index 329e58b88b7..8d0af0e4d31 100644
--- a/src/plugins/debugger/watchdata.cpp
+++ b/src/plugins/debugger/watchdata.cpp
@@ -216,6 +216,13 @@ public:
child->valueEditable = true;
item->appendChild(child);
}
+ if (childrenElided) {
+ auto child = new WatchItem;
+ child->name = WatchItem::loadMoreName;
+ child->iname = item->iname + "." + WatchItem::loadMoreName;
+ child->wantsChildren = true;
+ item->appendChild(child);
+ }
}
void decode()
@@ -264,6 +271,7 @@ public:
QString rawData;
QString childType;
DebuggerEncoding encoding;
+ int childrenElided;
quint64 addrbase;
quint64 addrstep;
Endian endian;
@@ -379,6 +387,7 @@ void WatchItem::parseHelper(const GdbMi &input, bool maySort)
decoder.item = this;
decoder.rawData = mi.data();
decoder.childType = input["childtype"].data();
+ decoder.childrenElided = input["childrenelided"].toInt();
decoder.addrbase = input["addrbase"].toAddress();
decoder.addrstep = input["addrstep"].toAddress();
decoder.endian = input["endian"].data() == ">" ? Endian::Big : Endian::Little;
@@ -504,6 +513,11 @@ QString WatchItem::toToolTip() const
return res;
}
+bool WatchItem::isLoadMore() const
+{
+ return name == loadMoreName;
+}
+
bool WatchItem::isLocal() const
{
if (arrayIndex >= 0)
diff --git a/src/plugins/debugger/watchdata.h b/src/plugins/debugger/watchdata.h
index 4ef57eacf18..42d046f2a13 100644
--- a/src/plugins/debugger/watchdata.h
+++ b/src/plugins/debugger/watchdata.h
@@ -36,8 +36,10 @@ public:
int editType() const;
static const qint64 InvalidId = -1;
+ constexpr static char loadMoreName[] = "<load more>";
void setHasChildren(bool c) { wantsChildren = c; }
+ bool isLoadMore() const;
bool isValid() const { return !iname.isEmpty(); }
bool isVTablePointer() const;
diff --git a/src/plugins/debugger/watchhandler.cpp b/src/plugins/debugger/watchhandler.cpp
index 17ce07bee04..0923348d94f 100644
--- a/src/plugins/debugger/watchhandler.cpp
+++ b/src/plugins/debugger/watchhandler.cpp
@@ -410,6 +410,7 @@ public:
bool hasChildren(const QModelIndex &idx) const override;
bool canFetchMore(const QModelIndex &idx) const override;
void fetchMore(const QModelIndex &idx) override;
+ void expand(WatchItem *item, bool requestEngineUpdate);
QString displayForAutoTest(const QByteArray &iname) const;
void reinitialize(bool includeInspectData = false);
@@ -468,6 +469,7 @@ public:
SeparatedView *m_separatedView; // Not owned.
QSet<QString> m_expandedINames;
+ QHash<QString, int> m_maxArrayCount;
QTimer m_requestUpdateTimer;
QTimer m_localsWindowsTimer;
@@ -1226,7 +1228,8 @@ bool WatchModel::setData(const QModelIndex &idx, const QVariant &value, int role
if (value.toBool()) {
// Should already have been triggered by fetchMore()
//QTC_CHECK(m_expandedINames.contains(item->iname));
- m_expandedINames.insert(item->iname);
+ if (!item->isLoadMore())
+ m_expandedINames.insert(item->iname);
} else {
m_expandedINames.remove(item->iname);
}
@@ -1336,13 +1339,23 @@ bool WatchModel::canFetchMore(const QModelIndex &idx) const
void WatchModel::fetchMore(const QModelIndex &idx)
{
- if (!idx.isValid())
- return;
+ if (idx.isValid())
+ expand(nonRootItemForIndex(idx), true);
+}
- WatchItem *item = nonRootItemForIndex(idx);
- if (item) {
+void WatchModel::expand(WatchItem *item, bool requestEngineUpdate)
+{
+ if (!item)
+ return;
+ if (item->isLoadMore()) {
+ item = item->parent();
+ m_maxArrayCount[item->iname]
+ = m_maxArrayCount.value(item->iname, debuggerSettings()->defaultArraySize.value()) * 10;
+ if (requestEngineUpdate)
+ m_engine->updateItem(item->iname);
+ } else {
m_expandedINames.insert(item->iname);
- if (item->childCount() == 0)
+ if (requestEngineUpdate && item->childCount() == 0)
m_engine->expandItem(item->iname);
}
}
@@ -1765,10 +1778,14 @@ bool WatchModel::contextMenuEvent(const ItemViewEvent &ev)
menu->addSeparator();
addAction(this, menu, Tr::tr("Expand All Children"), item, [this, name = item ? item->iname : QString()] {
- m_expandedINames.insert(name);
- if (auto item = findItem(name)) {
- item->forFirstLevelChildren(
- [this](WatchItem *child) { m_expandedINames.insert(child->iname); });
+ if (name.isEmpty())
+ return;
+ if (WatchItem *item = findItem(name)) {
+ expand(item, false);
+ item->forFirstLevelChildren([this](WatchItem *child) {
+ if (!child->isLoadMore())
+ expand(child, false);
+ });
m_engine->updateLocals();
}
});
@@ -2226,7 +2243,7 @@ bool WatchHandler::insertItem(WatchItem *item)
void WatchModel::reexpandItems()
{
- for (const QString &iname : std::as_const(m_expandedINames)) {
+ for (const QString &iname: m_expandedINames) {
if (WatchItem *item = findItem(iname)) {
emit itemIsExpanded(indexForItem(item));
emit inameIsExpanded(iname);
@@ -2308,7 +2325,8 @@ void WatchHandler::notifyUpdateFinished()
m_model->destroyItem(item);
m_model->forAllItems([this](WatchItem *item) {
- if (item->wantsChildren && isExpandedIName(item->iname)) {
+ if (item->wantsChildren && isExpandedIName(item->iname)
+ && item->name != WatchItem::loadMoreName) {
// m_model->m_engine->showMessage(QString("ADJUSTING CHILD EXPECTATION FOR " + item->iname));
item->wantsChildren = false;
}
@@ -2607,11 +2625,8 @@ const WatchItem *WatchHandler::watchItem(const QModelIndex &idx) const
void WatchHandler::fetchMore(const QString &iname) const
{
- if (WatchItem *item = m_model->findItem(iname)) {
- m_model->m_expandedINames.insert(iname);
- if (item->childCount() == 0)
- m_model->m_engine->expandItem(iname);
- }
+ if (WatchItem *item = m_model->findItem(iname))
+ m_model->expand(item, true);
}
WatchItem *WatchHandler::findItem(const QString &iname) const
@@ -2725,9 +2740,9 @@ QString WatchHandler::individualFormatRequests() const
void WatchHandler::appendFormatRequests(DebuggerCommand *cmd) const
{
- QJsonArray expanded;
- for (const QString &name : std::as_const(m_model->m_expandedINames))
- expanded.append(name);
+ QJsonObject expanded;
+ for (const QString &iname : std::as_const(m_model->m_expandedINames))
+ expanded.insert(iname, maxArrayCount(iname));
cmd->arg("expanded", expanded);
@@ -2831,6 +2846,11 @@ QSet<QString> WatchHandler::expandedINames() const
return m_model->m_expandedINames;
}
+int WatchHandler::maxArrayCount(const QString &iname) const
+{
+ return m_model->m_maxArrayCount.value(iname, debuggerSettings()->defaultArraySize.value());
+}
+
void WatchHandler::recordTypeInfo(const GdbMi &typeInfo)
{
if (typeInfo.type() == GdbMi::List) {
diff --git a/src/plugins/debugger/watchhandler.h b/src/plugins/debugger/watchhandler.h
index 0683d73ce94..58a64cea4a4 100644
--- a/src/plugins/debugger/watchhandler.h
+++ b/src/plugins/debugger/watchhandler.h
@@ -60,6 +60,7 @@ public:
bool isExpandedIName(const QString &iname) const;
QSet<QString> expandedINames() const;
+ int maxArrayCount(const QString &iname) const;
static QStringList watchedExpressions();
static QMap<QString, int> watcherNames();
diff --git a/src/plugins/designer/codemodelhelpers.cpp b/src/plugins/designer/codemodelhelpers.cpp
index beddb3714e6..8428fd98171 100644
--- a/src/plugins/designer/codemodelhelpers.cpp
+++ b/src/plugins/designer/codemodelhelpers.cpp
@@ -9,7 +9,7 @@
#include <projectexplorer/buildsystem.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorer.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <QCoreApplication>
@@ -30,7 +30,7 @@ static const char setupUiC[] = "setupUi";
// Find the generated "ui_form.h" header of the form via project.
static FilePath generatedHeaderOf(const FilePath &uiFileName)
{
- if (const Project *uiProject = SessionManager::projectForFile(uiFileName)) {
+ if (const Project *uiProject = ProjectManager::projectForFile(uiFileName)) {
if (Target *t = uiProject->activeTarget()) {
if (BuildSystem *bs = t->buildSystem()) {
FilePaths files = bs->filesGeneratedFrom(uiFileName);
diff --git a/src/plugins/designer/designer.qbs b/src/plugins/designer/designer.qbs
index 411be43283b..b730485329d 100644
--- a/src/plugins/designer/designer.qbs
+++ b/src/plugins/designer/designer.qbs
@@ -77,9 +77,7 @@ QtcPlugin {
]
}
- Group {
- name: "Tests"
- condition: qtc.testsEnabled
+ QtcTestFiles {
files: [ "gotoslot_test.cpp" ]
cpp.defines: outer.concat(['SRCDIR="' + FileInfo.path(filePath) + '"'])
diff --git a/src/plugins/designer/qtcreatorintegration.cpp b/src/plugins/designer/qtcreatorintegration.cpp
index db96523c360..02f01a5d091 100644
--- a/src/plugins/designer/qtcreatorintegration.cpp
+++ b/src/plugins/designer/qtcreatorintegration.cpp
@@ -16,20 +16,24 @@
#include <cppeditor/cppworkingcopy.h>
#include <cppeditor/insertionpointlocator.h>
#include <cppeditor/symbolfinder.h>
+
#include <cplusplus/LookupContext.h>
#include <cplusplus/Overview.h>
+
#include <coreplugin/icore.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/messagemanager.h>
-#include <texteditor/texteditor.h>
-#include <texteditor/textdocument.h>
+
#include <projectexplorer/buildsystem.h>
#include <projectexplorer/extracompiler.h>
#include <projectexplorer/projectexplorer.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/projecttree.h>
-#include <projectexplorer/session.h>
#include <projectexplorer/target.h>
+#include <texteditor/texteditor.h>
+#include <texteditor/textdocument.h>
+
#include <utils/algorithm.h>
#include <utils/mimeutils.h>
#include <utils/qtcassert.h>
@@ -493,10 +497,10 @@ bool QtCreatorIntegration::navigateToSlot(const QString &objectName,
// Retrieve code model snapshot restricted to project of ui file or the working copy.
Snapshot docTable = CppEditor::CppModelManager::instance()->snapshot();
Snapshot newDocTable;
- const Project *uiProject = SessionManager::projectForFile(currentUiFile);
+ const Project *uiProject = ProjectManager::projectForFile(currentUiFile);
if (uiProject) {
for (Snapshot::const_iterator i = docTable.begin(), ei = docTable.end(); i != ei; ++i) {
- const Project *project = SessionManager::projectForFile(i.key());
+ const Project *project = ProjectManager::projectForFile(i.key());
if (project == uiProject)
newDocTable.insert(i.value());
}
@@ -639,7 +643,7 @@ void QtCreatorIntegration::handleSymbolRenameStage1(
return;
// Get ExtraCompiler.
- const Project * const project = SessionManager::projectForFile(uiFile);
+ const Project * const project = ProjectManager::projectForFile(uiFile);
if (!project) {
return reportRenamingError(oldName, Designer::Tr::tr("File \"%1\" not found in project.")
.arg(uiFile.toUserOutput()));
diff --git a/src/plugins/designer/resourcehandler.cpp b/src/plugins/designer/resourcehandler.cpp
index 29838283d5c..fe2cdd28c60 100644
--- a/src/plugins/designer/resourcehandler.cpp
+++ b/src/plugins/designer/resourcehandler.cpp
@@ -2,13 +2,16 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "resourcehandler.h"
+
#include "designerconstants.h"
+#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorer.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/projectnodes.h>
-#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+
#include <resourceeditor/resourcenode.h>
+
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
@@ -42,10 +45,10 @@ void ResourceHandler::ensureInitialized()
Qt::QueuedConnection);
};
- for (Project *p : SessionManager::projects())
+ for (Project *p : ProjectManager::projects())
connector(p);
- connect(SessionManager::instance(), &SessionManager::projectAdded, this, connector);
+ connect(ProjectManager::instance(), &ProjectManager::projectAdded, this, connector);
m_originalUiQrcPaths = m_form->activeResourceFilePaths();
if (Designer::Constants::Internal::debug)
@@ -68,7 +71,7 @@ void ResourceHandler::updateResourcesHelper(bool updateProjectResources)
qDebug() << "ResourceHandler::updateResources()" << fileName;
// Filename could change in the meantime.
- Project *project = SessionManager::projectForFile(Utils::FilePath::fromUserInput(fileName));
+ Project *project = ProjectManager::projectForFile(Utils::FilePath::fromUserInput(fileName));
const bool dirty = m_form->property("_q_resourcepathchanged").toBool();
if (dirty)
m_form->setDirty(true);
diff --git a/src/plugins/diffeditor/diffeditordocument.cpp b/src/plugins/diffeditor/diffeditordocument.cpp
index 05b1684c8a3..1d43064d60f 100644
--- a/src/plugins/diffeditor/diffeditordocument.cpp
+++ b/src/plugins/diffeditor/diffeditordocument.cpp
@@ -291,8 +291,8 @@ Core::IDocument::OpenResult DiffEditorDocument::open(QString *errorString, const
return OpenResult::ReadError;
}
- bool ok = false;
- QList<FileData> fileDataList = DiffUtils::readPatch(patch, &ok);
+ const std::optional<QList<FileData>> fileDataList = DiffUtils::readPatch(patch);
+ bool ok = fileDataList.has_value();
if (!ok) {
*errorString = Tr::tr("Could not parse patch file \"%1\". "
"The content is not of unified diff format.")
@@ -302,7 +302,7 @@ Core::IDocument::OpenResult DiffEditorDocument::open(QString *errorString, const
emit temporaryStateChanged();
setFilePath(filePath.absoluteFilePath());
setWorkingDirectory(filePath.absoluteFilePath());
- setDiffFiles(fileDataList);
+ setDiffFiles(*fileDataList);
}
endReload(ok);
if (!ok && readResult == TextFileFormat::ReadEncodingError)
diff --git a/src/plugins/diffeditor/diffeditorplugin.cpp b/src/plugins/diffeditor/diffeditorplugin.cpp
index 2a813206657..6ae5f816c30 100644
--- a/src/plugins/diffeditor/diffeditorplugin.cpp
+++ b/src/plugins/diffeditor/diffeditorplugin.cpp
@@ -47,13 +47,12 @@ public:
m_ignoreWhitespace(ignoreWhitespace)
{}
- void operator()(QFutureInterface<FileData> &futureInterface,
- const ReloadInput &reloadInput) const
+ void operator()(QPromise<FileData> &promise, const ReloadInput &reloadInput) const
{
if (reloadInput.text[LeftSide] == reloadInput.text[RightSide])
return; // We show "No difference" in this case, regardless if it's binary or not
- Differ differ(&futureInterface);
+ Differ differ(QFuture<void>(promise.future()));
FileData fileData;
if (!reloadInput.binaryFiles) {
@@ -85,7 +84,7 @@ public:
fileData.fileInfo = reloadInput.fileInfo;
fileData.fileOperation = reloadInput.fileOperation;
fileData.binaryFiles = reloadInput.binaryFiles;
- futureInterface.reportResult(fileData);
+ promise.addResult(fileData);
}
private:
@@ -115,7 +114,7 @@ DiffFilesController::DiffFilesController(IDocument *document)
QList<std::optional<FileData>> *outputList = storage.activeStorage();
const auto setupDiff = [this](AsyncTask<FileData> &async, const ReloadInput &reloadInput) {
- async.setAsyncCallData(DiffFile(ignoreWhitespace(), contextLineCount()), reloadInput);
+ async.setConcurrentCallData(DiffFile(ignoreWhitespace(), contextLineCount()), reloadInput);
async.setFutureSynchronizer(Internal::DiffEditorPlugin::futureSynchronizer());
};
const auto onDiffDone = [outputList](const AsyncTask<FileData> &async, int i) {
@@ -135,7 +134,7 @@ DiffFilesController::DiffFilesController(IDocument *document)
taskTree.setupRoot(tasks);
};
const auto onTreeDone = [this, storage] {
- const QList<std::optional<FileData>> &results = *storage.activeStorage();
+ const QList<std::optional<FileData>> &results = *storage;
QList<FileData> finalList;
for (const std::optional<FileData> &result : results) {
if (result.has_value())
@@ -771,13 +770,13 @@ void DiffEditor::Internal::DiffEditorPlugin::testMakePatch()
QCOMPARE(result, patchText);
- bool ok;
- QList<FileData> resultList = DiffUtils::readPatch(result, &ok);
+ const std::optional<QList<FileData>> resultList = DiffUtils::readPatch(result);
+ const bool ok = resultList.has_value();
QVERIFY(ok);
- QCOMPARE(resultList.count(), 1);
- for (int i = 0; i < resultList.count(); i++) {
- const FileData &resultFileData = resultList.at(i);
+ QCOMPARE(resultList->count(), 1);
+ for (int i = 0; i < resultList->count(); i++) {
+ const FileData &resultFileData = resultList->at(i);
QCOMPARE(resultFileData.fileInfo[LeftSide].fileName, fileName);
QCOMPARE(resultFileData.fileInfo[RightSide].fileName, fileName);
QCOMPARE(resultFileData.chunks.count(), 1);
@@ -1335,14 +1334,14 @@ void DiffEditor::Internal::DiffEditorPlugin::testReadPatch()
QFETCH(QString, sourcePatch);
QFETCH(QList<FileData>, fileDataList);
- bool ok;
- const QList<FileData> &result = DiffUtils::readPatch(sourcePatch, &ok);
+ const std::optional<QList<FileData>> result = DiffUtils::readPatch(sourcePatch);
+ const bool ok = result.has_value();
QVERIFY(ok);
- QCOMPARE(result.count(), fileDataList.count());
+ QCOMPARE(result->count(), fileDataList.count());
for (int i = 0; i < fileDataList.count(); i++) {
const FileData &origFileData = fileDataList.at(i);
- const FileData &resultFileData = result.at(i);
+ const FileData &resultFileData = result->at(i);
QCOMPARE(resultFileData.fileInfo[LeftSide].fileName, origFileData.fileInfo[LeftSide].fileName);
QCOMPARE(resultFileData.fileInfo[LeftSide].typeInfo, origFileData.fileInfo[LeftSide].typeInfo);
QCOMPARE(resultFileData.fileInfo[RightSide].fileName, origFileData.fileInfo[RightSide].fileName);
diff --git a/src/plugins/diffeditor/diffutils.cpp b/src/plugins/diffeditor/diffutils.cpp
index f9d7d6b264a..afb75203037 100644
--- a/src/plugins/diffeditor/diffutils.cpp
+++ b/src/plugins/diffeditor/diffutils.cpp
@@ -6,7 +6,8 @@
#include <utils/algorithm.h>
#include <utils/differ.h>
-#include <QFutureInterfaceBase>
+#include <QFuture>
+#include <QPromise>
#include <QRegularExpression>
#include <QStringList>
#include <QTextStream>
@@ -556,7 +557,7 @@ static QList<RowData> readLines(QStringView patch, bool lastChunk, bool *lastChu
int noNewLineInDelete = -1;
int noNewLineInInsert = -1;
- const QVector<QStringView> lines = patch.split(newLine);
+ const QList<QStringView> lines = patch.split(newLine);
int i;
for (i = 0; i < lines.size(); i++) {
QStringView line = lines.at(i);
@@ -794,7 +795,7 @@ static QList<ChunkData> readChunks(QStringView patch, bool *lastChunkAtTheEndOfF
QList<ChunkData> chunkDataList;
int position = -1;
- QVector<int> startingPositions; // store starting positions of @@
+ QList<int> startingPositions; // store starting positions of @@
if (patch.startsWith(QStringLiteral("@@ -")))
startingPositions.append(position + 1);
@@ -892,7 +893,7 @@ static FileData readDiffHeaderAndChunks(QStringView headerAndChunks, bool *ok)
}
-static QList<FileData> readDiffPatch(QStringView patch, bool *ok, QFutureInterfaceBase *jobController)
+static void readDiffPatch(QPromise<QList<FileData>> &promise, QStringView patch)
{
const QRegularExpression diffRegExp("(?:\\n|^)" // new line of the beginning of a patch
"(" // either
@@ -911,23 +912,20 @@ static QList<FileData> readDiffPatch(QStringView patch, bool *ok, QFutureInterfa
")"); // end of or
bool readOk = false;
-
QList<FileData> fileDataList;
-
QRegularExpressionMatch diffMatch = diffRegExp.match(patch);
if (diffMatch.hasMatch()) {
readOk = true;
int lastPos = -1;
do {
- if (jobController && jobController->isCanceled())
- return {};
+ if (promise.isCanceled())
+ return;
int pos = diffMatch.capturedStart();
if (lastPos >= 0) {
QStringView headerAndChunks = patch.mid(lastPos, pos - lastPos);
- const FileData fileData = readDiffHeaderAndChunks(headerAndChunks,
- &readOk);
+ const FileData fileData = readDiffHeaderAndChunks(headerAndChunks, &readOk);
if (!readOk)
break;
@@ -942,21 +940,15 @@ static QList<FileData> readDiffPatch(QStringView patch, bool *ok, QFutureInterfa
if (readOk) {
QStringView headerAndChunks = patch.mid(lastPos, patch.size() - lastPos - 1);
- const FileData fileData = readDiffHeaderAndChunks(headerAndChunks,
- &readOk);
+ const FileData fileData = readDiffHeaderAndChunks(headerAndChunks, &readOk);
if (readOk)
fileDataList.append(fileData);
}
}
-
- if (ok)
- *ok = readOk;
-
if (!readOk)
- return {};
-
- return fileDataList;
+ return;
+ promise.addResult(fileDataList);
}
// The git diff patch format (ChangeFile, NewFile, DeleteFile)
@@ -1203,11 +1195,11 @@ static bool detectFileData(QStringView patch, FileData *fileData, QStringView *r
return detectIndexAndBinary(*remainingPatch, fileData, remainingPatch);
}
-static QList<FileData> readGitPatch(QStringView patch, bool *ok, QFutureInterfaceBase *jobController)
+static void readGitPatch(QPromise<QList<FileData>> &promise, QStringView patch)
{
int position = -1;
- QVector<int> startingPositions; // store starting positions of git headers
+ QList<int> startingPositions; // store starting positions of git headers
if (patch.startsWith(QStringLiteral("diff --git ")))
startingPositions.append(position + 1);
@@ -1221,13 +1213,12 @@ static QList<FileData> readGitPatch(QStringView patch, bool *ok, QFutureInterfac
};
const QChar newLine('\n');
- bool readOk = true;
- QVector<PatchInfo> patches;
+ QList<PatchInfo> patches;
const int count = startingPositions.size();
for (int i = 0; i < count; i++) {
- if (jobController && jobController->isCanceled())
- return {};
+ if (promise.isCanceled())
+ return;
const int diffStart = startingPositions.at(i);
const int diffEnd = (i < count - 1)
@@ -1242,65 +1233,53 @@ static QList<FileData> readGitPatch(QStringView patch, bool *ok, QFutureInterfac
FileData fileData;
QStringView remainingFileDiff;
- readOk = detectFileData(fileDiff, &fileData, &remainingFileDiff);
-
- if (!readOk)
- break;
+ if (!detectFileData(fileDiff, &fileData, &remainingFileDiff))
+ return;
patches.append(PatchInfo { remainingFileDiff, fileData });
}
- if (!readOk) {
- if (ok)
- *ok = readOk;
- return {};
- }
+ if (patches.isEmpty())
+ return;
- if (jobController)
- jobController->setProgressRange(0, patches.size());
+ promise.setProgressRange(0, patches.size());
QList<FileData> fileDataList;
- readOk = false;
int i = 0;
for (const auto &patchInfo : std::as_const(patches)) {
- if (jobController) {
- if (jobController->isCanceled())
- return {};
- jobController->setProgressValue(i++);
- }
+ if (promise.isCanceled())
+ return;
+ promise.setProgressValue(i++);
FileData fileData = patchInfo.fileData;
+ bool readOk = false;
if (!patchInfo.patch.isEmpty() || fileData.fileOperation == FileData::ChangeFile)
fileData.chunks = readChunks(patchInfo.patch, &fileData.lastChunkAtTheEndOfFile, &readOk);
else
readOk = true;
if (!readOk)
- break;
+ return;
fileDataList.append(fileData);
}
+ promise.addResult(fileDataList);
+}
- if (ok)
- *ok = readOk;
-
- if (!readOk)
+std::optional<QList<FileData>> DiffUtils::readPatch(const QString &patch)
+{
+ QPromise<QList<FileData>> promise;
+ promise.start();
+ readPatchWithPromise(promise, patch);
+ if (promise.future().resultCount() == 0)
return {};
-
- return fileDataList;
+ return promise.future().result();
}
-QList<FileData> DiffUtils::readPatch(const QString &patch, bool *ok,
- QFutureInterfaceBase *jobController)
+void DiffUtils::readPatchWithPromise(QPromise<QList<FileData>> &promise, const QString &patch)
{
- bool readOk = false;
-
- QList<FileData> fileDataList;
-
- if (jobController) {
- jobController->setProgressRange(0, 1);
- jobController->setProgressValue(0);
- }
+ promise.setProgressRange(0, 1);
+ promise.setProgressValue(0);
QStringView croppedPatch = QStringView(patch);
// Crop e.g. "-- \n2.10.2.windows.1\n\n" at end of file
const QRegularExpression formatPatchEndingRegExp("(\\n-- \\n\\S*\\n\\n$)");
@@ -1308,14 +1287,9 @@ QList<FileData> DiffUtils::readPatch(const QString &patch, bool *ok,
if (match.hasMatch())
croppedPatch = croppedPatch.left(match.capturedStart() + 1);
- fileDataList = readGitPatch(croppedPatch, &readOk, jobController);
- if (!readOk)
- fileDataList = readDiffPatch(croppedPatch, &readOk, jobController);
-
- if (ok)
- *ok = readOk;
-
- return fileDataList;
+ readGitPatch(promise, croppedPatch);
+ if (promise.future().resultCount() == 0)
+ readDiffPatch(promise, croppedPatch);
}
} // namespace DiffEditor
diff --git a/src/plugins/diffeditor/diffutils.h b/src/plugins/diffeditor/diffutils.h
index 65fcda46786..e128f01012c 100644
--- a/src/plugins/diffeditor/diffutils.h
+++ b/src/plugins/diffeditor/diffutils.h
@@ -14,7 +14,8 @@
#include <array>
QT_BEGIN_NAMESPACE
-class QFutureInterfaceBase;
+template <class T>
+class QPromise;
QT_END_NAMESPACE
namespace Utils { class Diff; }
@@ -142,9 +143,8 @@ public:
const QString &rightFileName,
bool lastChunk = false);
static QString makePatch(const QList<FileData> &fileDataList);
- static QList<FileData> readPatch(const QString &patch,
- bool *ok = nullptr,
- QFutureInterfaceBase *jobController = nullptr);
+ static std::optional<QList<FileData>> readPatch(const QString &patch);
+ static void readPatchWithPromise(QPromise<QList<FileData>> &promise, const QString &patch);
};
} // namespace DiffEditor
diff --git a/src/plugins/diffeditor/sidebysidediffeditorwidget.cpp b/src/plugins/diffeditor/sidebysidediffeditorwidget.cpp
index da69e607840..6847bf0e3a2 100644
--- a/src/plugins/diffeditor/sidebysidediffeditorwidget.cpp
+++ b/src/plugins/diffeditor/sidebysidediffeditorwidget.cpp
@@ -8,14 +8,8 @@
#include "diffeditorplugin.h"
#include "diffeditortr.h"
-#include <QMenu>
-#include <QPainter>
-#include <QScrollBar>
-#include <QTextBlock>
-#include <QVBoxLayout>
-
-#include <coreplugin/icore.h>
#include <coreplugin/find/highlightscrollbarcontroller.h>
+#include <coreplugin/icore.h>
#include <coreplugin/minisplitter.h>
#include <coreplugin/progressmanager/progressmanager.h>
@@ -29,6 +23,11 @@
#include <utils/mathutils.h>
#include <utils/tooltip/tooltip.h>
+#include <QMenu>
+#include <QPainter>
+#include <QScrollBar>
+#include <QVBoxLayout>
+
using namespace Core;
using namespace TextEditor;
using namespace Utils;
@@ -245,8 +244,8 @@ QString SideDiffEditorWidget::plainTextFromSelection(const QTextCursor &cursor)
return TextDocument::convertToPlainText(text);
}
-SideBySideDiffOutput SideDiffData::diffOutput(QFutureInterface<void> &fi, int progressMin,
- int progressMax, const DiffEditorInput &input)
+static SideBySideDiffOutput diffOutput(QPromise<SideBySideShowResults> &promise, int progressMin,
+ int progressMax, const DiffEditorInput &input)
{
SideBySideDiffOutput output;
@@ -366,8 +365,8 @@ SideBySideDiffOutput SideDiffData::diffOutput(QFutureInterface<void> &fi, int pr
diffText[RightSide].replace('\r', ' ');
output.side[LeftSide].diffText += diffText[LeftSide];
output.side[RightSide].diffText += diffText[RightSide];
- fi.setProgressValue(MathUtils::interpolateLinear(++i, 0, count, progressMin, progressMax));
- if (fi.isCanceled())
+ promise.setProgressValue(MathUtils::interpolateLinear(++i, 0, count, progressMin, progressMax));
+ if (promise.isCanceled())
return {};
}
output.side[LeftSide].selections = SelectableTextEditorWidget::polishedSelections(
@@ -869,7 +868,7 @@ void SideBySideDiffEditorWidget::restoreState()
void SideBySideDiffEditorWidget::showDiff()
{
- m_asyncTask.reset(new AsyncTask<ShowResults>());
+ m_asyncTask.reset(new AsyncTask<SideBySideShowResults>());
m_asyncTask->setFutureSynchronizer(DiffEditorPlugin::futureSynchronizer());
m_controller.setBusyShowing(true);
@@ -878,7 +877,7 @@ void SideBySideDiffEditorWidget::showDiff()
for (SideDiffEditorWidget *editor : m_editor)
editor->clearAll(Tr::tr("Retrieving data failed."));
} else {
- const ShowResults results = m_asyncTask->result();
+ const SideBySideShowResults results = m_asyncTask->result();
m_editor[LeftSide]->setDiffData(results[LeftSide].diffData);
m_editor[RightSide]->setDiffData(results[RightSide].diffData);
TextDocumentPtr leftDoc(results[LeftSide].textDocument);
@@ -914,28 +913,23 @@ void SideBySideDiffEditorWidget::showDiff()
const DiffEditorInput input(&m_controller);
- auto getDocument = [input](QFutureInterface<ShowResults> &futureInterface) {
- auto cleanup = qScopeGuard([&futureInterface] {
- if (futureInterface.isCanceled())
- futureInterface.reportCanceled();
- });
+ auto getDocument = [input](QPromise<SideBySideShowResults> &promise) {
const int firstPartMax = 20; // diffOutput is about 4 times quicker than filling document
const int leftPartMax = 60;
const int rightPartMax = 100;
- futureInterface.setProgressRange(0, rightPartMax);
- futureInterface.setProgressValue(0);
- QFutureInterface<void> fi = futureInterface;
- const SideBySideDiffOutput output = SideDiffData::diffOutput(fi, 0, firstPartMax, input);
- if (futureInterface.isCanceled())
+ promise.setProgressRange(0, rightPartMax);
+ promise.setProgressValue(0);
+ const SideBySideDiffOutput output = diffOutput(promise, 0, firstPartMax, input);
+ if (promise.isCanceled())
return;
- const ShowResult leftResult{TextDocumentPtr(new TextDocument("DiffEditor.SideDiffEditor")),
+ const SideBySideShowResult leftResult{TextDocumentPtr(new TextDocument("DiffEditor.SideDiffEditor")),
output.side[LeftSide].diffData, output.side[LeftSide].selections};
- const ShowResult rightResult{TextDocumentPtr(new TextDocument("DiffEditor.SideDiffEditor")),
+ const SideBySideShowResult rightResult{TextDocumentPtr(new TextDocument("DiffEditor.SideDiffEditor")),
output.side[RightSide].diffData, output.side[RightSide].selections};
- const ShowResults result{leftResult, rightResult};
+ const SideBySideShowResults result{leftResult, rightResult};
- auto propagateDocument = [&output, &fi](DiffSide side, const ShowResult &result,
+ auto propagateDocument = [&output, &promise](DiffSide side, const SideBySideShowResult &result,
int progressMin, int progressMax) {
// No need to store the change history
result.textDocument->document()->setUndoRedoEnabled(false);
@@ -952,8 +946,9 @@ void SideBySideDiffEditorWidget::showDiff()
const QString package = output.side[side].diffText.mid(currentPos, packageSize);
cursor.insertText(package);
currentPos += package.size();
- fi.setProgressValue(MathUtils::interpolateLinear(currentPos, 0, diffSize, progressMin, progressMax));
- if (fi.isCanceled())
+ promise.setProgressValue(MathUtils::interpolateLinear(currentPos, 0, diffSize,
+ progressMin, progressMax));
+ if (promise.isCanceled())
return;
}
@@ -968,16 +963,16 @@ void SideBySideDiffEditorWidget::showDiff()
};
propagateDocument(LeftSide, leftResult, firstPartMax, leftPartMax);
- if (fi.isCanceled())
+ if (promise.isCanceled())
return;
propagateDocument(RightSide, rightResult, leftPartMax, rightPartMax);
- if (fi.isCanceled())
+ if (promise.isCanceled())
return;
- futureInterface.reportResult(result);
+ promise.addResult(result);
};
- m_asyncTask->setAsyncCallData(getDocument);
+ m_asyncTask->setConcurrentCallData(getDocument);
m_asyncTask->start();
ProgressManager::addTask(m_asyncTask->future(), Tr::tr("Rendering diff"), "DiffEditor");
}
diff --git a/src/plugins/diffeditor/sidebysidediffeditorwidget.h b/src/plugins/diffeditor/sidebysidediffeditorwidget.h
index 21225fa5580..c12025a6c00 100644
--- a/src/plugins/diffeditor/sidebysidediffeditorwidget.h
+++ b/src/plugins/diffeditor/sidebysidediffeditorwidget.h
@@ -7,7 +7,6 @@
#include "diffeditorwidgetcontroller.h"
#include "selectabletexteditorwidget.h" // TODO: we need DiffSelections here only
-#include <QFutureInterface>
#include <QWidget>
namespace Core { class IContext; }
@@ -23,6 +22,7 @@ class AsyncTask;
}
QT_BEGIN_NAMESPACE
+class QFutureInterfaceBase;
class QMenu;
class QSplitter;
QT_END_NAMESPACE
@@ -40,9 +40,6 @@ class SideBySideDiffOutput;
class SideDiffData
{
public:
- static SideBySideDiffOutput diffOutput(QFutureInterface<void> &fi, int progressMin,
- int progressMax, const DiffEditorInput &input);
-
DiffChunkInfo m_chunkInfo;
// block number, fileInfo. Set for file lines only.
QMap<int, DiffFileInfo> m_fileInfo;
@@ -60,7 +57,6 @@ public:
int blockNumberForFileIndex(int fileIndex) const;
int fileIndexForBlockNumber(int blockNumber) const;
-private:
void setLineNumber(int blockNumber, int lineNumber);
void setFileInfo(int blockNumber, const DiffFileInfo &fileInfo);
void setSkippedLines(int blockNumber, int skippedLines, const QString &contextInfo = {}) {
@@ -88,6 +84,16 @@ public:
QHash<int, int> foldingIndent;
};
+class SideBySideShowResult
+{
+public:
+ QSharedPointer<TextEditor::TextDocument> textDocument{};
+ SideDiffData diffData;
+ DiffSelections selections;
+};
+
+using SideBySideShowResults = std::array<SideBySideShowResult, SideCount>;
+
class SideBySideDiffEditorWidget : public QWidget
{
Q_OBJECT
@@ -135,15 +141,7 @@ private:
bool m_horizontalSync = false;
- struct ShowResult
- {
- QSharedPointer<TextEditor::TextDocument> textDocument{};
- SideDiffData diffData;
- DiffSelections selections;
- };
- using ShowResults = std::array<ShowResult, SideCount>;
-
- std::unique_ptr<Utils::AsyncTask<ShowResults>> m_asyncTask;
+ std::unique_ptr<Utils::AsyncTask<SideBySideShowResults>> m_asyncTask;
};
} // namespace Internal
diff --git a/src/plugins/diffeditor/unifieddiffeditorwidget.cpp b/src/plugins/diffeditor/unifieddiffeditorwidget.cpp
index b937b919f7c..cb113612c20 100644
--- a/src/plugins/diffeditor/unifieddiffeditorwidget.cpp
+++ b/src/plugins/diffeditor/unifieddiffeditorwidget.cpp
@@ -8,23 +8,20 @@
#include "diffeditorplugin.h"
#include "diffeditortr.h"
-#include <QMenu>
-#include <QPainter>
-#include <QScrollBar>
-#include <QTextBlock>
-
#include <coreplugin/icore.h>
#include <coreplugin/progressmanager/progressmanager.h>
#include <texteditor/fontsettings.h>
#include <texteditor/textdocument.h>
-#include <texteditor/textdocumentlayout.h>
#include <texteditor/texteditorsettings.h>
#include <utils/asynctask.h>
#include <utils/mathutils.h>
#include <utils/qtcassert.h>
-#include <utils/tooltip/tooltip.h>
+
+#include <QMenu>
+#include <QScrollBar>
+#include <QTextBlock>
using namespace Core;
using namespace TextEditor;
@@ -48,10 +45,10 @@ UnifiedDiffEditorWidget::UnifiedDiffEditorWidget(QWidget *parent)
connect(this, &QPlainTextEdit::cursorPositionChanged,
this, &UnifiedDiffEditorWidget::slotCursorPositionChangedInEditor);
- auto context = new Core::IContext(this);
+ auto context = new IContext(this);
context->setWidget(this);
- context->setContext(Core::Context(Constants::UNIFIED_VIEW_ID));
- Core::ICore::addContextObject(context);
+ context->setContext(Context(Constants::UNIFIED_VIEW_ID));
+ ICore::addContextObject(context);
}
UnifiedDiffEditorWidget::~UnifiedDiffEditorWidget() = default;
@@ -68,6 +65,14 @@ DiffEditorDocument *UnifiedDiffEditorWidget::diffDocument() const
return m_controller.document();
}
+void UnifiedDiffEditorWidget::setDiff(const QList<FileData> &diffFileList)
+{
+ const GuardLocker locker(m_controller.m_ignoreChanges);
+ clear(Tr::tr("Waiting for data..."));
+ m_controller.m_contextFileData = diffFileList;
+ showDiff();
+}
+
void UnifiedDiffEditorWidget::saveState()
{
if (!m_state.isNull())
@@ -260,14 +265,6 @@ void UnifiedDiffData::setLineNumber(DiffSide side, int blockNumber, int lineNumb
m_lineNumberDigits[side] = qMax(m_lineNumberDigits[side], lineNumberString.count());
}
-void UnifiedDiffEditorWidget::setDiff(const QList<FileData> &diffFileList)
-{
- const GuardLocker locker(m_controller.m_ignoreChanges);
- clear(Tr::tr("Waiting for data..."));
- m_controller.m_contextFileData = diffFileList;
- showDiff();
-}
-
QString UnifiedDiffData::setChunk(const DiffEditorInput &input, const ChunkData &chunkData,
bool lastChunk, int *blockNumber, DiffSelections *selections)
{
@@ -391,8 +388,8 @@ QString UnifiedDiffData::setChunk(const DiffEditorInput &input, const ChunkData
return diffText;
}
-UnifiedDiffOutput UnifiedDiffData::diffOutput(QFutureInterface<void> &fi, int progressMin,
- int progressMax, const DiffEditorInput &input)
+static UnifiedDiffOutput diffOutput(QPromise<UnifiedShowResult> &promise, int progressMin,
+ int progressMax, const DiffEditorInput &input)
{
UnifiedDiffOutput output;
@@ -437,8 +434,8 @@ UnifiedDiffOutput UnifiedDiffData::diffOutput(QFutureInterface<void> &fi, int pr
output.diffData.m_chunkInfo.setChunkIndex(oldBlock, blockNumber - oldBlock, j);
}
}
- fi.setProgressValue(MathUtils::interpolateLinear(++i, 0, count, progressMin, progressMax));
- if (fi.isCanceled())
+ promise.setProgressValue(MathUtils::interpolateLinear(++i, 0, count, progressMin, progressMax));
+ if (promise.isCanceled())
return {};
}
@@ -454,14 +451,14 @@ void UnifiedDiffEditorWidget::showDiff()
return;
}
- m_asyncTask.reset(new AsyncTask<ShowResult>());
+ m_asyncTask.reset(new AsyncTask<UnifiedShowResult>());
m_asyncTask->setFutureSynchronizer(DiffEditorPlugin::futureSynchronizer());
m_controller.setBusyShowing(true);
connect(m_asyncTask.get(), &AsyncTaskBase::done, this, [this] {
if (m_asyncTask->isCanceled() || !m_asyncTask->isResultAvailable()) {
setPlainText(Tr::tr("Retrieving data failed."));
} else {
- const ShowResult result = m_asyncTask->result();
+ const UnifiedShowResult result = m_asyncTask->result();
m_data = result.diffData;
TextDocumentPtr doc(result.textDocument);
{
@@ -481,21 +478,16 @@ void UnifiedDiffEditorWidget::showDiff()
const DiffEditorInput input(&m_controller);
- auto getDocument = [input](QFutureInterface<ShowResult> &futureInterface) {
- auto cleanup = qScopeGuard([&futureInterface] {
- if (futureInterface.isCanceled())
- futureInterface.reportCanceled();
- });
+ auto getDocument = [input](QPromise<UnifiedShowResult> &promise) {
const int progressMax = 100;
const int firstPartMax = 20; // diffOutput is about 4 times quicker than filling document
- futureInterface.setProgressRange(0, progressMax);
- futureInterface.setProgressValue(0);
- QFutureInterface<void> fi = futureInterface;
- const UnifiedDiffOutput output = UnifiedDiffData::diffOutput(fi, 0, firstPartMax, input);
- if (futureInterface.isCanceled())
+ promise.setProgressRange(0, progressMax);
+ promise.setProgressValue(0);
+ const UnifiedDiffOutput output = diffOutput(promise, 0, firstPartMax, input);
+ if (promise.isCanceled())
return;
- const ShowResult result = {TextDocumentPtr(new TextDocument("DiffEditor.UnifiedDiffEditor")),
+ const UnifiedShowResult result = {TextDocumentPtr(new TextDocument("DiffEditor.UnifiedDiffEditor")),
output.diffData, output.selections};
// No need to store the change history
result.textDocument->document()->setUndoRedoEnabled(false);
@@ -512,8 +504,9 @@ void UnifiedDiffEditorWidget::showDiff()
const QString package = output.diffText.mid(currentPos, packageSize);
cursor.insertText(package);
currentPos += package.size();
- fi.setProgressValue(MathUtils::interpolateLinear(currentPos, 0, diffSize, firstPartMax, progressMax));
- if (futureInterface.isCanceled())
+ promise.setProgressValue(MathUtils::interpolateLinear(currentPos, 0, diffSize,
+ firstPartMax, progressMax));
+ if (promise.isCanceled())
return;
}
@@ -525,10 +518,10 @@ void UnifiedDiffEditorWidget::showDiff()
// to caller's thread. We push it to no thread (make object to have no thread affinity),
// and later, in the caller's thread, we pull it back to the caller's thread.
result.textDocument->moveToThread(nullptr);
- futureInterface.reportResult(result);
+ promise.addResult(result);
};
- m_asyncTask->setAsyncCallData(getDocument);
+ m_asyncTask->setConcurrentCallData(getDocument);
m_asyncTask->start();
ProgressManager::addTask(m_asyncTask->future(), Tr::tr("Rendering diff"), "DiffEditor");
}
diff --git a/src/plugins/diffeditor/unifieddiffeditorwidget.h b/src/plugins/diffeditor/unifieddiffeditorwidget.h
index 842eec9ea36..cbad273ab2f 100644
--- a/src/plugins/diffeditor/unifieddiffeditorwidget.h
+++ b/src/plugins/diffeditor/unifieddiffeditorwidget.h
@@ -6,8 +6,6 @@
#include "diffeditorwidgetcontroller.h"
#include "selectabletexteditorwidget.h"
-#include <QFutureInterface>
-
namespace Core { class IContext; }
namespace TextEditor { class FontSettings; }
@@ -17,6 +15,10 @@ template <typename R>
class AsyncTask;
}
+QT_BEGIN_NAMESPACE
+class QFutureInterfaceBase;
+QT_END_NAMESPACE
+
namespace DiffEditor {
class ChunkData;
@@ -31,9 +33,6 @@ class UnifiedDiffOutput;
class UnifiedDiffData
{
public:
- static UnifiedDiffOutput diffOutput(QFutureInterface<void> &fi, int progressMin, int progressMax,
- const DiffEditorInput &input);
-
DiffChunkInfo m_chunkInfo;
// block number, visual line number.
QMap<int, DiffFileInfoArray> m_fileInfo;
@@ -45,10 +44,10 @@ public:
int blockNumberForFileIndex(int fileIndex) const;
int fileIndexForBlockNumber(int blockNumber) const;
-private:
- void setLineNumber(DiffSide side, int blockNumber, int lineNumber, int rowNumberInChunk);
QString setChunk(const DiffEditorInput &input, const ChunkData &chunkData,
bool lastChunk, int *blockNumber, DiffSelections *selections);
+private:
+ void setLineNumber(DiffSide side, int blockNumber, int lineNumber, int rowNumberInChunk);
};
class UnifiedDiffOutput
@@ -63,6 +62,14 @@ public:
DiffSelections selections;
};
+class UnifiedShowResult
+{
+public:
+ QSharedPointer<TextEditor::TextDocument> textDocument;
+ UnifiedDiffData diffData;
+ DiffSelections selections;
+};
+
class UnifiedDiffEditorWidget final : public SelectableTextEditorWidget
{
Q_OBJECT
@@ -105,14 +112,7 @@ private:
DiffEditorWidgetController m_controller;
QByteArray m_state;
- struct ShowResult
- {
- QSharedPointer<TextEditor::TextDocument> textDocument;
- UnifiedDiffData diffData;
- DiffSelections selections;
- };
-
- std::unique_ptr<Utils::AsyncTask<ShowResult>> m_asyncTask;
+ std::unique_ptr<Utils::AsyncTask<UnifiedShowResult>> m_asyncTask;
};
} // namespace Internal
diff --git a/src/plugins/docker/dockerapi.cpp b/src/plugins/docker/dockerapi.cpp
index 75256f9473f..eb77059a544 100644
--- a/src/plugins/docker/dockerapi.cpp
+++ b/src/plugins/docker/dockerapi.cpp
@@ -6,9 +6,9 @@
#include "dockertr.h"
#include <coreplugin/progressmanager/progressmanager.h>
+#include <utils/asynctask.h>
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
-#include <utils/runextensions.h>
#include <QLoggingCategory>
@@ -65,7 +65,7 @@ void DockerApi::checkCanConnect(bool async)
m_dockerDaemonAvailable = std::nullopt;
emit dockerDaemonAvailableChanged();
- auto future = Utils::runAsync([lk = std::move(lk), this] {
+ auto future = Utils::asyncRun([lk = std::move(lk), this] {
m_dockerDaemonAvailable = canConnect();
emit dockerDaemonAvailableChanged();
});
diff --git a/src/plugins/docker/dockerdevice.cpp b/src/plugins/docker/dockerdevice.cpp
index 2a955f131fe..f1b650268e5 100644
--- a/src/plugins/docker/dockerdevice.cpp
+++ b/src/plugins/docker/dockerdevice.cpp
@@ -17,6 +17,7 @@
#include <projectexplorer/buildconfiguration.h>
#include <projectexplorer/devicesupport/devicemanager.h>
#include <projectexplorer/devicesupport/idevicewidget.h>
+#include <projectexplorer/devicesupport/processlist.h>
#include <projectexplorer/kitinformation.h>
#include <projectexplorer/kitmanager.h>
#include <projectexplorer/project.h>
@@ -48,6 +49,7 @@
#include <utils/qtcprocess.h>
#include <utils/sortfiltermodel.h>
#include <utils/temporaryfile.h>
+#include <utils/terminalhooks.h>
#include <utils/treemodel.h>
#include <utils/utilsicons.h>
@@ -124,7 +126,6 @@ public:
RunResult runInShell(const CommandLine &cmdLine,
const QByteArray &stdInData) const override;
QString mapToDevicePath(const QString &hostPath) const override;
- OsType osType(const FilePath &filePath) const override;
DockerDevicePrivate *m_dev = nullptr;
};
@@ -142,7 +143,7 @@ public:
RunResult runInShell(const CommandLine &cmd, const QByteArray &stdInData = {});
- void updateContainerAccess();
+ bool updateContainerAccess();
void changeMounts(QStringList newMounts);
bool ensureReachable(const FilePath &other);
void shutdown();
@@ -160,15 +161,17 @@ public:
Environment environment();
CommandLine withDockerExecCmd(const CommandLine &cmd,
- Environment *env = nullptr,
- FilePath *workDir = nullptr,
- bool interactive = false);
+ const std::optional<Environment> &env = std::nullopt,
+ const std::optional<FilePath> &workDir = std::nullopt,
+ bool interactive = false,
+ bool includeMarker = true,
+ bool withPty = false);
bool prepareForBuild(const Target *target);
Tasks validateMounts() const;
bool createContainer();
- void startContainer();
+ bool startContainer();
void stopCurrentContainer();
void fetchSystemEnviroment();
@@ -183,6 +186,8 @@ public:
QStringList createMountArgs() const;
+ bool isImageAvailable() const;
+
DockerDevice *const q;
DockerDeviceData m_data;
DockerSettings *m_settings;
@@ -236,10 +241,10 @@ DockerProcessImpl::DockerProcessImpl(IDevice::ConstPtr device, DockerDevicePriva
});
connect(&m_process, &QtcProcess::readyReadStandardOutput, this, [this] {
+ QByteArray output = m_process.readAllRawStandardOutput();
if (!m_hasReceivedFirstOutput) {
- QByteArray output = m_process.readAllRawStandardOutput();
qsizetype idx = output.indexOf('\n');
- QByteArray firstLine = output.left(idx);
+ QByteArray firstLine = output.left(idx).trimmed();
QByteArray rest = output.mid(idx + 1);
qCDebug(dockerDeviceLog)
<< "Process first line received:" << m_process.commandLine() << firstLine;
@@ -250,24 +255,41 @@ DockerProcessImpl::DockerProcessImpl(IDevice::ConstPtr device, DockerDevicePriva
if (ok)
emit started(m_remotePID);
- if (rest.size() > 0)
- emit readyRead(rest, {});
+ // In case we already received some error output, send it now.
+ const QByteArray stdErr = m_process.readAllRawStandardError();
+ if (rest.size() > 0 || stdErr.size() > 0)
+ emit readyRead(rest, stdErr);
m_hasReceivedFirstOutput = true;
return;
}
}
- emit readyRead(m_process.readAllRawStandardOutput(), {});
+ emit readyRead(output, {});
});
connect(&m_process, &QtcProcess::readyReadStandardError, this, [this] {
- emit readyRead({}, m_process.readAllRawStandardError());
+ if (m_remotePID)
+ emit readyRead({}, m_process.readAllRawStandardError());
});
connect(&m_process, &QtcProcess::done, this, [this] {
qCDebug(dockerDeviceLog) << "Process exited:" << m_process.commandLine()
<< "with code:" << m_process.resultData().m_exitCode;
- emit done(m_process.resultData());
+
+ Utils::ProcessResultData resultData = m_process.resultData();
+
+ if (m_remotePID == 0) {
+ resultData.m_error = QProcess::FailedToStart;
+ qCWarning(dockerDeviceLog) << "Process failed to start:" << m_process.commandLine();
+ QByteArray stdOut = m_process.readAllRawStandardOutput();
+ QByteArray stdErr = m_process.readAllRawStandardError();
+ if (!stdOut.isEmpty())
+ qCWarning(dockerDeviceLog) << "stdout:" << stdOut;
+ if (!stdErr.isEmpty())
+ qCWarning(dockerDeviceLog) << "stderr:" << stdErr;
+ }
+
+ emit done(resultData);
});
}
@@ -282,23 +304,27 @@ void DockerProcessImpl::start()
m_process.setProcessImpl(m_setup.m_processImpl);
m_process.setProcessMode(m_setup.m_processMode);
m_process.setTerminalMode(m_setup.m_terminalMode);
+ m_process.setPtyData(m_setup.m_ptyData);
m_process.setReaperTimeout(m_setup.m_reaperTimeout);
m_process.setWriteData(m_setup.m_writeData);
m_process.setProcessChannelMode(m_setup.m_processChannelMode);
m_process.setExtraData(m_setup.m_extraData);
m_process.setStandardInputFile(m_setup.m_standardInputFile);
m_process.setAbortOnMetaChars(m_setup.m_abortOnMetaChars);
+ m_process.setCreateConsoleOnWindows(m_setup.m_createConsoleOnWindows);
if (m_setup.m_lowPriority)
m_process.setLowPriority();
const bool interactive = m_setup.m_processMode == ProcessMode::Writer
|| !m_setup.m_writeData.isEmpty();
- const CommandLine fullCommandLine = m_devicePrivate
- ->withDockerExecCmd(m_setup.m_commandLine,
- &m_setup.m_environment,
- &m_setup.m_workingDirectory,
- interactive);
+ const CommandLine fullCommandLine
+ = m_devicePrivate->withDockerExecCmd(m_setup.m_commandLine,
+ m_setup.m_environment,
+ m_setup.m_workingDirectory,
+ interactive,
+ true,
+ m_setup.m_ptyData.has_value());
m_process.setCommand(fullCommandLine);
m_process.start();
@@ -367,12 +393,6 @@ QString DockerDeviceFileAccess::mapToDevicePath(const QString &hostPath) const
return newPath;
}
-OsType DockerDeviceFileAccess::osType(const FilePath &filePath) const
-{
- QTC_ASSERT(m_dev, return UnixDeviceFileAccess::osType(filePath));
- return m_dev->q->osType();
-}
-
DockerDevice::DockerDevice(DockerSettings *settings, const DockerDeviceData &data)
: d(new DockerDevicePrivate(this, settings, data))
{
@@ -380,20 +400,23 @@ DockerDevice::DockerDevice(DockerSettings *settings, const DockerDeviceData &dat
setDisplayType(Tr::tr("Docker"));
setOsType(OsTypeOtherUnix);
setDefaultDisplayName(Tr::tr("Docker Image"));
-
+ setupId(IDevice::ManuallyAdded);
+ setType(Constants::DOCKER_DEVICE_TYPE);
+ setMachineType(IDevice::Hardware);
setDisplayName(Tr::tr("Docker Image \"%1\" (%2)").arg(data.repoAndTag()).arg(data.imageId));
setAllowEmptyCommand(true);
- setOpenTerminal([this, settings](const Environment &env, const FilePath &workingDir) {
+ setOpenTerminal([this](const Environment &env, const FilePath &workingDir) {
Q_UNUSED(env); // TODO: That's the runnable's environment in general. Use it via -e below.
- updateContainerAccess();
+ if (!updateContainerAccess())
+ return;
+
if (d->containerId().isEmpty()) {
MessageManager::writeDisrupting(Tr::tr("Error starting remote shell. No container."));
return;
}
QtcProcess *proc = new QtcProcess(d);
- proc->setTerminalMode(TerminalMode::On);
QObject::connect(proc, &QtcProcess::done, [proc] {
if (proc->error() != QProcess::UnknownError && MessageManager::instance()) {
@@ -403,10 +426,10 @@ DockerDevice::DockerDevice(DockerSettings *settings, const DockerDeviceData &dat
proc->deleteLater();
});
- const QString wd = workingDir.isEmpty() ? "/" : workingDir.path();
- proc->setCommand({settings->dockerBinaryPath.filePath(),
- {"exec", "-it", "-w", wd, d->containerId(), "/bin/sh"}});
- proc->setEnvironment(Environment::systemEnvironment()); // The host system env. Intentional.
+ proc->setTerminalMode(TerminalMode::On);
+ proc->setEnvironment(env);
+ proc->setWorkingDirectory(workingDir);
+ proc->setCommand({Terminal::defaultShellForDevice(rootPath()), {}});
proc->start();
});
@@ -440,47 +463,58 @@ void DockerDevice::setData(const DockerDeviceData &data)
d->setData(data);
}
-void DockerDevice::updateContainerAccess() const
+bool DockerDevice::updateContainerAccess() const
{
- d->updateContainerAccess();
+ return d->updateContainerAccess();
}
CommandLine DockerDevicePrivate::withDockerExecCmd(const CommandLine &cmd,
- Environment *env,
- FilePath *workDir,
- bool interactive)
+ const std::optional<Environment> &env,
+ const std::optional<FilePath> &workDir,
+ bool interactive,
+ bool includeMarker,
+ bool withPty)
{
if (!m_settings)
return {};
- updateContainerAccess();
+ if (!updateContainerAccess())
+ return {};
CommandLine dockerCmd{m_settings->dockerBinaryPath.filePath(), {"exec"}};
if (interactive)
dockerCmd.addArg("-i");
+ if (withPty)
+ dockerCmd.addArg("-t");
+
if (env) {
- for (auto it = env->constBegin(); it != env->constEnd(); ++it) {
+ env->forEachEntry([&](const QString &key, const QString &value, bool) {
dockerCmd.addArg("-e");
- dockerCmd.addArg(env->key(it) + "=" + env->expandedValueForKey(env->key(it)));
- }
+ dockerCmd.addArg(key + "=" + env->expandVariables(value));
+ });
}
if (workDir && !workDir->isEmpty())
- dockerCmd.addArgs({"-w", workDir->path()});
+ dockerCmd.addArgs({"-w", workDir->onDevice(q->rootPath()).nativePath()});
dockerCmd.addArg(m_container);
- dockerCmd.addArgs({"/bin/sh", "-c"});
- CommandLine exec("exec");
- exec.addCommandLineAsArgs(cmd);
+ if (includeMarker) {
+ dockerCmd.addArgs({"/bin/sh", "-c"});
+
+ CommandLine exec("exec");
+ exec.addCommandLineAsArgs(cmd, CommandLine::Raw);
- CommandLine echo("echo");
- echo.addArgs("__qtc$$qtc__", CommandLine::Raw);
- echo.addCommandLineWithAnd(exec);
+ CommandLine echo("echo");
+ echo.addArgs("__qtc$$qtc__", CommandLine::Raw);
+ echo.addCommandLineWithAnd(exec);
- dockerCmd.addCommandLineAsSingleArg(echo);
+ dockerCmd.addCommandLineAsSingleArg(echo);
+ } else {
+ dockerCmd.addCommandLineAsArgs(cmd, CommandLine::Raw);
+ }
return dockerCmd;
}
@@ -494,7 +528,14 @@ void DockerDevicePrivate::stopCurrentContainer()
if (!DockerApi::isDockerDaemonAvailable(false).value_or(false))
return;
- m_shell.reset();
+ if (m_shell) {
+ // We have to disconnect the shell from the device, otherwise it will try to
+ // tell us about the container being stopped. Since that signal is emitted in a different
+ // thread, it would be delayed received by us when we might already have started
+ // a new shell.
+ m_shell->disconnect(this);
+ m_shell.reset();
+ }
QtcProcess proc;
proc.setCommand({m_settings->dockerBinaryPath.filePath(), {"container", "stop", m_container}});
@@ -598,11 +639,30 @@ QStringList DockerDevicePrivate::createMountArgs() const
return cmds;
}
+bool DockerDevicePrivate::isImageAvailable() const
+{
+ QtcProcess proc;
+ proc.setCommand(
+ {m_settings->dockerBinaryPath.filePath(),
+ {"image", "list", m_data.repoAndTag(), "--format", "{{.Repository}}:{{.Tag}}"}});
+ proc.runBlocking();
+ if (proc.result() != ProcessResult::FinishedWithSuccess)
+ return false;
+
+ if (proc.stdOut().trimmed() == m_data.repoAndTag())
+ return true;
+
+ return false;
+}
+
bool DockerDevicePrivate::createContainer()
{
if (!m_settings)
return false;
+ if (!isImageAvailable())
+ return false;
+
const QString display = HostOsInfo::isLinuxHost() ? QString(":0")
: QString("host.docker.internal:0");
CommandLine dockerCreate{m_settings->dockerBinaryPath.filePath(),
@@ -655,20 +715,21 @@ bool DockerDevicePrivate::createContainer()
return true;
}
-void DockerDevicePrivate::startContainer()
+bool DockerDevicePrivate::startContainer()
{
if (!createContainer())
- return;
+ return false;
m_shell = std::make_unique<ContainerShell>(m_settings, m_container, q->rootPath());
connect(m_shell.get(), &DeviceShell::done, this, [this](const ProcessResultData &resultData) {
+ if (m_shell)
+ m_shell.release()->deleteLater();
if (resultData.m_error != QProcess::UnknownError
|| resultData.m_exitStatus == QProcess::NormalExit)
return;
qCWarning(dockerDeviceLog) << "Container shell encountered error:" << resultData.m_error;
- m_shell.release()->deleteLater();
DockerApi::recheckDockerDaemon();
MessageManager::writeFlashing(Tr::tr("Docker daemon appears to be not running. "
@@ -677,23 +738,25 @@ void DockerDevicePrivate::startContainer()
"or restart Qt Creator."));
});
- if (!m_shell->start()) {
- qCWarning(dockerDeviceLog) << "Container shell failed to start";
- }
+ if (m_shell->start())
+ return true;
+
+ qCWarning(dockerDeviceLog) << "Container shell failed to start";
+ return false;
}
-void DockerDevicePrivate::updateContainerAccess()
+bool DockerDevicePrivate::updateContainerAccess()
{
if (m_isShutdown)
- return;
+ return false;
if (DockerApi::isDockerDaemonAvailable(false).value_or(false) == false)
- return;
+ return false;
if (m_shell)
- return;
+ return true;
- startContainer();
+ return startContainer();
}
void DockerDevice::setMounts(const QStringList &mounts) const
@@ -777,9 +840,9 @@ PortsGatheringMethod DockerDevice::portsGatheringMethod() const
&Port::parseFromSedOutput};
};
-DeviceProcessList *DockerDevice::createProcessListModel(QObject *) const
+DeviceProcessList *DockerDevice::createProcessListModel(QObject *parent) const
{
- return nullptr;
+ return new ProcessList(sharedFromThis(), parent);
}
DeviceTester *DockerDevice::createDeviceTester() const
@@ -865,7 +928,8 @@ void DockerDevice::aboutToBeRemoved() const
void DockerDevicePrivate::fetchSystemEnviroment()
{
- updateContainerAccess();
+ if (!updateContainerAccess())
+ return;
if (m_shell && m_shell->state() == DeviceShell::State::Succeeded) {
const RunResult result = runInShell({"env", {}});
@@ -889,7 +953,8 @@ void DockerDevicePrivate::fetchSystemEnviroment()
RunResult DockerDevicePrivate::runInShell(const CommandLine &cmd, const QByteArray &stdInData)
{
- updateContainerAccess();
+ if (!updateContainerAccess())
+ return {};
QTC_ASSERT(m_shell, return {});
return m_shell->runInShell(cmd, stdInData);
}
@@ -1057,9 +1122,6 @@ public:
QTC_ASSERT(item, return {});
auto device = DockerDevice::create(m_settings, *item);
- device->setupId(IDevice::ManuallyAdded);
- device->setType(Constants::DOCKER_DEVICE_TYPE);
- device->setMachineType(IDevice::Hardware);
return device;
}
@@ -1196,6 +1258,9 @@ bool DockerDevicePrivate::ensureReachable(const FilePath &other)
return true;
}
+ if (q->filePath(other.path()).exists())
+ return false;
+
addTemporaryMount(other, other);
return true;
}
diff --git a/src/plugins/docker/dockerdevice.h b/src/plugins/docker/dockerdevice.h
index 3ecc0118d49..affe4cf2714 100644
--- a/src/plugins/docker/dockerdevice.h
+++ b/src/plugins/docker/dockerdevice.h
@@ -75,7 +75,7 @@ public:
bool canAutoDetectPorts() const override;
ProjectExplorer::PortsGatheringMethod portsGatheringMethod() const override;
- bool canCreateProcessModel() const override { return false; }
+ bool canCreateProcessModel() const override { return true; }
ProjectExplorer::DeviceProcessList *createProcessListModel(QObject *parent) const override;
bool hasDeviceTester() const override { return false; }
ProjectExplorer::DeviceTester *createDeviceTester() const override;
@@ -95,7 +95,7 @@ public:
void setData(const DockerDeviceData &data);
- void updateContainerAccess() const;
+ bool updateContainerAccess() const;
void setMounts(const QStringList &mounts) const;
bool prepareForBuild(const ProjectExplorer::Target *target) override;
diff --git a/src/plugins/docker/dockersettings.cpp b/src/plugins/docker/dockersettings.cpp
index bfec44f2433..60e9ec4ff2a 100644
--- a/src/plugins/docker/dockersettings.cpp
+++ b/src/plugins/docker/dockersettings.cpp
@@ -5,13 +5,13 @@
#include "dockerconstants.h"
#include "dockertr.h"
-#include "utils/hostosinfo.h"
#include <coreplugin/icore.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <utils/filepath.h>
+#include <utils/hostosinfo.h>
#include <utils/layoutbuilder.h>
using namespace Utils;
diff --git a/src/plugins/fakevim/fakevim.qbs b/src/plugins/fakevim/fakevim.qbs
index cdfb9e24ddf..a4dad38428f 100644
--- a/src/plugins/fakevim/fakevim.qbs
+++ b/src/plugins/fakevim/fakevim.qbs
@@ -25,9 +25,7 @@ QtcPlugin {
"fakevimtr.h",
]
- Group {
- name: "Tests"
- condition: qtc.testsEnabled
+ QtcTestFiles {
files: ["fakevim_test.cpp"]
}
}
diff --git a/src/plugins/genericprojectmanager/genericproject.cpp b/src/plugins/genericprojectmanager/genericproject.cpp
index 518b765bcb2..5553e0a7404 100644
--- a/src/plugins/genericprojectmanager/genericproject.cpp
+++ b/src/plugins/genericprojectmanager/genericproject.cpp
@@ -96,7 +96,7 @@ private:
////////////////////////////////////////////////////////////////////////////////////
//
-// GenericProjectNode
+// GenericBuildSystem
//
////////////////////////////////////////////////////////////////////////////////////
@@ -400,7 +400,7 @@ static QStringList readFlags(const QString &filePath)
return QStringList();
QStringList flags;
for (const auto &line : lines)
- flags.append(ProcessArgs::splitArgs(line));
+ flags.append(ProcessArgs::splitArgs(line, HostOsInfo::hostOs()));
return flags;
}
@@ -569,8 +569,8 @@ void GenericBuildSystem::refreshCppCodeModel()
rpp.setQtVersion(kitInfo.projectPartQtVersion);
rpp.setHeaderPaths(m_projectIncludePaths);
rpp.setConfigFileName(m_configFileName);
- rpp.setFlagsForCxx({nullptr, m_cxxflags, projectDirectory().toString()});
- rpp.setFlagsForC({nullptr, m_cflags, projectDirectory().toString()});
+ rpp.setFlagsForCxx({nullptr, m_cxxflags, projectDirectory()});
+ rpp.setFlagsForC({nullptr, m_cflags, projectDirectory()});
static const auto sourceFilesToStringList = [](const SourceFiles &sourceFiles) {
return Utils::transform(sourceFiles, [](const SourceFile &f) {
diff --git a/src/plugins/git/branchmodel.cpp b/src/plugins/git/branchmodel.cpp
index 3c4971fa734..7b57df0739d 100644
--- a/src/plugins/git/branchmodel.cpp
+++ b/src/plugins/git/branchmodel.cpp
@@ -229,6 +229,7 @@ public:
QString currentSha;
QDateTime currentDateTime;
QStringList obsoleteLocalBranches;
+ std::unique_ptr<TaskTree> refreshTask;
bool oldBranchesIncluded = false;
struct OldEntry
@@ -399,50 +400,83 @@ void BranchModel::clear()
d->obsoleteLocalBranches.clear();
}
-bool BranchModel::refresh(const FilePath &workingDirectory, QString *errorMessage)
+void BranchModel::refresh(const FilePath &workingDirectory, ShowError showError)
{
+ if (d->refreshTask) {
+ endResetModel(); // for the running task tree.
+ d->refreshTask.reset(); // old running tree is reset, no handlers are being called
+ }
beginResetModel();
clear();
if (workingDirectory.isEmpty()) {
endResetModel();
- return true;
+ return;
}
- d->currentSha = d->client->synchronousTopRevision(workingDirectory, &d->currentDateTime);
- QStringList args = {"--format=%(objectname)\t%(refname)\t%(upstream:short)\t"
- "%(*objectname)\t%(committerdate:raw)\t%(*committerdate:raw)",
- "refs/heads/**",
- "refs/remotes/**"};
- if (d->client->settings().showTags.value())
- args << "refs/tags/**";
- QString output;
- if (!d->client->synchronousForEachRefCmd(workingDirectory, args, &output, errorMessage)) {
- endResetModel();
- return false;
- }
+ using namespace Tasking;
+ const Process topRevisionProc =
+ d->client->topRevision(workingDirectory,
+ [=](const QString &ref, const QDateTime &dateTime) {
+ d->currentSha = ref;
+ d->currentDateTime = dateTime;
+ });
+
+ const auto setupForEachRef = [=](QtcProcess &process) {
+ d->workingDirectory = workingDirectory;
+ QStringList args = {"for-each-ref",
+ "--format=%(objectname)\t%(refname)\t%(upstream:short)\t"
+ "%(*objectname)\t%(committerdate:raw)\t%(*committerdate:raw)",
+ "refs/heads/**",
+ "refs/remotes/**"};
+ if (d->client->settings().showTags.value())
+ args << "refs/tags/**";
+ d->client->setupCommand(process, workingDirectory, args);
+ };
- d->workingDirectory = workingDirectory;
- const QStringList lines = output.split('\n');
- for (const QString &l : lines)
- d->parseOutputLine(l);
- d->flushOldEntries();
+ const auto forEachRefDone = [=](const QtcProcess &process) {
+ const QString output = process.stdOut();
+ const QStringList lines = output.split('\n');
+ for (const QString &l : lines)
+ d->parseOutputLine(l);
+ d->flushOldEntries();
+
+ d->updateAllUpstreamStatus(d->rootNode->children.at(LocalBranches));
+ if (d->currentBranch) {
+ if (d->currentBranch->isLocal())
+ d->currentBranch = nullptr;
+ setCurrentBranch();
+ }
+ if (!d->currentBranch) {
+ BranchNode *local = d->rootNode->children.at(LocalBranches);
+ d->currentBranch = d->headNode = new BranchNode(
+ Tr::tr("Detached HEAD"), "HEAD", {}, d->currentDateTime);
+ local->prepend(d->headNode);
+ }
+ };
- d->updateAllUpstreamStatus(d->rootNode->children.at(LocalBranches));
- if (d->currentBranch) {
- if (d->currentBranch->isLocal())
- d->currentBranch = nullptr;
- setCurrentBranch();
- }
- if (!d->currentBranch) {
- BranchNode *local = d->rootNode->children.at(LocalBranches);
- d->currentBranch = d->headNode = new BranchNode(Tr::tr("Detached HEAD"), "HEAD", QString(),
- d->currentDateTime);
- local->prepend(d->headNode);
- }
+ const auto forEachRefError = [=](const QtcProcess &process) {
+ if (showError == ShowError::No)
+ return;
+ const QString message = Tr::tr("Cannot run \"%1\" in \"%2\": %3")
+ .arg("git for-each-ref")
+ .arg(workingDirectory.toUserOutput())
+ .arg(process.cleanedStdErr());
+ VcsBase::VcsOutputWindow::appendError(message);
+ };
- endResetModel();
+ const auto finalize = [this] {
+ endResetModel();
+ d->refreshTask.release()->deleteLater();
+ };
- return true;
+ const Group root {
+ topRevisionProc,
+ Process(setupForEachRef, forEachRefDone, forEachRefError),
+ OnGroupDone(finalize),
+ OnGroupError(finalize)
+ };
+ d->refreshTask.reset(new TaskTree(root));
+ d->refreshTask->start();
}
void BranchModel::setCurrentBranch()
@@ -469,7 +503,7 @@ void BranchModel::renameBranch(const QString &oldName, const QString &newName)
&output, &errorMessage))
VcsOutputWindow::appendError(errorMessage);
else
- refresh(d->workingDirectory, &errorMessage);
+ refresh(d->workingDirectory);
}
void BranchModel::renameTag(const QString &oldName, const QString &newName)
@@ -482,7 +516,7 @@ void BranchModel::renameTag(const QString &oldName, const QString &newName)
&output, &errorMessage)) {
VcsOutputWindow::appendError(errorMessage);
} else {
- refresh(d->workingDirectory, &errorMessage);
+ refresh(d->workingDirectory);
}
}
diff --git a/src/plugins/git/branchmodel.h b/src/plugins/git/branchmodel.h
index 580af612985..de126e9a5eb 100644
--- a/src/plugins/git/branchmodel.h
+++ b/src/plugins/git/branchmodel.h
@@ -34,7 +34,8 @@ public:
Qt::ItemFlags flags(const QModelIndex &index) const override;
void clear();
- bool refresh(const Utils::FilePath &workingDirectory, QString *errorMessage);
+ enum class ShowError { No, Yes };
+ void refresh(const Utils::FilePath &workingDirectory, ShowError showError = ShowError::No);
void renameBranch(const QString &oldName, const QString &newName);
void renameTag(const QString &oldName, const QString &newName);
diff --git a/src/plugins/git/branchview.cpp b/src/plugins/git/branchview.cpp
index 528ec288f97..df9c255c587 100644
--- a/src/plugins/git/branchview.cpp
+++ b/src/plugins/git/branchview.cpp
@@ -160,9 +160,7 @@ void BranchView::refresh(const FilePath &repository, bool force)
if (!isVisible())
return;
- QString errorMessage;
- if (!m_model->refresh(m_repository, &errorMessage))
- VcsBase::VcsOutputWindow::appendError(errorMessage);
+ m_model->refresh(m_repository, BranchModel::ShowError::Yes);
}
void BranchView::refreshCurrentBranch()
@@ -225,6 +223,7 @@ void BranchView::slotCustomContextMenu(const QPoint &point)
const bool isTag = m_model->isTag(index);
const bool hasActions = m_model->isLeaf(index);
const bool currentLocal = m_model->isLocal(currentBranch);
+ std::unique_ptr<TaskTree> taskTree;
QMenu contextMenu;
contextMenu.addAction(Tr::tr("&Add..."), this, &BranchView::add);
@@ -268,19 +267,19 @@ void BranchView::slotCustomContextMenu(const QPoint &point)
resetMenu->addAction(Tr::tr("&Mixed"), this, [this] { reset("mixed"); });
resetMenu->addAction(Tr::tr("&Soft"), this, [this] { reset("soft"); });
contextMenu.addMenu(resetMenu);
- QString mergeTitle;
- if (isFastForwardMerge()) {
- contextMenu.addAction(Tr::tr("&Merge \"%1\" into \"%2\" (Fast-Forward)")
- .arg(indexName, currentName),
- this, [this] { merge(true); });
- mergeTitle = Tr::tr("Merge \"%1\" into \"%2\" (No &Fast-Forward)")
- .arg(indexName, currentName);
- } else {
- mergeTitle = Tr::tr("&Merge \"%1\" into \"%2\"")
- .arg(indexName, currentName);
- }
+ QAction *mergeAction = contextMenu.addAction(Tr::tr("&Merge \"%1\" into \"%2\"")
+ .arg(indexName, currentName),
+ this,
+ [this] { merge(false); });
+ taskTree.reset(onFastForwardMerge([&] {
+ auto ffMerge = new QAction(
+ Tr::tr("&Merge \"%1\" into \"%2\" (Fast-Forward)").arg(indexName, currentName));
+ connect(ffMerge, &QAction::triggered, this, [this] { merge(true); });
+ contextMenu.insertAction(mergeAction, ffMerge);
+ mergeAction->setText(Tr::tr("Merge \"%1\" into \"%2\" (No &Fast-Forward)")
+ .arg(indexName, currentName));
+ }));
- contextMenu.addAction(mergeTitle, this, [this] { merge(false); });
contextMenu.addAction(Tr::tr("&Rebase \"%1\" on \"%2\"")
.arg(currentName, indexName),
this, &BranchView::rebase);
@@ -523,13 +522,50 @@ bool BranchView::reset(const QByteArray &resetType)
return false;
}
-bool BranchView::isFastForwardMerge()
+TaskTree *BranchView::onFastForwardMerge(const std::function<void()> &callback)
{
+ using namespace Tasking;
+
const QModelIndex selected = selectedIndex();
QTC_CHECK(selected != m_model->currentBranch());
const QString branch = m_model->fullName(selected, true);
- return GitClient::instance()->isFastForwardMerge(m_repository, branch);
+
+ struct FastForwardStorage
+ {
+ QString mergeBase;
+ QString topRevision;
+ };
+
+ const TreeStorage<FastForwardStorage> storage;
+
+ GitClient *client = GitClient::instance();
+ const auto setupMergeBase = [=](QtcProcess &process) {
+ client->setupCommand(process, m_repository, {"merge-base", "HEAD", branch});
+ };
+ const auto onMergeBaseDone = [storage](const QtcProcess &process) {
+ storage->mergeBase = process.cleanedStdOut().trimmed();
+ };
+
+ const Process topRevisionProc = client->topRevision(
+ m_repository,
+ [storage](const QString &revision, const QDateTime &) {
+ storage->topRevision = revision;
+ });
+
+ const Group root {
+ Storage(storage),
+ parallel,
+ Process(setupMergeBase, onMergeBaseDone),
+ topRevisionProc,
+ OnGroupDone([storage, callback] {
+ if (storage->mergeBase == storage->topRevision)
+ callback();
+ })
+ };
+ auto taskTree = new TaskTree(root);
+ taskTree->start();
+ return taskTree;
}
bool BranchView::merge(bool allowFastForward)
diff --git a/src/plugins/git/branchview.h b/src/plugins/git/branchview.h
index 9a1a3c1e455..c1ac77c82be 100644
--- a/src/plugins/git/branchview.h
+++ b/src/plugins/git/branchview.h
@@ -20,6 +20,7 @@ QT_END_NAMESPACE;
namespace Utils {
class ElidingLabel;
class NavigationTreeView;
+class TaskTree;
} // Utils
namespace Git::Internal {
@@ -54,7 +55,7 @@ private:
bool remove();
bool rename();
bool reset(const QByteArray &resetType);
- bool isFastForwardMerge();
+ Utils::TaskTree *onFastForwardMerge(const std::function<void()> &callback);
bool merge(bool allowFastForward);
void rebase();
bool cherryPick();
diff --git a/src/plugins/git/gerrit/gerritpushdialog.cpp b/src/plugins/git/gerrit/gerritpushdialog.cpp
index 057d4cd6448..fa45e2591f8 100644
--- a/src/plugins/git/gerrit/gerritpushdialog.cpp
+++ b/src/plugins/git/gerrit/gerritpushdialog.cpp
@@ -125,7 +125,7 @@ GerritPushDialog::GerritPushDialog(const Utils::FilePath &workingDir, const QStr
"Unchecked - Remove mark.\n"
"Partially checked - Do not change current state."));
m_commitView->setToolTip(::Git::Tr::tr(
- "Pushes the selected commit and all dependent commits."));
+ "Pushes the selected commit and all commits it depends on."));
m_reviewersLineEdit->setToolTip(::Git::Tr::tr("Comma-separated list of reviewers.\n"
"\n"
"Reviewers can be specified by nickname or email address. Spaces not allowed.\n"
diff --git a/src/plugins/git/gitclient.cpp b/src/plugins/git/gitclient.cpp
index e419449fe02..66f03aa974f 100644
--- a/src/plugins/git/gitclient.cpp
+++ b/src/plugins/git/gitclient.cpp
@@ -171,7 +171,7 @@ GitDiffEditorController::GitDiffEditorController(IDocument *document,
VcsOutputWindow::appendCommand(process.workingDirectory(), process.commandLine());
};
const auto onDiffDone = [diffInputStorage](const QtcProcess &process) {
- *diffInputStorage.activeStorage() = process.cleanedStdOut();
+ *diffInputStorage = process.cleanedStdOut();
};
const Group root {
@@ -258,7 +258,7 @@ FileListDiffController::FileListDiffController(IDocument *document, const QStrin
};
const auto onStagingDone = [storage, diffInputStorage] {
- *diffInputStorage.activeStorage() = storage->m_stagedOutput + storage->m_unstagedOutput;
+ *diffInputStorage = storage->m_stagedOutput + storage->m_unstagedOutput;
};
const Group root {
@@ -455,7 +455,7 @@ ShowController::ShowController(IDocument *document, const QString &id)
VcsOutputWindow::appendCommand(process.workingDirectory(), process.commandLine());
};
const auto onDiffDone = [diffInputStorage](const QtcProcess &process) {
- *diffInputStorage.activeStorage() = process.cleanedStdOut();
+ *diffInputStorage = process.cleanedStdOut();
};
const Group root {
@@ -1730,19 +1730,28 @@ bool GitClient::synchronousRevParseCmd(const FilePath &workingDirectory, const Q
}
// Retrieve head revision
-QString GitClient::synchronousTopRevision(const FilePath &workingDirectory, QDateTime *dateTime)
+Utils::Tasking::Process GitClient::topRevision(
+ const FilePath &workingDirectory,
+ const std::function<void(const QString &, const QDateTime &)> &callback)
{
- const QStringList arguments = {"show", "-s", "--pretty=format:%H:%ct", HEAD};
- const CommandResult result = vcsSynchronousExec(workingDirectory, arguments, RunFlags::NoOutput);
- if (result.result() != ProcessResult::FinishedWithSuccess)
- return QString();
- const QStringList output = result.cleanedStdOut().trimmed().split(':');
- if (dateTime && output.size() > 1) {
- bool ok = false;
- const qint64 timeT = output.at(1).toLongLong(&ok);
- *dateTime = ok ? QDateTime::fromSecsSinceEpoch(timeT) : QDateTime();
- }
- return output.first();
+ using namespace Tasking;
+
+ const auto setupProcess = [=](QtcProcess &process) {
+ setupCommand(process, workingDirectory, {"show", "-s", "--pretty=format:%H:%ct", HEAD});
+ };
+ const auto onProcessDone = [=](const QtcProcess &process) {
+ const QStringList output = process.cleanedStdOut().trimmed().split(':');
+ QDateTime dateTime;
+ if (output.size() > 1) {
+ bool ok = false;
+ const qint64 timeT = output.at(1).toLongLong(&ok);
+ if (ok)
+ dateTime = QDateTime::fromSecsSinceEpoch(timeT);
+ }
+ callback(output.first(), dateTime);
+ };
+
+ return Process(setupProcess, onProcessDone);
}
bool GitClient::isRemoteCommit(const FilePath &workingDirectory, const QString &commit)
@@ -1752,13 +1761,6 @@ bool GitClient::isRemoteCommit(const FilePath &workingDirectory, const QString &
return !result.rawStdOut().isEmpty();
}
-bool GitClient::isFastForwardMerge(const FilePath &workingDirectory, const QString &branch)
-{
- const CommandResult result = vcsSynchronousExec(workingDirectory,
- {"merge-base", HEAD, branch}, RunFlags::NoOutput);
- return result.cleanedStdOut().trimmed() == synchronousTopRevision(workingDirectory);
-}
-
// Format an entry in a one-liner for selection list using git log.
QString GitClient::synchronousShortDescription(const FilePath &workingDirectory, const QString &revision,
const QString &format) const
diff --git a/src/plugins/git/gitclient.h b/src/plugins/git/gitclient.h
index 2dfd6ddaaa1..5709a53b862 100644
--- a/src/plugins/git/gitclient.h
+++ b/src/plugins/git/gitclient.h
@@ -13,6 +13,7 @@
#include <utils/fileutils.h>
#include <utils/futuresynchronizer.h>
+#include <utils/qtcprocess.h>
#include <QObject>
#include <QString>
@@ -241,9 +242,10 @@ public:
QString synchronousTopic(const Utils::FilePath &workingDirectory) const;
bool synchronousRevParseCmd(const Utils::FilePath &workingDirectory, const QString &ref,
QString *output, QString *errorMessage = nullptr) const;
- QString synchronousTopRevision(const Utils::FilePath &workingDirectory, QDateTime *dateTime = nullptr);
+ Utils::Tasking::Process topRevision(
+ const Utils::FilePath &workingDirectory,
+ const std::function<void(const QString &, const QDateTime &)> &callback);
bool isRemoteCommit(const Utils::FilePath &workingDirectory, const QString &commit);
- bool isFastForwardMerge(const Utils::FilePath &workingDirectory, const QString &branch);
void fetch(const Utils::FilePath &workingDirectory, const QString &remote);
void pull(const Utils::FilePath &workingDirectory, bool rebase);
diff --git a/src/plugins/git/gitgrep.cpp b/src/plugins/git/gitgrep.cpp
index 6baa635080d..9d4aeec6980 100644
--- a/src/plugins/git/gitgrep.cpp
+++ b/src/plugins/git/gitgrep.cpp
@@ -13,6 +13,7 @@
#include <vcsbase/vcsbaseconstants.h>
#include <utils/algorithm.h>
+#include <utils/asynctask.h>
#include <utils/environment.h>
#include <utils/fancylineedit.h>
#include <utils/filesearch.h>
@@ -45,7 +46,7 @@ public:
class GitGrepRunner
{
- using FutureInterfaceType = QFutureInterface<FileSearchResultList>;
+ using PromiseType = QPromise<FileSearchResultList>;
public:
GitGrepRunner(const TextEditor::FileFindParameters &parameters)
@@ -116,7 +117,7 @@ public:
}
}
- void read(FutureInterfaceType &fi, const QString &text)
+ void read(PromiseType &fi, const QString &text)
{
FileSearchResultList resultList;
QString t = text;
@@ -124,10 +125,10 @@ public:
while (!stream.atEnd() && !fi.isCanceled())
processLine(stream.readLine(), &resultList);
if (!resultList.isEmpty() && !fi.isCanceled())
- fi.reportResult(resultList);
+ fi.addResult(resultList);
}
- void operator()(FutureInterfaceType &fi)
+ void operator()(PromiseType &promise)
{
QStringList arguments = {
"-c", "color.grep.match=bold red",
@@ -165,7 +166,7 @@ public:
process.setEnvironment(m_environment);
process.setCommand({m_vcsBinary, arguments});
process.setWorkingDirectory(m_directory);
- process.setStdOutCallback([this, &fi](const QString &text) { read(fi, text); });
+ process.setStdOutCallback([this, &promise](const QString &text) { read(promise, text); });
process.start();
process.waitForFinished();
@@ -173,7 +174,7 @@ public:
case ProcessResult::TerminatedAbnormally:
case ProcessResult::StartFailed:
case ProcessResult::Hang:
- fi.reportCanceled();
+ promise.future().cancel();
break;
case ProcessResult::FinishedWithSuccess:
case ProcessResult::FinishedWithError:
@@ -275,7 +276,7 @@ void GitGrep::writeSettings(QSettings *settings) const
QFuture<FileSearchResultList> GitGrep::executeSearch(const TextEditor::FileFindParameters &parameters,
TextEditor::BaseFileFind * /*baseFileFind*/)
{
- return Utils::runAsync(GitGrepRunner(parameters));
+ return Utils::asyncRun(GitGrepRunner(parameters));
}
IEditor *GitGrep::openEditor(const SearchResultItem &item,
diff --git a/src/plugins/git/gitsubmiteditor.cpp b/src/plugins/git/gitsubmiteditor.cpp
index 23755d98751..8cc483cbf74 100644
--- a/src/plugins/git/gitsubmiteditor.cpp
+++ b/src/plugins/git/gitsubmiteditor.cpp
@@ -11,8 +11,8 @@
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/iversioncontrol.h>
#include <coreplugin/progressmanager/progressmanager.h>
+#include <utils/asynctask.h>
#include <utils/qtcassert.h>
-#include <utils/runextensions.h>
#include <vcsbase/submitfilemodel.h>
#include <vcsbase/vcsoutputwindow.h>
@@ -204,7 +204,7 @@ void GitSubmitEditor::updateFileModel()
return;
w->setUpdateInProgress(true);
// TODO: Check if fetch works OK from separate thread, refactor otherwise
- m_fetchWatcher.setFuture(Utils::runAsync(&CommitDataFetchResult::fetch,
+ m_fetchWatcher.setFuture(Utils::asyncRun(&CommitDataFetchResult::fetch,
m_commitType, m_workingDirectory));
Core::ProgressManager::addTask(m_fetchWatcher.future(), Tr::tr("Refreshing Commit Data"),
TASK_UPDATE_COMMIT);
diff --git a/src/plugins/gitlab/gitlabdialog.cpp b/src/plugins/gitlab/gitlabdialog.cpp
index 4a10888c944..9375c057f57 100644
--- a/src/plugins/gitlab/gitlabdialog.cpp
+++ b/src/plugins/gitlab/gitlabdialog.cpp
@@ -9,7 +9,7 @@
#include "gitlabprojectsettings.h"
#include "gitlabtr.h"
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <texteditor/fontsettings.h>
#include <texteditor/texteditorsettings.h>
@@ -188,7 +188,7 @@ void GitLabDialog::requestMainViewUpdate()
bool linked = false;
m_currentServerId = Id();
- if (auto project = ProjectExplorer::SessionManager::startupProject()) {
+ if (auto project = ProjectExplorer::ProjectManager::startupProject()) {
GitLabProjectSettings *projSettings = GitLabPlugin::projectSettings(project);
if (projSettings->isLinked()) {
m_currentServerId = projSettings->currentServer();
diff --git a/src/plugins/gitlab/gitlabplugin.cpp b/src/plugins/gitlab/gitlabplugin.cpp
index 2059ae4d84b..9427aa1d44b 100644
--- a/src/plugins/gitlab/gitlabplugin.cpp
+++ b/src/plugins/gitlab/gitlabplugin.cpp
@@ -14,11 +14,15 @@
#include <coreplugin/actionmanager/actioncontainer.h>
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/icore.h>
+
#include <git/gitplugin.h>
+
#include <projectexplorer/project.h>
#include <projectexplorer/projectpanelfactory.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
+
#include <utils/qtcassert.h>
+
#include <vcsbase/vcsoutputwindow.h>
#include <QAction>
@@ -89,8 +93,8 @@ void GitLabPlugin::initialize()
if (dd->dialog)
dd->dialog->updateRemotes();
});
- connect(ProjectExplorer::SessionManager::instance(),
- &ProjectExplorer::SessionManager::startupProjectChanged,
+ connect(ProjectExplorer::ProjectManager::instance(),
+ &ProjectExplorer::ProjectManager::startupProjectChanged,
this, &GitLabPlugin::onStartupProjectChanged);
}
@@ -121,7 +125,7 @@ void GitLabPlugin::onStartupProjectChanged()
{
QTC_ASSERT(dd, return);
disconnect(&dd->notificationTimer);
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
if (!project) {
dd->notificationTimer.stop();
return;
@@ -147,7 +151,7 @@ void GitLabPluginPrivate::setupNotificationTimer()
void GitLabPluginPrivate::fetchEvents()
{
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
QTC_ASSERT(project, return);
if (runningQuery)
@@ -218,7 +222,7 @@ void GitLabPluginPrivate::handleEvents(const Events &events, const QDateTime &ti
{
runningQuery = false;
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
QTC_ASSERT(project, return);
GitLabProjectSettings *projSettings = GitLabPlugin::projectSettings(project);
@@ -311,7 +315,7 @@ void GitLabPlugin::linkedStateChanged(bool enabled)
{
QTC_ASSERT(dd, return);
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
if (project) {
const GitLabProjectSettings *pSettings = projectSettings(project);
dd->serverId = pSettings->currentServer();
diff --git a/src/plugins/haskell/CMakeLists.txt b/src/plugins/haskell/CMakeLists.txt
index 8f6cc4f1d85..dcc4bdb4a81 100644
--- a/src/plugins/haskell/CMakeLists.txt
+++ b/src/plugins/haskell/CMakeLists.txt
@@ -1,7 +1,6 @@
add_qtc_plugin(Haskell
PLUGIN_DEPENDS
QtCreator::Core QtCreator::TextEditor QtCreator::ProjectExplorer
- DEPENDS Qt5::Widgets
SOURCES
haskell.qrc
haskell_global.h
diff --git a/src/plugins/haskell/haskell.qrc b/src/plugins/haskell/haskell.qrc
index 654f45818c3..3ddf22f76d0 100644
--- a/src/plugins/haskell/haskell.qrc
+++ b/src/plugins/haskell/haskell.qrc
@@ -1,5 +1,6 @@
<RCC>
<qresource prefix="/haskell">
- <file>images/category_haskell.png</file>
+ <file>images/settingscategory_haskell.png</file>
+ <file>images/[email protected]</file>
</qresource>
</RCC>
diff --git a/src/plugins/haskell/images/category_haskell.png b/src/plugins/haskell/images/category_haskell.png
deleted file mode 100644
index 947f948bd91..00000000000
--- a/src/plugins/haskell/images/category_haskell.png
+++ /dev/null
Binary files differ
diff --git a/src/plugins/haskell/images/settingscategory_haskell.png b/src/plugins/haskell/images/settingscategory_haskell.png
new file mode 100644
index 00000000000..5bd3f69fa8c
--- /dev/null
+++ b/src/plugins/haskell/images/settingscategory_haskell.png
Binary files differ
diff --git a/src/plugins/haskell/images/[email protected] b/src/plugins/haskell/images/[email protected]
new file mode 100644
index 00000000000..1eb20959d5a
--- /dev/null
+++ b/src/plugins/haskell/images/[email protected]
Binary files differ
diff --git a/src/plugins/haskell/optionspage.cpp b/src/plugins/haskell/optionspage.cpp
index e64a6012345..ac33b757eb9 100644
--- a/src/plugins/haskell/optionspage.cpp
+++ b/src/plugins/haskell/optionspage.cpp
@@ -22,7 +22,7 @@ OptionsPage::OptionsPage()
setDisplayName(Tr::tr("General"));
setCategory("J.Z.Haskell");
setDisplayCategory(Tr::tr("Haskell"));
- setCategoryIcon(Utils::Icon(":/haskell/images/category_haskell.png"));
+ setCategoryIconPath(":/haskell/images/settingscategory_haskell.png");
}
QWidget *OptionsPage::widget()
diff --git a/src/plugins/help/CMakeLists.txt b/src/plugins/help/CMakeLists.txt
index 76a680c7595..d329b2a10aa 100644
--- a/src/plugins/help/CMakeLists.txt
+++ b/src/plugins/help/CMakeLists.txt
@@ -12,7 +12,6 @@ add_qtc_plugin(Help
helpfindsupport.cpp helpfindsupport.h
helpindexfilter.cpp helpindexfilter.h
helpmanager.cpp helpmanager.h
- helpmode.cpp helpmode.h
helpplugin.cpp helpplugin.h
helpviewer.cpp helpviewer.h
helpwidget.cpp helpwidget.h
@@ -46,7 +45,7 @@ extend_qtc_plugin(Help
)
option(BUILD_HELPVIEWERBACKEND_QTWEBENGINE "Build QtWebEngine based help viewer backend." YES)
-find_package(Qt5 COMPONENTS WebEngineWidgets QUIET)
+find_package(Qt6 COMPONENTS WebEngineWidgets QUIET)
extend_qtc_plugin(Help
CONDITION BUILD_HELPVIEWERBACKEND_QTWEBENGINE AND TARGET Qt::WebEngineWidgets
FEATURE_INFO "QtWebEngine help viewer"
diff --git a/src/plugins/help/help.qbs b/src/plugins/help/help.qbs
index 25d85bdc37e..d96cfa154e7 100644
--- a/src/plugins/help/help.qbs
+++ b/src/plugins/help/help.qbs
@@ -43,7 +43,6 @@ Project {
"helpfindsupport.cpp", "helpfindsupport.h",
"helpindexfilter.cpp", "helpindexfilter.h",
"helpmanager.cpp", "helpmanager.h",
- "helpmode.cpp", "helpmode.h",
"helpplugin.cpp", "helpplugin.h",
"helpviewer.cpp", "helpviewer.h",
"helpwidget.cpp", "helpwidget.h",
diff --git a/src/plugins/help/helpindexfilter.cpp b/src/plugins/help/helpindexfilter.cpp
index fe1c34a48e0..63578ef944d 100644
--- a/src/plugins/help/helpindexfilter.cpp
+++ b/src/plugins/help/helpindexfilter.cpp
@@ -11,11 +11,11 @@
#include <coreplugin/helpmanager.h>
#include <extensionsystem/pluginmanager.h>
#include <utils/utilsicons.h>
+#include <utils/tasktree.h>
#include <QHelpEngine>
#include <QHelpFilterEngine>
#include <QHelpLink>
-#include <QIcon>
using namespace Core;
using namespace Help;
@@ -27,6 +27,7 @@ HelpIndexFilter::HelpIndexFilter()
setDisplayName(Tr::tr("Help Index"));
setDefaultIncludedByDefault(false);
setDefaultShortcutString("?");
+ setRefreshRecipe(Utils::Tasking::Sync([this] { invalidateCache(); return true; }));
m_icon = Utils::Icons::BOOKMARK.icon();
connect(Core::HelpManager::Signals::instance(), &Core::HelpManager::Signals::setupFinished,
@@ -39,11 +40,25 @@ HelpIndexFilter::HelpIndexFilter()
this, &HelpIndexFilter::invalidateCache);
}
-HelpIndexFilter::~HelpIndexFilter() = default;
+void HelpIndexFilter::prepareSearch(const QString &entry)
+{
+ Q_UNUSED(entry)
+ if (!m_needsUpdate)
+ return;
+
+ m_needsUpdate = false;
+ LocalHelpManager::setupGuiHelpEngine();
+ m_allIndicesCache = LocalHelpManager::filterEngine()->indices({});
+ m_lastIndicesCache.clear();
+ m_lastEntry.clear();
+}
-bool HelpIndexFilter::updateCache(QFutureInterface<LocatorFilterEntry> &future,
- const QStringList &cache, const QString &entry)
+QList<LocatorFilterEntry> HelpIndexFilter::matchesFor(QFutureInterface<LocatorFilterEntry> &future,
+ const QString &entry)
{
+ const QStringList cache = m_lastEntry.isEmpty() || !entry.contains(m_lastEntry)
+ ? m_allIndicesCache : m_lastIndicesCache;
+
const Qt::CaseSensitivity cs = caseSensitivity(entry);
QStringList bestKeywords;
QStringList worseKeywords;
@@ -51,38 +66,15 @@ bool HelpIndexFilter::updateCache(QFutureInterface<LocatorFilterEntry> &future,
worseKeywords.reserve(cache.size());
for (const QString &keyword : cache) {
if (future.isCanceled())
- return false;
+ return {};
if (keyword.startsWith(entry, cs))
bestKeywords.append(keyword);
else if (keyword.contains(entry, cs))
worseKeywords.append(keyword);
}
- bestKeywords << worseKeywords;
- m_lastIndicesCache = bestKeywords;
+ m_lastIndicesCache = bestKeywords + worseKeywords;
m_lastEntry = entry;
- return true;
-}
-
-QList<LocatorFilterEntry> HelpIndexFilter::matchesFor(QFutureInterface<LocatorFilterEntry> &future, const QString &entry)
-{
- if (m_needsUpdate.exchange(false)) {
- QStringList indices;
- QMetaObject::invokeMethod(this, [this] { return allIndices(); },
- Qt::BlockingQueuedConnection, &indices);
- m_allIndicesCache = indices;
- // force updating the cache taking the m_allIndicesCache
- m_lastIndicesCache = QStringList();
- m_lastEntry = QString();
- }
-
- const QStringList cacheBase = m_lastEntry.isEmpty() || !entry.contains(m_lastEntry)
- ? m_allIndicesCache : m_lastIndicesCache;
-
- if (!updateCache(future, cacheBase, entry))
- return QList<LocatorFilterEntry>();
-
- const Qt::CaseSensitivity cs = caseSensitivity(entry);
QList<LocatorFilterEntry> entries;
for (const QString &keyword : std::as_const(m_lastIndicesCache)) {
const int index = keyword.indexOf(entry, 0, cs);
@@ -90,7 +82,6 @@ QList<LocatorFilterEntry> HelpIndexFilter::matchesFor(QFutureInterface<LocatorFi
filterEntry.highlightInfo = {index, int(entry.length())};
entries.append(filterEntry);
}
-
return entries;
}
@@ -105,18 +96,6 @@ void HelpIndexFilter::accept(const LocatorFilterEntry &selection,
emit linksActivated(links, key);
}
-void HelpIndexFilter::refresh(QFutureInterface<void> &future)
-{
- Q_UNUSED(future)
- invalidateCache();
-}
-
-QStringList HelpIndexFilter::allIndices() const
-{
- LocalHelpManager::setupGuiHelpEngine();
- return LocalHelpManager::filterEngine()->indices(QString());
-}
-
void HelpIndexFilter::invalidateCache()
{
m_needsUpdate = true;
diff --git a/src/plugins/help/helpindexfilter.h b/src/plugins/help/helpindexfilter.h
index 1dfc6f59952..f836941d483 100644
--- a/src/plugins/help/helpindexfilter.h
+++ b/src/plugins/help/helpindexfilter.h
@@ -7,11 +7,8 @@
#include <QIcon>
#include <QMultiMap>
-#include <QSet>
#include <QUrl>
-#include <atomic>
-
namespace Help {
namespace Internal {
@@ -21,16 +18,12 @@ class HelpIndexFilter final : public Core::ILocatorFilter
public:
HelpIndexFilter();
- ~HelpIndexFilter() final;
- // ILocatorFilter
+ void prepareSearch(const QString &entry) override;
QList<Core::LocatorFilterEntry> matchesFor(QFutureInterface<Core::LocatorFilterEntry> &future,
const QString &entry) override;
void accept(const Core::LocatorFilterEntry &selection,
QString *newText, int *selectionStart, int *selectionLength) const override;
- void refresh(QFutureInterface<void> &future) override;
-
- QStringList allIndices() const;
signals:
void linksActivated(const QMultiMap<QString, QUrl> &links, const QString &key) const;
@@ -38,13 +31,10 @@ signals:
private:
void invalidateCache();
- bool updateCache(QFutureInterface<Core::LocatorFilterEntry> &future,
- const QStringList &cache, const QString &entry);
-
QStringList m_allIndicesCache;
QStringList m_lastIndicesCache;
QString m_lastEntry;
- std::atomic_bool m_needsUpdate = true;
+ bool m_needsUpdate = true;
QIcon m_icon;
};
diff --git a/src/plugins/help/helpmanager.cpp b/src/plugins/help/helpmanager.cpp
index c3e0bd31512..69fe0dabf9e 100644
--- a/src/plugins/help/helpmanager.cpp
+++ b/src/plugins/help/helpmanager.cpp
@@ -7,7 +7,9 @@
#include <coreplugin/icore.h>
#include <coreplugin/progressmanager/progressmanager.h>
+
#include <utils/algorithm.h>
+#include <utils/asynctask.h>
#include <utils/filesystemwatcher.h>
#include <utils/qtcassert.h>
#include <utils/runextensions.h>
@@ -17,6 +19,7 @@
#include <QDesktopServices>
#include <QDir>
#include <QFileInfo>
+#include <QPromise>
#include <QStringList>
#include <QUrl>
@@ -99,7 +102,7 @@ void HelpManager::registerDocumentation(const QStringList &files)
return;
}
- QFuture<bool> future = Utils::runAsync(&HelpManager::registerDocumentationNow, files);
+ QFuture<bool> future = Utils::asyncRun(&HelpManager::registerDocumentationNow, files);
Utils::onResultReady(future, this, [](bool docsChanged){
if (docsChanged) {
d->m_helpEngine->setupData();
@@ -122,13 +125,12 @@ void HelpManager::unregisterDocumentation(const QStringList &fileNames)
unregisterNamespaces(getNamespaces(fileNames));
}
-void HelpManager::registerDocumentationNow(QFutureInterface<bool> &futureInterface,
- const QStringList &files)
+void HelpManager::registerDocumentationNow(QPromise<bool> &promise, const QStringList &files)
{
QMutexLocker locker(&d->m_helpengineMutex);
- futureInterface.setProgressRange(0, files.count());
- futureInterface.setProgressValue(0);
+ promise.setProgressRange(0, files.count());
+ promise.setProgressValue(0);
QHelpEngineCore helpEngine(collectionFilePath());
helpEngine.setReadOnly(false);
@@ -136,9 +138,9 @@ void HelpManager::registerDocumentationNow(QFutureInterface<bool> &futureInterfa
bool docsChanged = false;
QStringList nameSpaces = helpEngine.registeredDocumentations();
for (const QString &file : files) {
- if (futureInterface.isCanceled())
+ if (promise.isCanceled())
break;
- futureInterface.setProgressValue(futureInterface.progressValue() + 1);
+ promise.setProgressValue(promise.future().progressValue() + 1);
const QString &nameSpace = QHelpEngineCore::namespaceName(file);
if (nameSpace.isEmpty())
continue;
@@ -152,7 +154,7 @@ void HelpManager::registerDocumentationNow(QFutureInterface<bool> &futureInterfa
}
}
}
- futureInterface.reportResult(docsChanged);
+ promise.addResult(docsChanged);
}
void HelpManager::unregisterNamespaces(const QStringList &nameSpaces)
diff --git a/src/plugins/help/helpmanager.h b/src/plugins/help/helpmanager.h
index d868e96256e..6ab3e72cd97 100644
--- a/src/plugins/help/helpmanager.h
+++ b/src/plugins/help/helpmanager.h
@@ -5,12 +5,16 @@
#include <coreplugin/helpmanager_implementation.h>
-QT_FORWARD_DECLARE_CLASS(QUrl)
-
#include <QFutureInterface>
#include <QHelpEngineCore>
#include <QVariant>
+QT_BEGIN_NAMESPACE
+template <typename T>
+class QPromise;
+class QUrl;
+QT_END_NAMESPACE
+
namespace Help {
namespace Internal {
@@ -55,10 +59,9 @@ public:
const QUrl &url,
Core::HelpManager::HelpViewerLocation location = Core::HelpManager::HelpModeAlways) override;
-
static void setupHelpManager();
- static void registerDocumentationNow(QFutureInterface<bool> &futureInterface,
- const QStringList &fileNames);
+ static void registerDocumentationNow(QPromise<bool> &promise, const QStringList &fileNames);
+
signals:
void collectionFileChanged();
void helpRequested(const QUrl &url, Core::HelpManager::HelpViewerLocation location);
diff --git a/src/plugins/help/helpmode.cpp b/src/plugins/help/helpmode.cpp
deleted file mode 100644
index 2e2ee468939..00000000000
--- a/src/plugins/help/helpmode.cpp
+++ /dev/null
@@ -1,24 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#include "helpmode.h"
-#include "helpconstants.h"
-#include "helpicons.h"
-#include "helptr.h"
-
-#include <QCoreApplication>
-
-using namespace Help;
-using namespace Help::Internal;
-
-HelpMode::HelpMode(QObject *parent)
- : Core::IMode(parent)
-{
- setObjectName("HelpMode");
- setContext(Core::Context(Constants::C_MODE_HELP));
- setIcon(Utils::Icon::modeIcon(Icons::MODE_HELP_CLASSIC,
- Icons::MODE_HELP_FLAT, Icons::MODE_HELP_FLAT_ACTIVE));
- setDisplayName(Tr::tr("Help"));
- setPriority(Constants::P_MODE_HELP);
- setId(Constants::ID_MODE_HELP);
-}
diff --git a/src/plugins/help/helpmode.h b/src/plugins/help/helpmode.h
deleted file mode 100644
index fbe69f08bca..00000000000
--- a/src/plugins/help/helpmode.h
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#pragma once
-
-#include <coreplugin/imode.h>
-
-#include <QString>
-#include <QIcon>
-
-namespace Help {
-namespace Internal {
-
-class HelpMode : public Core::IMode
-{
-public:
- explicit HelpMode(QObject *parent = nullptr);
-};
-
-} // namespace Internal
-} // namespace Help
diff --git a/src/plugins/help/helpplugin.cpp b/src/plugins/help/helpplugin.cpp
index 56126372476..de055889edb 100644
--- a/src/plugins/help/helpplugin.cpp
+++ b/src/plugins/help/helpplugin.cpp
@@ -4,7 +4,6 @@
#include "helpplugin.h"
#include "bookmarkmanager.h"
-#include "contentwindow.h"
#include "docsettingspage.h"
#include "filtersettingspage.h"
#include "generalsettingspage.h"
@@ -13,11 +12,9 @@
#include "helpicons.h"
#include "helpindexfilter.h"
#include "helpmanager.h"
-#include "helpmode.h"
#include "helptr.h"
#include "helpviewer.h"
#include "helpwidget.h"
-#include "indexwindow.h"
#include "localhelpmanager.h"
#include "openpagesmanager.h"
#include "searchtaskhandler.h"
@@ -35,6 +32,7 @@
#include <coreplugin/findplaceholder.h>
#include <coreplugin/helpitem.h>
#include <coreplugin/icore.h>
+#include <coreplugin/imode.h>
#include <coreplugin/minisplitter.h>
#include <coreplugin/modemanager.h>
#include <coreplugin/rightpane.h>
@@ -52,38 +50,47 @@
#include <utils/theme/theme.h>
#include <utils/tooltip/tooltip.h>
+#include <QAction>
#include <QApplication>
+#include <QComboBox>
+#include <QDesktopServices>
#include <QDialog>
#include <QDialogButtonBox>
-#include <QDir>
-#include <QFileInfo>
+#include <QHelpEngine>
#include <QLabel>
#include <QLibraryInfo>
+#include <QMenu>
#include <QPlainTextEdit>
-#include <QTimer>
-#include <QTranslator>
-#include <qplugin.h>
#include <QRegularExpression>
-
-#include <QAction>
-#include <QComboBox>
-#include <QDesktopServices>
-#include <QMenu>
-#include <QStackedLayout>
#include <QSplitter>
-
-#include <QHelpEngine>
+#include <QStackedLayout>
+#include <QTimer>
+#include <QTranslator>
#include <functional>
-static const char kExternalWindowStateKey[] = "Help/ExternalWindowState";
-static const char kToolTipHelpContext[] = "Help.ToolTip";
-
using namespace Core;
using namespace Utils;
-namespace Help {
-namespace Internal {
+namespace Help::Internal {
+
+const char kExternalWindowStateKey[] = "Help/ExternalWindowState";
+const char kToolTipHelpContext[] = "Help.ToolTip";
+
+class HelpMode : public IMode
+{
+public:
+ HelpMode()
+ {
+ setObjectName("HelpMode");
+ setContext(Core::Context(Constants::C_MODE_HELP));
+ setIcon(Icon::modeIcon(Icons::MODE_HELP_CLASSIC,
+ Icons::MODE_HELP_FLAT, Icons::MODE_HELP_FLAT_ACTIVE));
+ setDisplayName(Tr::tr("Help"));
+ setPriority(Constants::P_MODE_HELP);
+ setId(Constants::ID_MODE_HELP);
+ }
+};
class HelpPluginPrivate : public QObject
{
@@ -641,5 +648,4 @@ void HelpPluginPrivate::doSetupIfNeeded()
}
}
-} // Internal
-} // Help
+} // Help::Internal
diff --git a/src/plugins/imageviewer/CMakeLists.txt b/src/plugins/imageviewer/CMakeLists.txt
index 5c6f0ca98b6..5c027bbd38c 100644
--- a/src/plugins/imageviewer/CMakeLists.txt
+++ b/src/plugins/imageviewer/CMakeLists.txt
@@ -1,4 +1,4 @@
-find_package(Qt5 COMPONENTS SvgWidgets QUIET)
+find_package(Qt6 COMPONENTS SvgWidgets QUIET)
if (TARGET Qt::SvgWidgets)
set(SVG_WIDGETS Qt::SvgWidgets)
endif()
diff --git a/src/plugins/imageviewer/imageview.cpp b/src/plugins/imageviewer/imageview.cpp
index 34b74d49086..9a2af284f49 100644
--- a/src/plugins/imageviewer/imageview.cpp
+++ b/src/plugins/imageviewer/imageview.cpp
@@ -8,11 +8,11 @@
#include "imageviewerfile.h"
#include "imageviewertr.h"
#include "multiexportdialog.h"
-#include "utils/mimeutils.h"
#include <coreplugin/messagemanager.h>
#include <utils/fileutils.h>
+#include <utils/mimeutils.h>
#include <utils/qtcassert.h>
#include <utils/qtcsettings.h>
diff --git a/src/plugins/ios/iosbuildstep.cpp b/src/plugins/ios/iosbuildstep.cpp
index eb6664c155c..e4771d97e84 100644
--- a/src/plugins/ios/iosbuildstep.cpp
+++ b/src/plugins/ios/iosbuildstep.cpp
@@ -101,7 +101,8 @@ QWidget *IosBuildStep::createConfigWidget()
updateDetails();
connect(buildArgumentsTextEdit, &QPlainTextEdit::textChanged, this, [=] {
- setBaseArguments(ProcessArgs::splitArgs(buildArgumentsTextEdit->toPlainText()));
+ setBaseArguments(ProcessArgs::splitArgs(buildArgumentsTextEdit->toPlainText(),
+ HostOsInfo::hostOs()));
resetDefaultsButton->setEnabled(!m_useDefaultArguments);
updateDetails();
});
@@ -113,7 +114,8 @@ QWidget *IosBuildStep::createConfigWidget()
});
connect(extraArgumentsLineEdit, &QLineEdit::editingFinished, this, [=] {
- setExtraArguments(ProcessArgs::splitArgs(extraArgumentsLineEdit->text()));
+ setExtraArguments(ProcessArgs::splitArgs(extraArgumentsLineEdit->text(),
+ HostOsInfo::hostOs()));
});
connect(ProjectExplorerPlugin::instance(), &ProjectExplorerPlugin::settingsChanged,
diff --git a/src/plugins/ios/iosdsymbuildstep.cpp b/src/plugins/ios/iosdsymbuildstep.cpp
index 54481ded9b7..33ff0e7e202 100644
--- a/src/plugins/ios/iosdsymbuildstep.cpp
+++ b/src/plugins/ios/iosdsymbuildstep.cpp
@@ -242,7 +242,8 @@ QWidget *IosDsymBuildStep::createConfigWidget()
connect(argumentsTextEdit, &QPlainTextEdit::textChanged, this,
[this, argumentsTextEdit, resetDefaultsButton, updateDetails] {
- setArguments(Utils::ProcessArgs::splitArgs(argumentsTextEdit->toPlainText()));
+ setArguments(ProcessArgs::splitArgs(argumentsTextEdit->toPlainText(),
+ HostOsInfo::hostOs()));
resetDefaultsButton->setEnabled(!isDefault());
updateDetails();
});
diff --git a/src/plugins/ios/iostoolhandler.cpp b/src/plugins/ios/iostoolhandler.cpp
index 0331ba2ccab..b8a3c592ef4 100644
--- a/src/plugins/ios/iostoolhandler.cpp
+++ b/src/plugins/ios/iostoolhandler.cpp
@@ -12,6 +12,7 @@
#include <debugger/debuggerconstants.h>
+#include <utils/asynctask.h>
#include <utils/filepath.h>
#include <utils/futuresynchronizer.h>
#include <utils/qtcassert.h>
@@ -63,22 +64,22 @@ class LogTailFiles : public QObject
Q_OBJECT
public:
- void exec(QFutureInterface<void> &fi, std::shared_ptr<QTemporaryFile> stdoutFile,
- std::shared_ptr<QTemporaryFile> stderrFile)
+ void exec(QPromise<void> &promise, std::shared_ptr<QTemporaryFile> stdoutFile,
+ std::shared_ptr<QTemporaryFile> stderrFile)
{
- if (fi.isCanceled())
+ if (promise.isCanceled())
return;
// The future is canceled when app on simulator is stoped.
QEventLoop loop;
QFutureWatcher<void> watcher;
connect(&watcher, &QFutureWatcher<void>::canceled, &loop, [&] { loop.quit(); });
- watcher.setFuture(fi.future());
+ watcher.setFuture(promise.future());
// Process to print the console output while app is running.
auto logProcess = [&](QProcess *tailProcess, std::shared_ptr<QTemporaryFile> file) {
- QObject::connect(tailProcess, &QProcess::readyReadStandardOutput, &loop, [=] {
- if (!fi.isCanceled())
+ QObject::connect(tailProcess, &QProcess::readyReadStandardOutput, &loop, [&, tailProcess] {
+ if (!promise.isCanceled())
emit logMessage(QString::fromLocal8Bit(tailProcess->readAll()));
});
tailProcess->start(QStringLiteral("tail"), {"-f", file->fileName()});
@@ -931,17 +932,17 @@ void IosSimulatorToolHandlerPrivate::launchAppOnSimulator(const QStringList &ext
"Install Xcode 8 or later.").arg(bundleId));
}
- auto monitorPid = [this](QFutureInterface<void> &fi, qint64 pid) {
+ auto monitorPid = [this](QPromise<void> &promise, qint64 pid) {
#ifdef Q_OS_UNIX
do {
// Poll every 1 sec to check whether the app is running.
QThread::msleep(1000);
- } while (!fi.isCanceled() && kill(pid, 0) == 0);
+ } while (!promise.isCanceled() && kill(pid, 0) == 0);
#else
Q_UNUSED(pid)
#endif
// Future is cancelled if the app is stopped from the qt creator.
- if (!fi.isCanceled())
+ if (!promise.isCanceled())
stop(0);
};
@@ -953,9 +954,9 @@ void IosSimulatorToolHandlerPrivate::launchAppOnSimulator(const QStringList &ext
gotInferiorPid(m_bundlePath, m_deviceId, response.pID);
didStartApp(m_bundlePath, m_deviceId, Ios::IosToolHandler::Success);
// Start monitoring app's life signs.
- futureSynchronizer.addFuture(Utils::runAsync(monitorPid, response.pID));
+ futureSynchronizer.addFuture(Utils::asyncRun(monitorPid, response.pID));
if (captureConsole)
- futureSynchronizer.addFuture(Utils::runAsync(&LogTailFiles::exec, &outputLogger,
+ futureSynchronizer.addFuture(Utils::asyncRun(&LogTailFiles::exec, &outputLogger,
stdoutFile, stderrFile));
} else {
m_pid = -1;
diff --git a/src/plugins/ios/simulatorcontrol.cpp b/src/plugins/ios/simulatorcontrol.cpp
index 6e090b61886..6a19513fd2a 100644
--- a/src/plugins/ios/simulatorcontrol.cpp
+++ b/src/plugins/ios/simulatorcontrol.cpp
@@ -5,9 +5,10 @@
#include "iosconfigurations.h"
#include <utils/algorithm.h>
-#include <utils/runextensions.h>
+#include <utils/asynctask.h>
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
+#include <utils/runextensions.h>
#ifdef Q_OS_MAC
#include <CoreFoundation/CoreFoundation.h>
@@ -162,30 +163,30 @@ static SimulatorInfo deviceInfo(const QString &simUdid);
static QString bundleIdentifier(const Utils::FilePath &bundlePath);
static QString bundleExecutable(const Utils::FilePath &bundlePath);
-static void startSimulator(QFutureInterface<SimulatorControl::ResponseData> &fi,
+static void startSimulator(QPromise<SimulatorControl::ResponseData> &promise,
const QString &simUdid);
-static void installApp(QFutureInterface<SimulatorControl::ResponseData> &fi,
+static void installApp(QPromise<SimulatorControl::ResponseData> &promise,
const QString &simUdid,
const Utils::FilePath &bundlePath);
-static void launchApp(QFutureInterface<SimulatorControl::ResponseData> &fi,
+static void launchApp(QPromise<SimulatorControl::ResponseData> &promise,
const QString &simUdid,
const QString &bundleIdentifier,
bool waitForDebugger,
const QStringList &extraArgs,
const QString &stdoutPath,
const QString &stderrPath);
-static void deleteSimulator(QFutureInterface<SimulatorControl::ResponseData> &fi,
+static void deleteSimulator(QPromise<SimulatorControl::ResponseData> &promise,
const QString &simUdid);
-static void resetSimulator(QFutureInterface<SimulatorControl::ResponseData> &fi,
+static void resetSimulator(QPromise<SimulatorControl::ResponseData> &promise,
const QString &simUdid);
-static void renameSimulator(QFutureInterface<SimulatorControl::ResponseData> &fi,
+static void renameSimulator(QPromise<SimulatorControl::ResponseData> &promise,
const QString &simUdid,
const QString &newName);
-static void createSimulator(QFutureInterface<SimulatorControl::ResponseData> &fi,
+static void createSimulator(QPromise<SimulatorControl::ResponseData> &promise,
const QString &name,
const DeviceTypeInfo &deviceType,
const RuntimeInfo &runtime);
-static void takeSceenshot(QFutureInterface<SimulatorControl::ResponseData> &fi,
+static void takeSceenshot(QPromise<SimulatorControl::ResponseData> &promise,
const QString &simUdid,
const QString &filePath);
@@ -234,9 +235,9 @@ static QList<SimulatorInfo> getAvailableSimulators()
return availableDevices;
}
-QFuture<QList<DeviceTypeInfo> > SimulatorControl::updateDeviceTypes()
+QFuture<QList<DeviceTypeInfo>> SimulatorControl::updateDeviceTypes()
{
- QFuture< QList<DeviceTypeInfo> > future = Utils::runAsync(getAvailableDeviceTypes);
+ QFuture<QList<DeviceTypeInfo>> future = Utils::asyncRun(getAvailableDeviceTypes);
Utils::onResultReady(future, [](const QList<DeviceTypeInfo> &deviceTypes) {
s_availableDeviceTypes = deviceTypes;
});
@@ -248,18 +249,18 @@ QList<RuntimeInfo> SimulatorControl::availableRuntimes()
return s_availableRuntimes;
}
-QFuture<QList<RuntimeInfo> > SimulatorControl::updateRuntimes()
+QFuture<QList<RuntimeInfo>> SimulatorControl::updateRuntimes()
{
- QFuture< QList<RuntimeInfo> > future = Utils::runAsync(getAvailableRuntimes);
+ QFuture<QList<RuntimeInfo>> future = Utils::asyncRun(getAvailableRuntimes);
Utils::onResultReady(future, [](const QList<RuntimeInfo> &runtimes) {
s_availableRuntimes = runtimes;
});
return future;
}
-QFuture< QList<SimulatorInfo> > SimulatorControl::updateAvailableSimulators()
+QFuture<QList<SimulatorInfo>> SimulatorControl::updateAvailableSimulators()
{
- QFuture< QList<SimulatorInfo> > future = Utils::runAsync(getAvailableSimulators);
+ QFuture<QList<SimulatorInfo>> future = Utils::asyncRun(getAvailableSimulators);
Utils::onResultReady(future,
[](const QList<SimulatorInfo> &devices) { s_availableDevices = devices; });
return future;
@@ -284,13 +285,13 @@ QString SimulatorControl::bundleExecutable(const Utils::FilePath &bundlePath)
QFuture<SimulatorControl::ResponseData> SimulatorControl::startSimulator(const QString &simUdid)
{
- return Utils::runAsync(Internal::startSimulator, simUdid);
+ return Utils::asyncRun(Internal::startSimulator, simUdid);
}
QFuture<SimulatorControl::ResponseData> SimulatorControl::installApp(
const QString &simUdid, const Utils::FilePath &bundlePath)
{
- return Utils::runAsync(Internal::installApp, simUdid, bundlePath);
+ return Utils::asyncRun(Internal::installApp, simUdid, bundlePath);
}
QFuture<SimulatorControl::ResponseData> SimulatorControl::launchApp(const QString &simUdid,
@@ -300,7 +301,7 @@ QFuture<SimulatorControl::ResponseData> SimulatorControl::launchApp(const QStrin
const QString &stdoutPath,
const QString &stderrPath)
{
- return Utils::runAsync(Internal::launchApp,
+ return Utils::asyncRun(Internal::launchApp,
simUdid,
bundleIdentifier,
waitForDebugger,
@@ -311,18 +312,18 @@ QFuture<SimulatorControl::ResponseData> SimulatorControl::launchApp(const QStrin
QFuture<SimulatorControl::ResponseData> SimulatorControl::deleteSimulator(const QString &simUdid)
{
- return Utils::runAsync(Internal::deleteSimulator, simUdid);
+ return Utils::asyncRun(Internal::deleteSimulator, simUdid);
}
QFuture<SimulatorControl::ResponseData> SimulatorControl::resetSimulator(const QString &simUdid)
{
- return Utils::runAsync(Internal::resetSimulator, simUdid);
+ return Utils::asyncRun(Internal::resetSimulator, simUdid);
}
QFuture<SimulatorControl::ResponseData> SimulatorControl::renameSimulator(const QString &simUdid,
const QString &newName)
{
- return Utils::runAsync(Internal::renameSimulator, simUdid, newName);
+ return Utils::asyncRun(Internal::renameSimulator, simUdid, newName);
}
QFuture<SimulatorControl::ResponseData>
@@ -330,13 +331,13 @@ SimulatorControl::createSimulator(const QString &name,
const DeviceTypeInfo &deviceType,
const RuntimeInfo &runtime)
{
- return Utils::runAsync(Internal::createSimulator, name, deviceType, runtime);
+ return Utils::asyncRun(Internal::createSimulator, name, deviceType, runtime);
}
QFuture<SimulatorControl::ResponseData> SimulatorControl::takeSceenshot(const QString &simUdid,
const QString &filePath)
{
- return Utils::runAsync(Internal::takeSceenshot, simUdid, filePath);
+ return Utils::asyncRun(Internal::takeSceenshot, simUdid, filePath);
}
// Static members
@@ -392,7 +393,7 @@ QString bundleExecutable(const Utils::FilePath &bundlePath)
return executable;
}
-void startSimulator(QFutureInterface<SimulatorControl::ResponseData> &fi, const QString &simUdid)
+void startSimulator(QPromise<SimulatorControl::ResponseData> &promise, const QString &simUdid)
{
SimulatorControl::ResponseData response(simUdid);
SimulatorInfo simInfo = deviceInfo(simUdid);
@@ -420,7 +421,7 @@ void startSimulator(QFutureInterface<SimulatorControl::ResponseData> &fi, const
if (simInfo.isShutdown()) {
if (launchSimulator(simUdid)) {
- if (fi.isCanceled())
+ if (promise.isCanceled())
return;
// At this point the sim device exists, available and was not running.
// So the simulator is started and we'll wait for it to reach to a state
@@ -429,13 +430,11 @@ void startSimulator(QFutureInterface<SimulatorControl::ResponseData> &fi, const
SimulatorInfo info;
do {
info = deviceInfo(simUdid);
- if (fi.isCanceled())
+ if (promise.isCanceled())
return;
- } while (!info.isBooted()
- && !checkForTimeout(start, simulatorStartTimeout));
- if (info.isBooted()) {
+ } while (!info.isBooted() && !checkForTimeout(start, simulatorStartTimeout));
+ if (info.isBooted())
response.success = true;
- }
} else {
qCDebug(simulatorLog) << "Error starting simulator.";
}
@@ -444,14 +443,12 @@ void startSimulator(QFutureInterface<SimulatorControl::ResponseData> &fi, const
<< simInfo;
}
- if (!fi.isCanceled()) {
- fi.reportResult(response);
- }
+ if (!promise.isCanceled())
+ promise.addResult(response);
}
-void installApp(QFutureInterface<SimulatorControl::ResponseData> &fi,
- const QString &simUdid,
- const Utils::FilePath &bundlePath)
+void installApp(QPromise<SimulatorControl::ResponseData> &promise,
+ const QString &simUdid, const Utils::FilePath &bundlePath)
{
QTC_CHECK(bundlePath.exists());
@@ -459,11 +456,11 @@ void installApp(QFutureInterface<SimulatorControl::ResponseData> &fi,
response.success = runSimCtlCommand({"install", simUdid, bundlePath.toString()},
nullptr,
&response.commandOutput);
- if (!fi.isCanceled())
- fi.reportResult(response);
+ if (!promise.isCanceled())
+ promise.addResult(response);
}
-void launchApp(QFutureInterface<SimulatorControl::ResponseData> &fi,
+void launchApp(QPromise<SimulatorControl::ResponseData> &promise,
const QString &simUdid,
const QString &bundleIdentifier,
bool waitForDebugger,
@@ -472,7 +469,7 @@ void launchApp(QFutureInterface<SimulatorControl::ResponseData> &fi,
const QString &stderrPath)
{
SimulatorControl::ResponseData response(simUdid);
- if (!bundleIdentifier.isEmpty() && !fi.isCanceled()) {
+ if (!bundleIdentifier.isEmpty() && !promise.isCanceled()) {
QStringList args({"launch", simUdid, bundleIdentifier});
// simctl usage documentation : Note: Log output is often directed to stderr, not stdout.
@@ -499,30 +496,29 @@ void launchApp(QFutureInterface<SimulatorControl::ResponseData> &fi,
}
}
- if (!fi.isCanceled()) {
- fi.reportResult(response);
- }
+ if (!promise.isCanceled())
+ promise.addResult(response);
}
-void deleteSimulator(QFutureInterface<SimulatorControl::ResponseData> &fi, const QString &simUdid)
+void deleteSimulator(QPromise<SimulatorControl::ResponseData> &promise, const QString &simUdid)
{
SimulatorControl::ResponseData response(simUdid);
response.success = runSimCtlCommand({"delete", simUdid}, nullptr, &response.commandOutput);
- if (!fi.isCanceled())
- fi.reportResult(response);
+ if (!promise.isCanceled())
+ promise.addResult(response);
}
-void resetSimulator(QFutureInterface<SimulatorControl::ResponseData> &fi, const QString &simUdid)
+void resetSimulator(QPromise<SimulatorControl::ResponseData> &promise, const QString &simUdid)
{
SimulatorControl::ResponseData response(simUdid);
response.success = runSimCtlCommand({"erase", simUdid}, nullptr, &response.commandOutput);
- if (!fi.isCanceled())
- fi.reportResult(response);
+ if (!promise.isCanceled())
+ promise.addResult(response);
}
-void renameSimulator(QFutureInterface<SimulatorControl::ResponseData> &fi,
+void renameSimulator(QPromise<SimulatorControl::ResponseData> &promise,
const QString &simUdid,
const QString &newName)
{
@@ -530,12 +526,11 @@ void renameSimulator(QFutureInterface<SimulatorControl::ResponseData> &fi,
response.success = runSimCtlCommand({"rename", simUdid, newName},
nullptr,
&response.commandOutput);
-
- if (!fi.isCanceled())
- fi.reportResult(response);
+ if (!promise.isCanceled())
+ promise.addResult(response);
}
-void createSimulator(QFutureInterface<SimulatorControl::ResponseData> &fi,
+void createSimulator(QPromise<SimulatorControl::ResponseData> &promise,
const QString &name,
const DeviceTypeInfo &deviceType,
const RuntimeInfo &runtime)
@@ -550,11 +545,11 @@ void createSimulator(QFutureInterface<SimulatorControl::ResponseData> &fi,
response.simUdid = response.success ? stdOutput.trimmed() : QString();
}
- if (!fi.isCanceled())
- fi.reportResult(response);
+ if (!promise.isCanceled())
+ promise.addResult(response);
}
-void takeSceenshot(QFutureInterface<SimulatorControl::ResponseData> &fi,
+void takeSceenshot(QPromise<SimulatorControl::ResponseData> &promise,
const QString &simUdid,
const QString &filePath)
{
@@ -562,8 +557,8 @@ void takeSceenshot(QFutureInterface<SimulatorControl::ResponseData> &fi,
response.success = runSimCtlCommand({"io", simUdid, "screenshot", filePath},
nullptr,
&response.commandOutput);
- if (!fi.isCanceled())
- fi.reportResult(response);
+ if (!promise.isCanceled())
+ promise.addResult(response);
}
QDebug &operator<<(QDebug &stream, const SimulatorInfo &info)
diff --git a/src/plugins/ios/simulatorcontrol.h b/src/plugins/ios/simulatorcontrol.h
index 15fea7b27bd..90a0a6ac4b2 100644
--- a/src/plugins/ios/simulatorcontrol.h
+++ b/src/plugins/ios/simulatorcontrol.h
@@ -65,11 +65,11 @@ public:
public:
static QList<DeviceTypeInfo> availableDeviceTypes();
- static QFuture<QList<DeviceTypeInfo> > updateDeviceTypes();
+ static QFuture<QList<DeviceTypeInfo>> updateDeviceTypes();
static QList<RuntimeInfo> availableRuntimes();
- static QFuture<QList<RuntimeInfo> > updateRuntimes();
+ static QFuture<QList<RuntimeInfo>> updateRuntimes();
static QList<SimulatorInfo> availableSimulators();
- static QFuture<QList<SimulatorInfo> > updateAvailableSimulators();
+ static QFuture<QList<SimulatorInfo>> updateAvailableSimulators();
static bool isSimulatorRunning(const QString &simUdid);
static QString bundleIdentifier(const Utils::FilePath &bundlePath);
static QString bundleExecutable(const Utils::FilePath &bundlePath);
diff --git a/src/plugins/languageclient/CMakeLists.txt b/src/plugins/languageclient/CMakeLists.txt
index 8e1748a04a1..0677cdf867a 100644
--- a/src/plugins/languageclient/CMakeLists.txt
+++ b/src/plugins/languageclient/CMakeLists.txt
@@ -4,6 +4,7 @@ add_qtc_plugin(LanguageClient
SOURCES
callhierarchy.cpp callhierarchy.h
client.cpp client.h
+ clientrequesttask.cpp clientrequesttask.h
diagnosticmanager.cpp diagnosticmanager.h
documentsymbolcache.cpp documentsymbolcache.h
dynamiccapabilities.cpp dynamiccapabilities.h
diff --git a/src/plugins/languageclient/callhierarchy.cpp b/src/plugins/languageclient/callhierarchy.cpp
index 3e0c6589aa7..a05922cbe5e 100644
--- a/src/plugins/languageclient/callhierarchy.cpp
+++ b/src/plugins/languageclient/callhierarchy.cpp
@@ -23,8 +23,6 @@ using namespace LanguageServerProtocol;
namespace LanguageClient {
-const char CALL_HIERARCHY_FACTORY_ID[] = "LanguageClient.CallHierarchy";
-
namespace {
enum Direction { Incoming, Outgoing };
@@ -186,6 +184,9 @@ public:
layout()->setSpacing(0);
connect(m_view, &NavigationTreeView::activated, this, &CallHierarchy::onItemActivated);
+
+ connect(LanguageClientManager::instance(), &LanguageClientManager::openCallHierarchy,
+ this, &CallHierarchy::updateHierarchyAtCursorPosition);
}
void onItemActivated(const QModelIndex &index)
@@ -211,26 +212,14 @@ void CallHierarchy::updateHierarchyAtCursorPosition()
BaseTextEditor *editor = BaseTextEditor::currentTextEditor();
if (!editor)
return;
- Client *client = LanguageClientManager::clientForFilePath(editor->document()->filePath());
+
+ Core::IDocument *document = editor->document();
+
+ Client *client = LanguageClientManager::clientForFilePath(document->filePath());
if (!client)
return;
- const QString methodName = PrepareCallHierarchyRequest::methodName;
- std::optional<bool> registered = client->dynamicCapabilities().isRegistered(methodName);
- bool supported = registered.value_or(false);
- const Core::IDocument *document = editor->document();
- if (registered) {
- if (supported) {
- const QJsonValue &options = client->dynamicCapabilities().option(methodName);
- const TextDocumentRegistrationOptions docOptions(options);
- supported = docOptions.filterApplies(document->filePath(),
- Utils::mimeTypeForName(document->mimeType()));
- }
- } else {
- supported = client->capabilities().callHierarchyProvider().has_value();
- }
-
- if (!supported)
+ if (!CallHierarchyFactory::supportsCallHierarchy(client, document))
return;
TextDocumentPositionParams params;
@@ -273,7 +262,25 @@ CallHierarchyFactory::CallHierarchyFactory()
{
setDisplayName(Tr::tr("Call Hierarchy"));
setPriority(650);
- setId(CALL_HIERARCHY_FACTORY_ID);
+ setId(Constants::CALL_HIERARCHY_FACTORY_ID);
+}
+
+bool CallHierarchyFactory::supportsCallHierarchy(Client *client, const Core::IDocument *document)
+{
+ const QString methodName = PrepareCallHierarchyRequest::methodName;
+ std::optional<bool> registered = client->dynamicCapabilities().isRegistered(methodName);
+ bool supported = registered.value_or(false);
+ if (registered) {
+ if (supported) {
+ const QJsonValue &options = client->dynamicCapabilities().option(methodName);
+ const TextDocumentRegistrationOptions docOptions(options);
+ supported = docOptions.filterApplies(document->filePath(),
+ Utils::mimeTypeForName(document->mimeType()));
+ }
+ } else {
+ supported = client->capabilities().callHierarchyProvider().has_value();
+ }
+ return supported;
}
Core::NavigationView CallHierarchyFactory::createWidget()
@@ -284,6 +291,7 @@ Core::NavigationView CallHierarchyFactory::createWidget()
Icons::RELOAD_TOOLBAR.icon();
auto button = new QToolButton;
button->setIcon(Icons::RELOAD_TOOLBAR.icon());
+ button->setToolTip(Tr::tr("Reloads the call hierarchy for the symbol under cursor position."));
connect(button, &QToolButton::clicked, [h](){
h->updateHierarchyAtCursorPosition();
});
diff --git a/src/plugins/languageclient/callhierarchy.h b/src/plugins/languageclient/callhierarchy.h
index f707c4fbcbb..bbc15b09712 100644
--- a/src/plugins/languageclient/callhierarchy.h
+++ b/src/plugins/languageclient/callhierarchy.h
@@ -5,8 +5,12 @@
#pragma once
+namespace Core { class IDocument; }
+
namespace LanguageClient {
+class Client;
+
class CallHierarchyFactory : public Core::INavigationWidgetFactory
{
Q_OBJECT
@@ -14,6 +18,8 @@ class CallHierarchyFactory : public Core::INavigationWidgetFactory
public:
CallHierarchyFactory();
+ static bool supportsCallHierarchy(Client *client, const Core::IDocument *document);
+
Core::NavigationView createWidget() override;
};
diff --git a/src/plugins/languageclient/client.cpp b/src/plugins/languageclient/client.cpp
index 0efaacd3500..17248c7ca29 100644
--- a/src/plugins/languageclient/client.cpp
+++ b/src/plugins/languageclient/client.cpp
@@ -3,6 +3,7 @@
#include "client.h"
+#include "callhierarchy.h"
#include "diagnosticmanager.h"
#include "documentsymbolcache.h"
#include "languageclientcompletionassist.h"
@@ -11,6 +12,7 @@
#include "languageclienthoverhandler.h"
#include "languageclientinterface.h"
#include "languageclientmanager.h"
+#include "languageclientoutline.h"
#include "languageclientquickfix.h"
#include "languageclientsymbolsupport.h"
#include "languageclientutils.h"
@@ -38,7 +40,7 @@
#include <languageserverprotocol/workspace.h>
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <texteditor/codeassist/documentcontentcompletion.h>
#include <texteditor/codeassist/iassistprocessor.h>
@@ -155,7 +157,7 @@ public:
m_documentUpdateTimer.setInterval(500);
connect(&m_documentUpdateTimer, &QTimer::timeout, this,
[this] { sendPostponedDocumentUpdates(Schedule::Now); });
- connect(SessionManager::instance(), &SessionManager::projectRemoved,
+ connect(ProjectManager::instance(), &ProjectManager::projectRemoved,
q, &Client::projectClosed);
QTC_ASSERT(clientInterface, return);
@@ -325,7 +327,6 @@ public:
SemanticTokenSupport m_tokenSupport;
QString m_serverName;
QString m_serverVersion;
- LanguageServerProtocol::SymbolStringifier m_symbolStringifier;
Client::LogTarget m_logTarget = Client::LogTarget::Ui;
bool m_locatorsEnabled = true;
bool m_autoRequestCodeActions = true;
@@ -509,7 +510,7 @@ void Client::initialize()
params.setRootUri(hostPathToServerUri(d->m_project->projectDirectory()));
const QList<WorkSpaceFolder> workspaces
- = Utils::transform(SessionManager::projects(), [this](Project *pro) {
+ = Utils::transform(ProjectManager::projects(), [this](Project *pro) {
return WorkSpaceFolder(hostPathToServerUri(pro->projectDirectory()),
pro->displayName());
});
@@ -879,6 +880,8 @@ void Client::activateEditor(Core::IEditor *editor)
optionalActions |= TextEditor::TextEditorActionHandler::FindUsage;
if (symbolSupport().supportsRename(widget->textDocument()))
optionalActions |= TextEditor::TextEditorActionHandler::RenameSymbol;
+ if (CallHierarchyFactory::supportsCallHierarchy(this, textEditor->document()))
+ optionalActions |= TextEditor::TextEditorActionHandler::CallHierarchy;
widget->setOptionalActions(optionalActions);
}
}
@@ -1484,16 +1487,6 @@ void Client::setSemanticTokensHandler(const SemanticTokensHandler &handler)
d->m_tokenSupport.setTokensHandler(handler);
}
-void Client::setSymbolStringifier(const LanguageServerProtocol::SymbolStringifier &stringifier)
-{
- d->m_symbolStringifier = stringifier;
-}
-
-SymbolStringifier Client::symbolStringifier() const
-{
- return d->m_symbolStringifier;
-}
-
void Client::setSnippetsGroup(const QString &group)
{
if (const auto provider = qobject_cast<LanguageClientCompletionAssistProvider *>(
@@ -1679,10 +1672,15 @@ LanguageClientValue<MessageActionItem> ClientPrivate::showMessageBox(
}
QHash<QAbstractButton *, MessageActionItem> itemForButton;
if (const std::optional<QList<MessageActionItem>> actions = message.actions()) {
- for (const MessageActionItem &action : *actions)
- itemForButton.insert(box->addButton(action.title(), QMessageBox::InvalidRole), action);
+ auto button = box->addButton(QMessageBox::Close);
+ connect(button, &QPushButton::clicked, box, &QMessageBox::reject);
+ for (const MessageActionItem &action : *actions) {
+ connect(button, &QPushButton::clicked, box, &QMessageBox::accept);
+ itemForButton.insert(button, action);
+ }
}
- box->exec();
+ if (box->exec() == QDialog::Rejected)
+ return {};
const MessageActionItem &item = itemForButton.value(box->clickedButton());
return item.isValid() ? LanguageClientValue<MessageActionItem>(item)
: LanguageClientValue<MessageActionItem>();
@@ -1872,7 +1870,7 @@ void ClientPrivate::handleMethod(const QString &method, const MessageId &id, con
} else if (method == WorkSpaceFolderRequest::methodName) {
WorkSpaceFolderRequest::Response response(id);
const QList<ProjectExplorer::Project *> projects
- = ProjectExplorer::SessionManager::projects();
+ = ProjectExplorer::ProjectManager::projects();
if (projects.isEmpty()) {
response.setResult(nullptr);
} else {
@@ -2089,6 +2087,12 @@ bool Client::fileBelongsToProject(const Utils::FilePath &filePath) const
return project() && project()->isKnownFile(filePath);
}
+LanguageClientOutlineItem *Client::createOutlineItem(
+ const LanguageServerProtocol::DocumentSymbol &symbol)
+{
+ return new LanguageClientOutlineItem(this, symbol);
+}
+
FilePath toHostPath(const FilePath serverDeviceTemplate, const FilePath localClientPath)
{
const FilePath onDevice = serverDeviceTemplate.withNewPath(localClientPath.path());
diff --git a/src/plugins/languageclient/client.h b/src/plugins/languageclient/client.h
index 525739d7a63..51f72cc7d4a 100644
--- a/src/plugins/languageclient/client.h
+++ b/src/plugins/languageclient/client.h
@@ -31,7 +31,6 @@ class Unregistration;
} // namespace LanguageServerProtocol
namespace LanguageClient {
-
class BaseClientInterface;
class ClientPrivate;
class DiagnosticManager;
@@ -40,6 +39,7 @@ class DynamicCapabilities;
class HoverHandler;
class InterfaceController;
class LanguageClientCompletionAssistProvider;
+class LanguageClientOutlineItem;
class LanguageClientQuickFixProvider;
class LanguageFilter;
class ProgressManager;
@@ -160,13 +160,13 @@ public:
const LanguageServerProtocol::Diagnostic &diag) const;
bool hasDiagnostics(const TextEditor::TextDocument *document) const;
void setSemanticTokensHandler(const SemanticTokensHandler &handler);
- void setSymbolStringifier(const LanguageServerProtocol::SymbolStringifier &stringifier);
- LanguageServerProtocol::SymbolStringifier symbolStringifier() const;
void setSnippetsGroup(const QString &group);
void setCompletionAssistProvider(LanguageClientCompletionAssistProvider *provider);
void setQuickFixAssistProvider(LanguageClientQuickFixProvider *provider);
virtual bool supportsDocumentSymbols(const TextEditor::TextDocument *doc) const;
virtual bool fileBelongsToProject(const Utils::FilePath &filePath) const;
+ virtual LanguageClientOutlineItem *createOutlineItem(
+ const LanguageServerProtocol::DocumentSymbol &symbol);
LanguageServerProtocol::DocumentUri::PathMapper hostPathMapper() const;
Utils::FilePath serverUriToHostPath(const LanguageServerProtocol::DocumentUri &uri) const;
diff --git a/src/plugins/languageclient/clientrequesttask.cpp b/src/plugins/languageclient/clientrequesttask.cpp
new file mode 100644
index 00000000000..c5503bd6947
--- /dev/null
+++ b/src/plugins/languageclient/clientrequesttask.cpp
@@ -0,0 +1,39 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "clientrequesttask.h"
+
+#include <QScopeGuard>
+
+using namespace LanguageServerProtocol;
+
+namespace LanguageClient {
+
+ClientRequestTaskAdapter::ClientRequestTaskAdapter()
+{
+ task()->setResponseCallback([this](const WorkspaceSymbolRequest::Response &response){
+ emit done(response.result().has_value());
+ });
+}
+
+void ClientRequestTaskAdapter::start()
+{
+ task()->start();
+}
+
+bool WorkspaceSymbolRequestTask::preStartCheck()
+{
+ if (!ClientRequestTask::preStartCheck() || !client()->locatorsEnabled())
+ return false;
+
+ const std::optional<std::variant<bool, WorkDoneProgressOptions>> capability
+ = client()->capabilities().workspaceSymbolProvider();
+ if (!capability.has_value())
+ return false;
+ if (std::holds_alternative<bool>(*capability) && !std::get<bool>(*capability))
+ return false;
+
+ return true;
+}
+
+} // namespace LanguageClient
diff --git a/src/plugins/languageclient/clientrequesttask.h b/src/plugins/languageclient/clientrequesttask.h
new file mode 100644
index 00000000000..c2de15e7007
--- /dev/null
+++ b/src/plugins/languageclient/clientrequesttask.h
@@ -0,0 +1,78 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "languageclient_global.h"
+
+#include "client.h"
+
+#include <languageserverprotocol/lsptypes.h>
+#include <languageserverprotocol/lsputils.h>
+#include <languageserverprotocol/workspace.h>
+#include <utils/tasktree.h>
+
+namespace LanguageClient {
+
+
+template <typename Request>
+class LANGUAGECLIENT_EXPORT ClientRequestTask
+{
+public:
+ virtual ~ClientRequestTask()
+ {
+ if (m_id)
+ m_client->cancelRequest(*m_id); // In order to not to invoke a response callback anymore
+ }
+
+ void setClient(Client *client) { m_client = client; }
+ Client *client() const { return m_client; }
+ void setParams(const typename Request::Parameters &params) { m_params = params; }
+
+ void start()
+ {
+ QTC_ASSERT(isRunning(), return);
+ QTC_ASSERT(preStartCheck(), m_callback({}); return);
+
+ Request request(m_params);
+ request.setResponseCallback([this](const typename Request::Response &response) {
+ m_response = response;
+ m_id = {};
+ m_callback(response);
+ });
+ m_id = request.id();
+ m_client->sendMessage(request);
+ }
+
+ bool isRunning() const { return m_id.has_value(); }
+ virtual bool preStartCheck() { return m_client && m_client->reachable() && m_params.isValid(); }
+
+ typename Request::Response response() const { return m_response; }
+ void setResponseCallback(typename Request::ResponseCallback callback) { m_callback = callback; }
+
+private:
+ Client *m_client = nullptr;
+ typename Request::Parameters m_params;
+ typename Request::ResponseCallback m_callback;
+ std::optional<LanguageServerProtocol::MessageId> m_id;
+ typename Request::Response m_response;
+};
+
+class LANGUAGECLIENT_EXPORT WorkspaceSymbolRequestTask
+ : public ClientRequestTask<LanguageServerProtocol::WorkspaceSymbolRequest>
+{
+public:
+ bool preStartCheck() override;
+};
+
+class LANGUAGECLIENT_EXPORT ClientRequestTaskAdapter
+ : public Utils::Tasking::TaskAdapter<WorkspaceSymbolRequestTask>
+{
+public:
+ ClientRequestTaskAdapter();
+ void start() final;
+};
+
+} // namespace LanguageClient
+
+QTC_DECLARE_CUSTOM_TASK(WorkspaceSymbolRequest, LanguageClient::WorkspaceSymbolRequestTask);
diff --git a/src/plugins/languageclient/languageclient.qbs b/src/plugins/languageclient/languageclient.qbs
index 9a48caf9e3c..649308b9b9b 100644
--- a/src/plugins/languageclient/languageclient.qbs
+++ b/src/plugins/languageclient/languageclient.qbs
@@ -24,6 +24,8 @@ QtcPlugin {
"callhierarchy.h",
"client.cpp",
"client.h",
+ "clientrequesttask.cpp",
+ "clientrequesttask.h",
"diagnosticmanager.cpp",
"diagnosticmanager.h",
"documentsymbolcache.cpp",
diff --git a/src/plugins/languageclient/languageclient_global.h b/src/plugins/languageclient/languageclient_global.h
index 0dca3e6bbfc..d115d10a252 100644
--- a/src/plugins/languageclient/languageclient_global.h
+++ b/src/plugins/languageclient/languageclient_global.h
@@ -29,5 +29,7 @@ const char LANGUAGECLIENT_WORKSPACE_CLASS_FILTER_DISPLAY_NAME[] = QT_TRANSLATE_N
const char LANGUAGECLIENT_WORKSPACE_METHOD_FILTER_ID[] = "Workspace Functions and Methods";
const char LANGUAGECLIENT_WORKSPACE_METHOD_FILTER_DISPLAY_NAME[] = QT_TRANSLATE_NOOP("QtC::LanguageClient", "Functions and Methods in Workspace");
+const char CALL_HIERARCHY_FACTORY_ID[] = "LanguageClient.CallHierarchy";
+
} // namespace Constants
} // namespace LanguageClient
diff --git a/src/plugins/languageclient/languageclientmanager.cpp b/src/plugins/languageclient/languageclientmanager.cpp
index a41c4f01d6a..2d896f1a2d3 100644
--- a/src/plugins/languageclient/languageclientmanager.cpp
+++ b/src/plugins/languageclient/languageclientmanager.cpp
@@ -11,13 +11,14 @@
#include <coreplugin/editormanager/ieditor.h>
#include <coreplugin/find/searchresultwindow.h>
#include <coreplugin/icore.h>
+#include <coreplugin/navigationwidget.h>
#include <languageserverprotocol/messages.h>
#include <languageserverprotocol/progresssupport.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorer.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <texteditor/textdocument.h>
#include <texteditor/texteditor.h>
@@ -41,7 +42,7 @@ static LanguageClientManager *managerInstance = nullptr;
static bool g_shuttingDown = false;
LanguageClientManager::LanguageClientManager(QObject *parent)
- : QObject (parent)
+ : QObject(parent)
{
using namespace Core;
using namespace ProjectExplorer;
@@ -55,9 +56,9 @@ LanguageClientManager::LanguageClientManager(QObject *parent)
this, &LanguageClientManager::documentContentsSaved);
connect(EditorManager::instance(), &EditorManager::aboutToSave,
this, &LanguageClientManager::documentWillSave);
- connect(SessionManager::instance(), &SessionManager::projectAdded,
+ connect(ProjectManager::instance(), &ProjectManager::projectAdded,
this, &LanguageClientManager::projectAdded);
- connect(SessionManager::instance(), &SessionManager::projectRemoved,
+ connect(ProjectManager::instance(), &ProjectManager::projectRemoved,
this, [&](Project *project) { project->disconnect(this); });
}
@@ -91,7 +92,7 @@ void LanguageClient::LanguageClientManager::addClient(Client *client)
&Client::initialized,
managerInstance,
[client](const LanguageServerProtocol::ServerCapabilities &capabilities) {
- managerInstance->m_currentDocumentLocatorFilter.updateCurrentClient();
+ emit managerInstance->clientInitialized(client);
managerInstance->m_inspector.clientInitialized(client->name(), capabilities);
});
connect(client,
@@ -317,7 +318,7 @@ void LanguageClientManager::applySettings()
continue;
const Utils::FilePath filePath = textDocument->filePath();
for (ProjectExplorer::Project *project :
- ProjectExplorer::SessionManager::projects()) {
+ ProjectExplorer::ProjectManager::projects()) {
if (project->isKnownFile(filePath)) {
Client *client = clientForProject[project];
if (!client) {
@@ -448,6 +449,8 @@ QList<Client *> LanguageClientManager::reachableClients()
void LanguageClientManager::editorOpened(Core::IEditor *editor)
{
using namespace TextEditor;
+ using namespace Core;
+
if (auto *textEditor = qobject_cast<BaseTextEditor *>(editor)) {
if (TextEditorWidget *widget = textEditor->editorWidget()) {
connect(widget, &TextEditorWidget::requestLinkAt, this,
@@ -466,6 +469,14 @@ void LanguageClientManager::editorOpened(Core::IEditor *editor)
if (auto client = clientForDocument(document))
client->symbolSupport().renameSymbol(document, cursor);
});
+ connect(widget, &TextEditorWidget::requestCallHierarchy, this,
+ [this, document = textEditor->textDocument()]() {
+ if (clientForDocument(document)) {
+ emit openCallHierarchy();
+ NavigationWidget::activateSubWidget(Constants::CALL_HIERARCHY_FACTORY_ID,
+ Side::Left);
+ }
+ });
connect(widget, &TextEditorWidget::cursorPositionChanged, this, [widget]() {
if (Client *client = clientForDocument(widget->textDocument()))
if (client->reachable())
@@ -494,7 +505,7 @@ void LanguageClientManager::documentOpened(Core::IDocument *document)
if (setting->m_startBehavior == BaseSettings::RequiresProject) {
const Utils::FilePath &filePath = document->filePath();
for (ProjectExplorer::Project *project :
- ProjectExplorer::SessionManager::projects()) {
+ ProjectExplorer::ProjectManager::projects()) {
// check whether file is part of this project
if (!project->isKnownFile(filePath))
continue;
diff --git a/src/plugins/languageclient/languageclientmanager.h b/src/plugins/languageclient/languageclientmanager.h
index 6ceca307a61..cf90a2f7468 100644
--- a/src/plugins/languageclient/languageclientmanager.h
+++ b/src/plugins/languageclient/languageclientmanager.h
@@ -80,8 +80,10 @@ public:
signals:
void clientAdded(Client *client);
+ void clientInitialized(Client *client);
void clientRemoved(Client *client);
void shutdownFinished();
+ void openCallHierarchy();
private:
LanguageClientManager(QObject *parent);
@@ -102,7 +104,7 @@ private:
QList<BaseSettings *> m_currentSettings; // owned
QMap<QString, QList<Client *>> m_clientsForSetting;
QHash<TextEditor::TextDocument *, QPointer<Client>> m_clientForDocument;
- DocumentLocatorFilter m_currentDocumentLocatorFilter;
+ DocumentLocatorFilter m_currentDocumentLocatorFilter{this};
WorkspaceLocatorFilter m_workspaceLocatorFilter;
WorkspaceClassLocatorFilter m_workspaceClassLocatorFilter;
WorkspaceMethodLocatorFilter m_workspaceMethodLocatorFilter;
diff --git a/src/plugins/languageclient/languageclientoutline.cpp b/src/plugins/languageclient/languageclientoutline.cpp
index 0d7ae8c3715..b065754ec0d 100644
--- a/src/plugins/languageclient/languageclientoutline.cpp
+++ b/src/plugins/languageclient/languageclientoutline.cpp
@@ -42,67 +42,10 @@ const QList<DocumentSymbol> sortedSymbols(const QList<DocumentSymbol> &symbols)
});
}
-class LanguageClientOutlineItem : public Utils::TypedTreeItem<LanguageClientOutlineItem>
-{
-public:
- LanguageClientOutlineItem() = default;
- LanguageClientOutlineItem(const SymbolInformation &info)
- : m_name(info.name())
- , m_range(info.location().range())
- , m_type(info.kind())
- { }
-
- LanguageClientOutlineItem(const DocumentSymbol &info, const SymbolStringifier &stringifier)
- : m_name(info.name())
- , m_detail(info.detail().value_or(QString()))
- , m_range(info.range())
- , m_symbolStringifier(stringifier)
- , m_type(info.kind())
- {
- const QList<LanguageServerProtocol::DocumentSymbol> children = sortedSymbols(
- info.children().value_or(QList<DocumentSymbol>()));
- for (const DocumentSymbol &child : children)
- appendChild(new LanguageClientOutlineItem(child, stringifier));
- }
-
- // TreeItem interface
- QVariant data(int column, int role) const override
- {
- switch (role) {
- case Qt::DecorationRole:
- return symbolIcon(m_type);
- case Qt::DisplayRole:
- return m_symbolStringifier
- ? m_symbolStringifier(static_cast<SymbolKind>(m_type), m_name, m_detail)
- : m_name;
- default:
- return Utils::TreeItem::data(column, role);
- }
- }
-
- Qt::ItemFlags flags(int column) const override
- {
- Q_UNUSED(column)
- return Utils::TypedTreeItem<LanguageClientOutlineItem>::flags(column)
- | Qt::ItemIsDragEnabled;
- }
-
- Range range() const { return m_range; }
- Position pos() const { return m_range.start(); }
- bool contains(const Position &pos) const { return m_range.contains(pos); }
-
-private:
- QString m_name;
- QString m_detail;
- Range m_range;
- SymbolStringifier m_symbolStringifier;
- int m_type = -1;
-};
-
class LanguageClientOutlineModel : public Utils::TreeModel<LanguageClientOutlineItem>
{
public:
- using Utils::TreeModel<LanguageClientOutlineItem>::TreeModel;
+ LanguageClientOutlineModel(Client *client) : m_client(client) {}
void setFilePath(const Utils::FilePath &filePath) { m_filePath = filePath; }
void setInfo(const QList<SymbolInformation> &info)
@@ -115,12 +58,7 @@ public:
{
clear();
for (const DocumentSymbol &symbol : sortedSymbols(info))
- rootItem()->appendChild(new LanguageClientOutlineItem(symbol, m_symbolStringifier));
- }
-
- void setSymbolStringifier(const SymbolStringifier &stringifier)
- {
- m_symbolStringifier = stringifier;
+ rootItem()->appendChild(m_client->createOutlineItem(symbol));
}
Qt::DropActions supportedDragActions() const override
@@ -146,7 +84,7 @@ public:
}
private:
- SymbolStringifier m_symbolStringifier;
+ Client * const m_client;
Utils::FilePath m_filePath;
};
@@ -195,6 +133,7 @@ LanguageClientOutlineWidget::LanguageClientOutlineWidget(Client *client,
TextEditor::BaseTextEditor *editor)
: m_client(client)
, m_editor(editor)
+ , m_model(client)
, m_view(this)
, m_uri(m_client->hostPathToServerUri(editor->textDocument()->filePath()))
{
@@ -214,7 +153,6 @@ LanguageClientOutlineWidget::LanguageClientOutlineWidget(Client *client,
layout->setSpacing(0);
layout->addWidget(Core::ItemViewFind::createSearchableWrapper(&m_view));
setLayout(layout);
- m_model.setSymbolStringifier(m_client->symbolStringifier());
m_model.setFilePath(editor->textDocument()->filePath());
m_proxyModel.setSourceModel(&m_model);
m_view.setModel(&m_proxyModel);
@@ -373,11 +311,11 @@ Utils::TreeViewComboBox *LanguageClientOutlineWidgetFactory::createComboBox(
}
OutlineComboBox::OutlineComboBox(Client *client, TextEditor::BaseTextEditor *editor)
- : m_client(client)
+ : m_model(client)
+ , m_client(client)
, m_editorWidget(editor->editorWidget())
, m_uri(m_client->hostPathToServerUri(editor->document()->filePath()))
{
- m_model.setSymbolStringifier(client->symbolStringifier());
m_proxyModel.setSourceModel(&m_model);
const bool sorted = LanguageClientSettings::outlineComboBoxIsSorted();
m_proxyModel.sort(sorted ? 0 : -1);
@@ -455,4 +393,40 @@ void OutlineComboBox::setSorted(bool sorted)
m_proxyModel.sort(sorted ? 0 : -1);
}
+LanguageClientOutlineItem::LanguageClientOutlineItem(const SymbolInformation &info)
+ : m_name(info.name())
+ , m_range(info.location().range())
+ , m_type(info.kind())
+{ }
+
+LanguageClientOutlineItem::LanguageClientOutlineItem(Client *client, const DocumentSymbol &info)
+ : m_client(client)
+ , m_name(info.name())
+ , m_detail(info.detail().value_or(QString()))
+ , m_range(info.range())
+ , m_selectionRange(info.selectionRange())
+ , m_type(info.kind())
+{
+ const QList<LanguageServerProtocol::DocumentSymbol> children = sortedSymbols(
+ info.children().value_or(QList<DocumentSymbol>()));
+ for (const DocumentSymbol &child : children)
+ appendChild(m_client->createOutlineItem(child));
+}
+
+QVariant LanguageClientOutlineItem::data(int column, int role) const
+{
+ switch (role) {
+ case Qt::DecorationRole:
+ return symbolIcon(m_type);
+ case Qt::DisplayRole:
+ return m_name;
+ default:
+ return Utils::TreeItem::data(column, role);
+ }
+}
+Qt::ItemFlags LanguageClientOutlineItem::flags(int column) const
+{
+ Q_UNUSED(column)
+ return Utils::TypedTreeItem<LanguageClientOutlineItem>::flags(column) | Qt::ItemIsDragEnabled;
+}
} // namespace LanguageClient
diff --git a/src/plugins/languageclient/languageclientoutline.h b/src/plugins/languageclient/languageclientoutline.h
index e333563dc2d..4ddc9c79c08 100644
--- a/src/plugins/languageclient/languageclientoutline.h
+++ b/src/plugins/languageclient/languageclientoutline.h
@@ -3,7 +3,11 @@
#pragma once
+#include "languageclient_global.h"
+
+#include <languageserverprotocol/lsptypes.h>
#include <texteditor/ioutlinewidget.h>
+#include <utils/treemodel.h>
namespace TextEditor {
class TextDocument;
@@ -12,6 +16,40 @@ class BaseTextEditor;
namespace Utils { class TreeViewComboBox; }
namespace LanguageClient {
+class Client;
+
+class LANGUAGECLIENT_EXPORT LanguageClientOutlineItem
+ : public Utils::TypedTreeItem<LanguageClientOutlineItem>
+{
+public:
+ LanguageClientOutlineItem() = default;
+ LanguageClientOutlineItem(const LanguageServerProtocol::SymbolInformation &info);
+ LanguageClientOutlineItem(Client *client, const LanguageServerProtocol::DocumentSymbol &info);
+
+ LanguageServerProtocol::Range range() const { return m_range; }
+ LanguageServerProtocol::Range selectionRange() const { return m_selectionRange; }
+ LanguageServerProtocol::Position pos() const { return m_range.start(); }
+ bool contains(const LanguageServerProtocol::Position &pos) const {
+ return m_range.contains(pos);
+ }
+
+protected:
+ // TreeItem interface
+ QVariant data(int column, int role) const override;
+ Qt::ItemFlags flags(int column) const override;
+
+ QString name() const { return m_name; }
+ QString detail() const { return m_detail; }
+ int type() const { return m_type; }
+
+private:
+ Client * const m_client = nullptr;
+ QString m_name;
+ QString m_detail;
+ LanguageServerProtocol::Range m_range;
+ LanguageServerProtocol::Range m_selectionRange;
+ int m_type = -1;
+};
class Client;
diff --git a/src/plugins/languageclient/languageclientsettings.cpp b/src/plugins/languageclient/languageclientsettings.cpp
index 6f2cf7dafe5..5b15c7c218d 100644
--- a/src/plugins/languageclient/languageclientsettings.cpp
+++ b/src/plugins/languageclient/languageclientsettings.cpp
@@ -14,7 +14,7 @@
#include <coreplugin/idocument.h>
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <texteditor/plaintexteditorfactory.h>
#include <texteditor/textmark.h>
diff --git a/src/plugins/languageclient/languageclientsymbolsupport.cpp b/src/plugins/languageclient/languageclientsymbolsupport.cpp
index 995ca770f3d..1de3bede6d4 100644
--- a/src/plugins/languageclient/languageclientsymbolsupport.cpp
+++ b/src/plugins/languageclient/languageclientsymbolsupport.cpp
@@ -13,7 +13,7 @@
#include <coreplugin/find/searchresultwindow.h>
#include <projectexplorer/projectexplorer.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <utils/algorithm.h>
#include <utils/mimeutils.h>
@@ -237,7 +237,7 @@ QList<Core::SearchResultItem> generateSearchResultItems(
item.setFilePath(filePath);
item.setUseTextEditorFont(true);
if (renaming && limitToProjects) {
- const bool fileBelongsToProject = ProjectExplorer::SessionManager::projectForFile(
+ const bool fileBelongsToProject = ProjectExplorer::ProjectManager::projectForFile(
filePath);
item.setSelectForReplacement(fileBelongsToProject);
if (fileBelongsToProject
diff --git a/src/plugins/languageclient/locatorfilter.cpp b/src/plugins/languageclient/locatorfilter.cpp
index ddcfc6b43bc..f8b14f04bf6 100644
--- a/src/plugins/languageclient/locatorfilter.cpp
+++ b/src/plugins/languageclient/locatorfilter.cpp
@@ -7,27 +7,24 @@
#include "languageclient_global.h"
#include "languageclientmanager.h"
#include "languageclienttr.h"
-#include "languageclientutils.h"
#include <coreplugin/editormanager/editormanager.h>
#include <languageserverprotocol/lsptypes.h>
-#include <languageserverprotocol/servercapabilities.h>
#include <texteditor/textdocument.h>
-#include <texteditor/texteditor.h>
#include <utils/fuzzymatcher.h>
-#include <utils/linecolumn.h>
#include <QFutureWatcher>
#include <QRegularExpression>
+using namespace Core;
using namespace LanguageServerProtocol;
namespace LanguageClient {
-DocumentLocatorFilter::DocumentLocatorFilter()
+DocumentLocatorFilter::DocumentLocatorFilter(LanguageClientManager *languageManager)
{
setId(Constants::LANGUAGECLIENT_DOCUMENT_FILTER_ID);
setDisplayName(Tr::tr(Constants::LANGUAGECLIENT_DOCUMENT_FILTER_DISPLAY_NAME));
@@ -36,7 +33,9 @@ DocumentLocatorFilter::DocumentLocatorFilter()
setDefaultShortcutString(".");
setDefaultIncludedByDefault(false);
setPriority(ILocatorFilter::Low);
- connect(Core::EditorManager::instance(), &Core::EditorManager::currentEditorChanged,
+ connect(EditorManager::instance(), &EditorManager::currentEditorChanged,
+ this, &DocumentLocatorFilter::updateCurrentClient);
+ connect(languageManager, &LanguageClientManager::clientInitialized,
this, &DocumentLocatorFilter::updateCurrentClient);
}
@@ -56,7 +55,7 @@ void DocumentLocatorFilter::updateCurrentClient()
m_updateSymbolsConnection = connect(m_symbolCache, &DocumentSymbolCache::gotSymbols,
this, &DocumentLocatorFilter::updateSymbols);
}
- m_resetSymbolsConnection = connect(document, &Core::IDocument::contentsChanged,
+ m_resetSymbolsConnection = connect(document, &IDocument::contentsChanged,
this, &DocumentLocatorFilter::resetSymbols);
m_currentUri = client->hostPathToServerUri(document->filePath());
m_pathMapper = client->hostPathMapper();
@@ -85,29 +84,29 @@ void DocumentLocatorFilter::resetSymbols()
m_currentSymbols.reset();
}
-static Core::LocatorFilterEntry generateLocatorEntry(const SymbolInformation &info,
- Core::ILocatorFilter *filter,
- DocumentUri::PathMapper pathMapper)
+static LocatorFilterEntry generateLocatorEntry(const SymbolInformation &info,
+ ILocatorFilter *filter,
+ DocumentUri::PathMapper pathMapper)
{
- Core::LocatorFilterEntry entry;
+ LocatorFilterEntry entry;
entry.filter = filter;
entry.displayName = info.name();
if (std::optional<QString> container = info.containerName())
entry.extraInfo = container.value_or(QString());
entry.displayIcon = symbolIcon(info.kind());
- entry.internalData = QVariant::fromValue(info.location().toLink(pathMapper));
+ entry.linkForEditor = info.location().toLink(pathMapper);
return entry;
}
-Core::LocatorFilterEntry DocumentLocatorFilter::generateLocatorEntry(const SymbolInformation &info)
+LocatorFilterEntry DocumentLocatorFilter::generateLocatorEntry(const SymbolInformation &info)
{
QTC_ASSERT(m_pathMapper, return {});
return LanguageClient::generateLocatorEntry(info, this, m_pathMapper);
}
-QList<Core::LocatorFilterEntry> DocumentLocatorFilter::generateLocatorEntries(
+QList<LocatorFilterEntry> DocumentLocatorFilter::generateLocatorEntries(
const SymbolInformation &info, const QRegularExpression &regexp,
- const Core::LocatorFilterEntry &parent)
+ const LocatorFilterEntry &parent)
{
Q_UNUSED(parent)
if (regexp.match(info.name()).hasMatch())
@@ -115,30 +114,29 @@ QList<Core::LocatorFilterEntry> DocumentLocatorFilter::generateLocatorEntries(
return {};
}
-Core::LocatorFilterEntry DocumentLocatorFilter::generateLocatorEntry(
- const DocumentSymbol &info,
- const Core::LocatorFilterEntry &parent)
+LocatorFilterEntry DocumentLocatorFilter::generateLocatorEntry(const DocumentSymbol &info,
+ const LocatorFilterEntry &parent)
{
Q_UNUSED(parent)
- Core::LocatorFilterEntry entry;
+ LocatorFilterEntry entry;
entry.filter = this;
entry.displayName = info.name();
if (std::optional<QString> detail = info.detail())
entry.extraInfo = detail.value_or(QString());
entry.displayIcon = symbolIcon(info.kind());
const Position &pos = info.range().start();
- entry.internalData = QVariant::fromValue(Utils::LineColumn(pos.line(), pos.character()));
+ entry.linkForEditor = {m_currentFilePath, pos.line() + 1, pos.character()};
return entry;
}
-QList<Core::LocatorFilterEntry> DocumentLocatorFilter::generateLocatorEntries(
+QList<LocatorFilterEntry> DocumentLocatorFilter::generateLocatorEntries(
const DocumentSymbol &info, const QRegularExpression &regexp,
- const Core::LocatorFilterEntry &parent)
+ const LocatorFilterEntry &parent)
{
- QList<Core::LocatorFilterEntry> entries;
+ QList<LocatorFilterEntry> entries;
const QList<DocumentSymbol> children = info.children().value_or(QList<DocumentSymbol>());
const bool hasMatch = regexp.match(info.name()).hasMatch();
- Core::LocatorFilterEntry entry;
+ LocatorFilterEntry entry;
if (hasMatch || !children.isEmpty())
entry = generateLocatorEntry(info, parent);
if (hasMatch)
@@ -149,10 +147,10 @@ QList<Core::LocatorFilterEntry> DocumentLocatorFilter::generateLocatorEntries(
}
template<class T>
-QList<Core::LocatorFilterEntry> DocumentLocatorFilter::generateEntries(const QList<T> &list,
- const QString &filter)
+QList<LocatorFilterEntry> DocumentLocatorFilter::generateEntries(const QList<T> &list,
+ const QString &filter)
{
- QList<Core::LocatorFilterEntry> entries;
+ QList<LocatorFilterEntry> entries;
FuzzyMatcher::CaseSensitivity caseSensitivity
= ILocatorFilter::caseSensitivity(filter) == Qt::CaseSensitive
? FuzzyMatcher::CaseSensitivity::CaseSensitive
@@ -169,26 +167,24 @@ QList<Core::LocatorFilterEntry> DocumentLocatorFilter::generateEntries(const QLi
void DocumentLocatorFilter::prepareSearch(const QString &/*entry*/)
{
QMutexLocker locker(&m_mutex);
+ m_currentFilePath = m_pathMapper ? m_currentUri.toFilePath(m_pathMapper) : Utils::FilePath();
if (m_symbolCache && !m_currentSymbols.has_value()) {
locker.unlock();
m_symbolCache->requestSymbols(m_currentUri, Schedule::Now);
}
}
-QList<Core::LocatorFilterEntry> DocumentLocatorFilter::matchesFor(
- QFutureInterface<Core::LocatorFilterEntry> &future, const QString &entry)
+QList<LocatorFilterEntry> DocumentLocatorFilter::matchesFor(
+ QFutureInterface<LocatorFilterEntry> &future, const QString &entry)
{
QMutexLocker locker(&m_mutex);
if (!m_symbolCache)
return {};
if (!m_currentSymbols.has_value()) {
QEventLoop loop;
- connect(this, &DocumentLocatorFilter::symbolsUpToDate, &loop, [&]() { loop.exit(1); });
- QFutureWatcher<Core::LocatorFilterEntry> watcher;
- connect(&watcher,
- &QFutureWatcher<Core::LocatorFilterEntry>::canceled,
- &loop,
- &QEventLoop::quit);
+ connect(this, &DocumentLocatorFilter::symbolsUpToDate, &loop, [&] { loop.exit(1); });
+ QFutureWatcher<LocatorFilterEntry> watcher;
+ connect(&watcher, &QFutureWatcher<LocatorFilterEntry>::canceled, &loop, &QEventLoop::quit);
watcher.setFuture(future.future());
locker.unlock();
if (!loop.exec())
@@ -206,25 +202,6 @@ QList<Core::LocatorFilterEntry> DocumentLocatorFilter::matchesFor(
return {};
}
-void DocumentLocatorFilter::accept(const Core::LocatorFilterEntry &selection,
- QString * /*newText*/,
- int * /*selectionStart*/,
- int * /*selectionLength*/) const
-{
- if (selection.internalData.canConvert<Utils::LineColumn>()) {
- QTC_ASSERT(m_pathMapper, return);
- auto lineColumn = qvariant_cast<Utils::LineColumn>(selection.internalData);
- const Utils::Link link(m_currentUri.toFilePath(m_pathMapper),
- lineColumn.line + 1,
- lineColumn.column);
- Core::EditorManager::openEditorAt(link, {}, Core::EditorManager::AllowExternalEditor);
- } else if (selection.internalData.canConvert<Utils::Link>()) {
- Core::EditorManager::openEditorAt(qvariant_cast<Utils::Link>(selection.internalData),
- {},
- Core::EditorManager::AllowExternalEditor);
- }
-}
-
WorkspaceLocatorFilter::WorkspaceLocatorFilter()
: WorkspaceLocatorFilter(QVector<SymbolKind>())
{}
@@ -241,21 +218,23 @@ WorkspaceLocatorFilter::WorkspaceLocatorFilter(const QVector<SymbolKind> &filter
void WorkspaceLocatorFilter::prepareSearch(const QString &entry)
{
- prepareSearch(entry, LanguageClientManager::clients(), false);
+ prepareSearchHelper(entry, LanguageClientManager::clients(), false);
}
-void WorkspaceLocatorFilter::prepareSearch(const QString &entry, const QList<Client *> &clients)
+void WorkspaceLocatorFilter::prepareSearchForClients(const QString &entry, const QList<Client *> &clients)
{
- prepareSearch(entry, clients, true);
+ prepareSearchHelper(entry, clients, true);
}
-void WorkspaceLocatorFilter::prepareSearch(const QString &entry,
- const QList<Client *> &clients,
- bool force)
+void WorkspaceLocatorFilter::prepareSearchHelper(const QString &entry,
+ const QList<Client *> &clients, bool force)
{
m_pendingRequests.clear();
m_results.clear();
+ if (clients.isEmpty())
+ return;
+
WorkspaceSymbolParams params;
params.setQuery(entry);
if (m_maxResultCount > 0)
@@ -283,16 +262,16 @@ void WorkspaceLocatorFilter::prepareSearch(const QString &entry,
}
}
-QList<Core::LocatorFilterEntry> WorkspaceLocatorFilter::matchesFor(
- QFutureInterface<Core::LocatorFilterEntry> &future, const QString & /*entry*/)
+QList<LocatorFilterEntry> WorkspaceLocatorFilter::matchesFor(
+ QFutureInterface<LocatorFilterEntry> &future, const QString & /*entry*/)
{
QMutexLocker locker(&m_mutex);
if (!m_pendingRequests.isEmpty()) {
QEventLoop loop;
- connect(this, &WorkspaceLocatorFilter::allRequestsFinished, &loop, [&]() { loop.exit(1); });
- QFutureWatcher<Core::LocatorFilterEntry> watcher;
+ connect(this, &WorkspaceLocatorFilter::allRequestsFinished, &loop, [&] { loop.exit(1); });
+ QFutureWatcher<LocatorFilterEntry> watcher;
connect(&watcher,
- &QFutureWatcher<Core::LocatorFilterEntry>::canceled,
+ &QFutureWatcher<LocatorFilterEntry>::canceled,
&loop,
&QEventLoop::quit);
watcher.setFuture(future.future());
@@ -303,7 +282,6 @@ QList<Core::LocatorFilterEntry> WorkspaceLocatorFilter::matchesFor(
locker.relock();
}
-
if (!m_filterKinds.isEmpty()) {
m_results = Utils::filtered(m_results, [&](const SymbolInfoWithPathMapper &info) {
return m_filterKinds.contains(SymbolKind(info.symbol.kind()));
@@ -315,17 +293,6 @@ QList<Core::LocatorFilterEntry> WorkspaceLocatorFilter::matchesFor(
return Utils::transform(m_results, generateEntry).toList();
}
-void WorkspaceLocatorFilter::accept(const Core::LocatorFilterEntry &selection,
- QString * /*newText*/,
- int * /*selectionStart*/,
- int * /*selectionLength*/) const
-{
- if (selection.internalData.canConvert<Utils::Link>())
- Core::EditorManager::openEditorAt(qvariant_cast<Utils::Link>(selection.internalData),
- {},
- Core::EditorManager::AllowExternalEditor);
-}
-
void WorkspaceLocatorFilter::handleResponse(Client *client,
const WorkspaceSymbolRequest::Response &response)
{
diff --git a/src/plugins/languageclient/locatorfilter.h b/src/plugins/languageclient/locatorfilter.h
index 3eebd907a26..e37b013ef71 100644
--- a/src/plugins/languageclient/locatorfilter.h
+++ b/src/plugins/languageclient/locatorfilter.h
@@ -19,21 +19,17 @@ namespace Core { class IEditor; }
namespace LanguageClient {
+class LanguageClientManager;
+
class LANGUAGECLIENT_EXPORT DocumentLocatorFilter : public Core::ILocatorFilter
{
Q_OBJECT
public:
- DocumentLocatorFilter();
+ DocumentLocatorFilter(LanguageClientManager *languageManager);
- void updateCurrentClient();
void prepareSearch(const QString &entry) override;
QList<Core::LocatorFilterEntry> matchesFor(QFutureInterface<Core::LocatorFilterEntry> &future,
const QString &entry) override;
- void accept(const Core::LocatorFilterEntry &selection,
- QString *newText,
- int *selectionStart,
- int *selectionLength) const override;
-
signals:
void symbolsUpToDate(QPrivateSignal);
@@ -42,8 +38,10 @@ protected:
QPointer<DocumentSymbolCache> m_symbolCache;
LanguageServerProtocol::DocumentUri m_currentUri;
+ Utils::FilePath m_currentFilePath;
private:
+ void updateCurrentClient();
void updateSymbols(const LanguageServerProtocol::DocumentUri &uri,
const LanguageServerProtocol::DocumentSymbolsResult &symbols);
void resetSymbols();
@@ -81,14 +79,9 @@ public:
/// request workspace symbols for all clients with enabled locator
void prepareSearch(const QString &entry) override;
/// force request workspace symbols for all given clients
- void prepareSearch(const QString &entry, const QList<Client *> &clients);
+ void prepareSearchForClients(const QString &entry, const QList<Client *> &clients);
QList<Core::LocatorFilterEntry> matchesFor(QFutureInterface<Core::LocatorFilterEntry> &future,
const QString &entry) override;
- void accept(const Core::LocatorFilterEntry &selection,
- QString *newText,
- int *selectionStart,
- int *selectionLength) const override;
-
signals:
void allRequestsFinished(QPrivateSignal);
@@ -98,7 +91,7 @@ protected:
void setMaxResultCount(qint64 limit) { m_maxResultCount = limit; }
private:
- void prepareSearch(const QString &entry, const QList<Client *> &clients, bool force);
+ void prepareSearchHelper(const QString &entry, const QList<Client *> &clients, bool force);
void handleResponse(Client *client,
const LanguageServerProtocol::WorkspaceSymbolRequest::Response &response);
diff --git a/src/plugins/mcusupport/mcusupport.qbs b/src/plugins/mcusupport/mcusupport.qbs
index e6aec151a06..b0fa090b414 100644
--- a/src/plugins/mcusupport/mcusupport.qbs
+++ b/src/plugins/mcusupport/mcusupport.qbs
@@ -58,8 +58,7 @@ QtcPlugin {
"settingshandler.cpp",
]
- Group {
- name: "McuSupport test files"
+ QtcTestFiles {
condition: qtc.testsEnabled && (qtc_gtest_gmock.hasRepo || qtc_gtest_gmock.externalLibsPresent)
prefix: "test/"
files: [
diff --git a/src/plugins/mcusupport/mcusupportdevice.h b/src/plugins/mcusupport/mcusupportdevice.h
index 262edc39ab8..78bf6b68e3a 100644
--- a/src/plugins/mcusupport/mcusupportdevice.h
+++ b/src/plugins/mcusupport/mcusupportdevice.h
@@ -4,6 +4,7 @@
#pragma once
#include <projectexplorer/devicesupport/desktopdevice.h>
+#include <projectexplorer/devicesupport/idevicefactory.h>
namespace McuSupport {
namespace Internal {
diff --git a/src/plugins/mcusupport/mcusupportplugin.cpp b/src/plugins/mcusupport/mcusupportplugin.cpp
index 6b4e851c476..8c1d2ea35f6 100644
--- a/src/plugins/mcusupport/mcusupportplugin.cpp
+++ b/src/plugins/mcusupport/mcusupportplugin.cpp
@@ -27,8 +27,8 @@
#include <projectexplorer/kitmanager.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorerconstants.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/projecttree.h>
-#include <projectexplorer/session.h>
#include <projectexplorer/target.h>
#include <cmakeprojectmanager/cmakeprojectconstants.h>
@@ -113,8 +113,8 @@ void McuSupportPlugin::initialize()
setObjectName("McuSupportPlugin");
dd = new McuSupportPluginPrivate;
- connect(SessionManager::instance(),
- &SessionManager::projectFinishedParsing,
+ connect(ProjectManager::instance(),
+ &ProjectManager::projectFinishedParsing,
updateMCUProjectTree);
dd->m_options.registerQchFiles();
diff --git a/src/plugins/mercurial/mercurialclient.cpp b/src/plugins/mercurial/mercurialclient.cpp
index 43f942a1675..fd03bd2b2b6 100644
--- a/src/plugins/mercurial/mercurialclient.cpp
+++ b/src/plugins/mercurial/mercurialclient.cpp
@@ -60,7 +60,7 @@ MercurialDiffEditorController::MercurialDiffEditorController(IDocument *document
VcsOutputWindow::appendCommand(process.workingDirectory(), process.commandLine());
};
const auto onDiffDone = [diffInputStorage](const QtcProcess &process) {
- *diffInputStorage.activeStorage() = process.cleanedStdOut();
+ *diffInputStorage = process.cleanedStdOut();
};
const Group root {
diff --git a/src/plugins/mesonprojectmanager/mesonbuildconfiguration.cpp b/src/plugins/mesonprojectmanager/mesonbuildconfiguration.cpp
index a24a1f52bf5..bd90ee818c6 100644
--- a/src/plugins/mesonprojectmanager/mesonbuildconfiguration.cpp
+++ b/src/plugins/mesonprojectmanager/mesonbuildconfiguration.cpp
@@ -91,7 +91,8 @@ void MesonBuildConfiguration::build(const QString &target)
QStringList MesonBuildConfiguration::mesonConfigArgs()
{
- return Utils::ProcessArgs::splitArgs(m_parameters) + QStringList{QString("-Dbuildtype=%1").arg(mesonBuildTypeName(m_buildType))};
+ return Utils::ProcessArgs::splitArgs(m_parameters, HostOsInfo::hostOs())
+ + QStringList{QString("-Dbuildtype=%1").arg(mesonBuildTypeName(m_buildType))};
}
const QString &MesonBuildConfiguration::parameters() const
diff --git a/src/plugins/mesonprojectmanager/mesonbuildconfiguration.h b/src/plugins/mesonprojectmanager/mesonbuildconfiguration.h
index 6bc4a96afe8..a4059091892 100644
--- a/src/plugins/mesonprojectmanager/mesonbuildconfiguration.h
+++ b/src/plugins/mesonprojectmanager/mesonbuildconfiguration.h
@@ -2,8 +2,8 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#pragma once
-#include "projectexplorer/buildconfiguration.h"
-#include "projectexplorer/target.h"
+#include <projectexplorer/buildconfiguration.h>
+#include <projectexplorer/target.h>
namespace MesonProjectManager {
namespace Internal {
diff --git a/src/plugins/mesonprojectmanager/mesonprojectparser.cpp b/src/plugins/mesonprojectmanager/mesonprojectparser.cpp
index 5a9babeedfd..96b5fa9cb7b 100644
--- a/src/plugins/mesonprojectmanager/mesonprojectparser.cpp
+++ b/src/plugins/mesonprojectmanager/mesonprojectparser.cpp
@@ -12,6 +12,7 @@
#include <projectexplorer/projectexplorer.h>
+#include <utils/asynctask.h>
#include <utils/fileinprojectfinder.h>
#include <utils/runextensions.h>
@@ -197,7 +198,7 @@ QList<ProjectExplorer::BuildTargetInfo> MesonProjectParser::appsTargets() const
bool MesonProjectParser::startParser()
{
- m_parserFutureResult = Utils::runAsync(
+ m_parserFutureResult = Utils::asyncRun(
ProjectExplorer::ProjectExplorerPlugin::sharedThreadPool(),
[processOutput = m_process.stdOut(), introType = m_introType,
buildDir = m_buildDir.toString(), srcDir = m_srcDir] {
diff --git a/src/plugins/modeleditor/componentviewcontroller.cpp b/src/plugins/modeleditor/componentviewcontroller.cpp
index 4ce7cc16caf..75495c6684e 100644
--- a/src/plugins/modeleditor/componentviewcontroller.cpp
+++ b/src/plugins/modeleditor/componentviewcontroller.cpp
@@ -18,9 +18,9 @@
#include <cppeditor/cppmodelmanager.h>
#include <cplusplus/CppDocument.h>
-#include <projectexplorer/session.h>
-#include <projectexplorer/projectnodes.h>
#include <projectexplorer/project.h>
+#include <projectexplorer/projectmanager.h>
+#include <projectexplorer/projectnodes.h>
#include <utils/qtcassert.h>
@@ -136,7 +136,7 @@ void UpdateIncludeDependenciesVisitor::setModelUtilities(ModelUtilities *modelUt
void UpdateIncludeDependenciesVisitor::updateFilePaths()
{
m_filePaths.clear();
- for (const ProjectExplorer::Project *project : ProjectExplorer::SessionManager::projects()) {
+ for (const ProjectExplorer::Project *project : ProjectExplorer::ProjectManager::projects()) {
ProjectExplorer::ProjectNode *projectNode = project->rootProjectNode();
if (projectNode)
collectElementPaths(projectNode, &m_filePaths);
diff --git a/src/plugins/modeleditor/elementtasks.cpp b/src/plugins/modeleditor/elementtasks.cpp
index e0efd6914a6..1df37cb71ca 100644
--- a/src/plugins/modeleditor/elementtasks.cpp
+++ b/src/plugins/modeleditor/elementtasks.cpp
@@ -33,6 +33,8 @@
#include <QMenu>
+using namespace Core;
+
namespace ModelEditor {
namespace Internal {
@@ -87,15 +89,15 @@ bool ElementTasks::hasClassDefinition(const qmt::MElement *element) const
? klass->name()
: klass->umlNamespace() + "::" + klass->name();
- Core::ILocatorFilter *classesFilter
- = CppEditor::CppModelManager::instance()->classesFilter();
+ ILocatorFilter *classesFilter = CppEditor::CppModelManager::instance()->classesFilter();
if (!classesFilter)
return false;
- QFutureInterface<Core::LocatorFilterEntry> dummyInterface;
- const QList<Core::LocatorFilterEntry> matches
+ QFutureInterface<LocatorFilterEntry> dummyInterface;
+ classesFilter->prepareSearch(qualifiedClassName);
+ const QList<LocatorFilterEntry> matches
= classesFilter->matchesFor(dummyInterface, qualifiedClassName);
- for (const Core::LocatorFilterEntry &entry : matches) {
+ for (const LocatorFilterEntry &entry : matches) {
CppEditor::IndexItem::Ptr info = qvariant_cast<CppEditor::IndexItem::Ptr>(entry.internalData);
if (info->scopedSymbolName() != qualifiedClassName)
continue;
@@ -124,22 +126,20 @@ void ElementTasks::openClassDefinition(const qmt::MElement *element)
? klass->name()
: klass->umlNamespace() + "::" + klass->name();
- Core::ILocatorFilter *classesFilter
- = CppEditor::CppModelManager::instance()->classesFilter();
+ ILocatorFilter *classesFilter = CppEditor::CppModelManager::instance()->classesFilter();
if (!classesFilter)
return;
- QFutureInterface<Core::LocatorFilterEntry> dummyInterface;
- const QList<Core::LocatorFilterEntry> matches
+ QFutureInterface<LocatorFilterEntry> dummyInterface;
+ classesFilter->prepareSearch(qualifiedClassName);
+ const QList<LocatorFilterEntry> matches
= classesFilter->matchesFor(dummyInterface, qualifiedClassName);
- for (const Core::LocatorFilterEntry &entry : matches) {
+ for (const LocatorFilterEntry &entry : matches) {
CppEditor::IndexItem::Ptr info = qvariant_cast<CppEditor::IndexItem::Ptr>(entry.internalData);
if (info->scopedSymbolName() != qualifiedClassName)
continue;
- if (Core::EditorManager::instance()->openEditorAt(
- {info->filePath(), info->line(), info->column()})) {
+ if (EditorManager::openEditor(entry))
return;
- }
}
}
}
diff --git a/src/plugins/modeleditor/modelindexer.cpp b/src/plugins/modeleditor/modelindexer.cpp
index c6ace660248..9923782ac72 100644
--- a/src/plugins/modeleditor/modelindexer.cpp
+++ b/src/plugins/modeleditor/modelindexer.cpp
@@ -18,7 +18,7 @@
#include "qmt/tasks/findrootdiagramvisitor.h"
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/projectnodes.h>
#include <utils/mimeutils.h>
@@ -308,9 +308,9 @@ ModelIndexer::ModelIndexer(QObject *parent)
connect(this, &ModelIndexer::filesQueued,
d->indexerThread, &ModelIndexer::IndexerThread::onFilesQueued);
d->indexerThread->start();
- connect(ProjectExplorer::SessionManager::instance(), &ProjectExplorer::SessionManager::projectAdded,
+ connect(ProjectExplorer::ProjectManager::instance(), &ProjectExplorer::ProjectManager::projectAdded,
this, &ModelIndexer::onProjectAdded);
- connect(ProjectExplorer::SessionManager::instance(), &ProjectExplorer::SessionManager::aboutToRemoveProject,
+ connect(ProjectExplorer::ProjectManager::instance(), &ProjectExplorer::ProjectManager::aboutToRemoveProject,
this, &ModelIndexer::onAboutToRemoveProject);
}
diff --git a/src/plugins/nim/editor/nimcompletionassistprovider.cpp b/src/plugins/nim/editor/nimcompletionassistprovider.cpp
index 7179a8a64ec..c94001b348f 100644
--- a/src/plugins/nim/editor/nimcompletionassistprovider.cpp
+++ b/src/plugins/nim/editor/nimcompletionassistprovider.cpp
@@ -5,7 +5,7 @@
#include "suggest/nimsuggestcache.h"
#include "suggest/nimsuggest.h"
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <texteditor/codeassist/assistinterface.h>
#include <texteditor/codeassist/assistproposalitem.h>
#include <texteditor/codeassist/genericproposal.h>
diff --git a/src/plugins/nim/images/settingscategory_nim.png b/src/plugins/nim/images/settingscategory_nim.png
index 016c570eb49..bee1123cff8 100644
--- a/src/plugins/nim/images/settingscategory_nim.png
+++ b/src/plugins/nim/images/settingscategory_nim.png
Binary files differ
diff --git a/src/plugins/nim/images/[email protected] b/src/plugins/nim/images/[email protected]
index 4fb9c101887..2cbe6cfdc1b 100644
--- a/src/plugins/nim/images/[email protected]
+++ b/src/plugins/nim/images/[email protected]
Binary files differ
diff --git a/src/plugins/nim/project/nimcompilerbuildstep.cpp b/src/plugins/nim/project/nimcompilerbuildstep.cpp
index 3ca151def72..87f93e53e2a 100644
--- a/src/plugins/nim/project/nimcompilerbuildstep.cpp
+++ b/src/plugins/nim/project/nimcompilerbuildstep.cpp
@@ -76,7 +76,7 @@ QWidget *NimCompilerBuildStep::createConfigWidget()
auto updateUi = [=] {
const CommandLine cmd = commandLine();
- const QStringList parts = ProcessArgs::splitArgs(cmd.toUserOutput());
+ const QStringList parts = ProcessArgs::splitArgs(cmd.toUserOutput(), HostOsInfo::hostOs());
commandTextEdit->setText(parts.join(QChar::LineFeed));
diff --git a/src/plugins/nim/project/nimcompilercleanstep.cpp b/src/plugins/nim/project/nimcompilercleanstep.cpp
index c17f9437a53..8e13c068131 100644
--- a/src/plugins/nim/project/nimcompilercleanstep.cpp
+++ b/src/plugins/nim/project/nimcompilercleanstep.cpp
@@ -110,7 +110,7 @@ bool NimCompilerCleanStep::removeOutFilePath()
NimCompilerCleanStepFactory::NimCompilerCleanStepFactory()
{
registerStep<NimCompilerCleanStep>(Constants::C_NIMCOMPILERCLEANSTEP_ID);
- setFlags(BuildStepInfo::Unclonable);
+ setFlags(BuildStep::Unclonable);
setSupportedStepList(ProjectExplorer::Constants::BUILDSTEPS_CLEAN);
setSupportedConfiguration(Constants::C_NIMBUILDCONFIGURATION_ID);
setRepeatable(false);
diff --git a/src/plugins/perfprofiler/perfdatareader.cpp b/src/plugins/perfprofiler/perfdatareader.cpp
index 603d2ad016b..a545dff913e 100644
--- a/src/plugins/perfprofiler/perfdatareader.cpp
+++ b/src/plugins/perfprofiler/perfdatareader.cpp
@@ -18,7 +18,7 @@
#include <projectexplorer/kitinformation.h>
#include <projectexplorer/project.h>
#include <projectexplorer/runcontrol.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <projectexplorer/toolchain.h>
diff --git a/src/plugins/perfprofiler/perfloaddialog.cpp b/src/plugins/perfprofiler/perfloaddialog.cpp
index 8bf34316fe6..8f162ed5969 100644
--- a/src/plugins/perfprofiler/perfloaddialog.cpp
+++ b/src/plugins/perfprofiler/perfloaddialog.cpp
@@ -9,7 +9,7 @@
#include <projectexplorer/kit.h>
#include <projectexplorer/kitchooser.h>
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <utils/layoutbuilder.h>
@@ -109,7 +109,7 @@ void PerfLoadDialog::on_browseExecutableDirButton_pressed()
void PerfLoadDialog::chooseDefaults()
{
- ProjectExplorer::Target *target = ProjectExplorer::SessionManager::startupTarget();
+ ProjectExplorer::Target *target = ProjectExplorer::ProjectManager::startupTarget();
if (!target)
return;
diff --git a/src/plugins/perfprofiler/perfprofiler.qbs b/src/plugins/perfprofiler/perfprofiler.qbs
index f3151ae0a63..7a037e7fa47 100644
--- a/src/plugins/perfprofiler/perfprofiler.qbs
+++ b/src/plugins/perfprofiler/perfprofiler.qbs
@@ -75,9 +75,7 @@ QtcPlugin {
files: [ "PerfProfilerFlameGraphView.qml" ]
}
- Group {
- name: "Unit tests"
- condition: qtc.testsEnabled
+ QtcTestFiles {
prefix: "tests/"
files: [
"perfprofilertracefile_test.cpp",
diff --git a/src/plugins/perfprofiler/perfprofilertool.cpp b/src/plugins/perfprofiler/perfprofilertool.cpp
index d4515f79b85..8362ccc4ed8 100644
--- a/src/plugins/perfprofiler/perfprofilertool.cpp
+++ b/src/plugins/perfprofiler/perfprofilertool.cpp
@@ -27,7 +27,7 @@
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/runcontrol.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <qtsupport/qtkitinformation.h>
@@ -224,7 +224,7 @@ void PerfProfilerTool::createViews()
connect(recordMenu, &QMenu::aboutToShow, recordMenu, [recordMenu] {
recordMenu->hide();
PerfSettings *settings = nullptr;
- Target *target = SessionManager::startupTarget();
+ Target *target = ProjectManager::startupTarget();
if (target) {
if (auto runConfig = target->activeRunConfiguration())
settings = runConfig->currentSettings<PerfSettings>(Constants::PerfSettingsId);
@@ -572,7 +572,7 @@ static Utils::FilePaths sourceFiles(const Project *currentProject = nullptr)
if (currentProject)
sourceFiles.append(currentProject->files(Project::SourceFiles));
- const QList<Project *> projects = SessionManager::projects();
+ const QList<Project *> projects = ProjectManager::projects();
for (const Project *project : projects) {
if (project != currentProject)
sourceFiles.append(project->files(Project::SourceFiles));
@@ -609,7 +609,7 @@ void PerfProfilerTool::showLoadTraceDialog()
startLoading();
- const Project *currentProject = SessionManager::startupProject();
+ const Project *currentProject = ProjectManager::startupProject();
const Target *target = currentProject ? currentProject->activeTarget() : nullptr;
const Kit *kit = target ? target->kit() : nullptr;
populateFileFinder(currentProject, kit);
diff --git a/src/plugins/perfprofiler/perfsettings.cpp b/src/plugins/perfprofiler/perfsettings.cpp
index 8175dece0ea..9bea76b3e29 100644
--- a/src/plugins/perfprofiler/perfsettings.cpp
+++ b/src/plugins/perfprofiler/perfsettings.cpp
@@ -122,7 +122,7 @@ QStringList PerfSettings::perfRecordArguments() const
"--call-graph", callgraphArg,
sampleMode.itemValue().toString(),
QString::number(period.value())})
- + ProcessArgs::splitArgs(extraArguments.value());
+ + ProcessArgs::splitArgs(extraArguments.value(), HostOsInfo::hostOs());
}
void PerfSettings::resetToDefault()
diff --git a/src/plugins/perfprofiler/perftracepointdialog.cpp b/src/plugins/perfprofiler/perftracepointdialog.cpp
index da3b4c38c32..f276431ab5e 100644
--- a/src/plugins/perfprofiler/perftracepointdialog.cpp
+++ b/src/plugins/perfprofiler/perftracepointdialog.cpp
@@ -8,7 +8,7 @@
#include <projectexplorer/kitinformation.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorerconstants.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <utils/qtcassert.h>
@@ -51,7 +51,7 @@ PerfTracePointDialog::PerfTracePointDialog()
m_buttonBox,
}.attachTo(this);
- if (const Target *target = SessionManager::startupTarget()) {
+ if (const Target *target = ProjectManager::startupTarget()) {
const Kit *kit = target->kit();
QTC_ASSERT(kit, return);
diff --git a/src/plugins/plugins.qbs b/src/plugins/plugins.qbs
index b878e76c3e3..8e57311d9fe 100644
--- a/src/plugins/plugins.qbs
+++ b/src/plugins/plugins.qbs
@@ -23,6 +23,7 @@ Project {
"coco/coco.qbs",
"compilationdatabaseprojectmanager/compilationdatabaseprojectmanager.qbs",
"conan/conan.qbs",
+ "copilot/copilot.qbs",
"coreplugin/coreplugin.qbs",
"coreplugin/images/logo/logo.qbs",
"cpaster/cpaster.qbs",
@@ -78,12 +79,14 @@ Project {
"squish/squish.qbs",
"studiowelcome/studiowelcome.qbs",
"subversion/subversion.qbs",
+ "terminal/terminal.qbs",
"texteditor/texteditor.qbs",
"todo/todo.qbs",
"updateinfo/updateinfo.qbs",
"valgrind/valgrind.qbs",
+ "vcpkg/vcpkg.qbs",
"vcsbase/vcsbase.qbs",
"webassembly/webassembly.qbs",
- "welcome/welcome.qbs"
+ "welcome/welcome.qbs",
].concat(project.additionalPlugins)
}
diff --git a/src/plugins/projectexplorer/CMakeLists.txt b/src/plugins/projectexplorer/CMakeLists.txt
index 518d2842c8b..491587d7766 100644
--- a/src/plugins/projectexplorer/CMakeLists.txt
+++ b/src/plugins/projectexplorer/CMakeLists.txt
@@ -28,6 +28,7 @@ add_qtc_plugin(ProjectExplorer
codestylesettingspropertiespage.cpp codestylesettingspropertiespage.h
compileoutputwindow.cpp compileoutputwindow.h
configtaskhandler.cpp configtaskhandler.h
+ copystep.cpp copystep.h
copytaskhandler.cpp copytaskhandler.h
currentprojectfilter.cpp currentprojectfilter.h
currentprojectfind.cpp currentprojectfind.h
@@ -65,8 +66,7 @@ add_qtc_plugin(ProjectExplorer
devicesupport/idevicefactory.cpp devicesupport/idevicefactory.h
devicesupport/idevicefwd.h
devicesupport/idevicewidget.h
- devicesupport/localprocesslist.cpp devicesupport/localprocesslist.h
- devicesupport/sshdeviceprocesslist.cpp devicesupport/sshdeviceprocesslist.h
+ devicesupport/processlist.cpp devicesupport/processlist.h
devicesupport/sshparameters.cpp devicesupport/sshparameters.h
devicesupport/sshsettings.cpp devicesupport/sshsettings.h
devicesupport/sshsettingspage.cpp devicesupport/sshsettingspage.h
@@ -140,7 +140,7 @@ add_qtc_plugin(ProjectExplorer
projectfilewizardextension.cpp projectfilewizardextension.h
projectimporter.cpp projectimporter.h
projectmacro.cpp projectmacro.h
- projectmanager.h
+ projectmanager.cpp projectmanager.h
projectmodels.cpp projectmodels.h
projectnodes.cpp projectnodes.h
projectpanelfactory.cpp projectpanelfactory.h
@@ -158,7 +158,7 @@ add_qtc_plugin(ProjectExplorer
runsettingspropertiespage.cpp runsettingspropertiespage.h
sanitizerparser.cpp sanitizerparser.h
selectablefilesmodel.cpp selectablefilesmodel.h
- session.cpp session.h
+ session.cpp session.h session_p.h
sessiondialog.cpp sessiondialog.h
sessionmodel.cpp sessionmodel.h
sessionview.cpp sessionview.h
diff --git a/src/plugins/projectexplorer/allprojectsfilter.cpp b/src/plugins/projectexplorer/allprojectsfilter.cpp
index ea4d86dba7d..3a9b8e7a45a 100644
--- a/src/plugins/projectexplorer/allprojectsfilter.cpp
+++ b/src/plugins/projectexplorer/allprojectsfilter.cpp
@@ -3,14 +3,16 @@
#include "allprojectsfilter.h"
+#include "project.h"
#include "projectexplorer.h"
#include "projectexplorertr.h"
-#include "session.h"
-#include "project.h"
+#include "projectmanager.h"
#include <utils/algorithm.h>
+#include <utils/tasktree.h>
using namespace Core;
+using namespace Utils;
namespace ProjectExplorer::Internal {
@@ -23,22 +25,18 @@ AllProjectsFilter::AllProjectsFilter()
"\"+<number>\" or \":<number>\" to jump to the column number as well."));
setDefaultShortcutString("a");
setDefaultIncludedByDefault(true);
+ setRefreshRecipe(Tasking::Sync([this] { invalidateCache(); return true; }));
connect(ProjectExplorerPlugin::instance(), &ProjectExplorerPlugin::fileListChanged,
- this, &AllProjectsFilter::markFilesAsOutOfDate);
-}
-
-void AllProjectsFilter::markFilesAsOutOfDate()
-{
- setFileIterator(nullptr);
+ this, &AllProjectsFilter::invalidateCache);
}
void AllProjectsFilter::prepareSearch(const QString &entry)
{
Q_UNUSED(entry)
if (!fileIterator()) {
- Utils::FilePaths paths;
- for (Project *project : SessionManager::projects())
+ FilePaths paths;
+ for (Project *project : ProjectManager::projects())
paths.append(project->files(Project::SourceFiles));
Utils::sort(paths);
setFileIterator(new BaseFileFilter::ListIterator(paths));
@@ -46,10 +44,9 @@ void AllProjectsFilter::prepareSearch(const QString &entry)
BaseFileFilter::prepareSearch(entry);
}
-void AllProjectsFilter::refresh(QFutureInterface<void> &future)
+void AllProjectsFilter::invalidateCache()
{
- Q_UNUSED(future)
- QMetaObject::invokeMethod(this, &AllProjectsFilter::markFilesAsOutOfDate, Qt::QueuedConnection);
+ setFileIterator(nullptr);
}
} // ProjectExplorer::Internal
diff --git a/src/plugins/projectexplorer/allprojectsfilter.h b/src/plugins/projectexplorer/allprojectsfilter.h
index ae9fdffe92b..5d6e3b72fff 100644
--- a/src/plugins/projectexplorer/allprojectsfilter.h
+++ b/src/plugins/projectexplorer/allprojectsfilter.h
@@ -5,8 +5,6 @@
#include <coreplugin/locator/basefilefilter.h>
-#include <QFutureInterface>
-
namespace ProjectExplorer {
namespace Internal {
@@ -16,11 +14,10 @@ class AllProjectsFilter : public Core::BaseFileFilter
public:
AllProjectsFilter();
- void refresh(QFutureInterface<void> &future) override;
void prepareSearch(const QString &entry) override;
private:
- void markFilesAsOutOfDate();
+ void invalidateCache();
};
} // namespace Internal
diff --git a/src/plugins/projectexplorer/allprojectsfind.cpp b/src/plugins/projectexplorer/allprojectsfind.cpp
index ac4afc1d6e2..d61b1560523 100644
--- a/src/plugins/projectexplorer/allprojectsfind.cpp
+++ b/src/plugins/projectexplorer/allprojectsfind.cpp
@@ -7,7 +7,7 @@
#include "project.h"
#include "projectexplorer.h"
#include "projectexplorertr.h"
-#include "session.h"
+#include "projectmanager.h"
#include <coreplugin/editormanager/editormanager.h>
@@ -44,7 +44,7 @@ QString AllProjectsFind::displayName() const
bool AllProjectsFind::isEnabled() const
{
- return BaseFileFind::isEnabled() && SessionManager::hasProjects();
+ return BaseFileFind::isEnabled() && ProjectManager::hasProjects();
}
FileIterator *AllProjectsFind::files(const QStringList &nameFilters,
@@ -52,7 +52,7 @@ FileIterator *AllProjectsFind::files(const QStringList &nameFilters,
const QVariant &additionalParameters) const
{
Q_UNUSED(additionalParameters)
- return filesForProjects(nameFilters, exclusionFilters, SessionManager::projects());
+ return filesForProjects(nameFilters, exclusionFilters, ProjectManager::projects());
}
FileIterator *AllProjectsFind::filesForProjects(const QStringList &nameFilters,
diff --git a/src/plugins/projectexplorer/buildconfiguration.cpp b/src/plugins/projectexplorer/buildconfiguration.cpp
index ff14b7ab07b..fc8967aaa0b 100644
--- a/src/plugins/projectexplorer/buildconfiguration.cpp
+++ b/src/plugins/projectexplorer/buildconfiguration.cpp
@@ -17,8 +17,8 @@
#include "projectexplorer.h"
#include "projectexplorertr.h"
#include "project.h"
+#include "projectmanager.h"
#include "projecttree.h"
-#include "session.h"
#include "target.h"
#include <coreplugin/fileutils.h>
@@ -209,7 +209,7 @@ BuildConfiguration::BuildConfiguration(Target *target, Utils::Id id)
connect(target, &Target::parsingStarted, this, &BuildConfiguration::enabledChanged);
connect(target, &Target::parsingFinished, this, &BuildConfiguration::enabledChanged);
connect(this, &BuildConfiguration::enabledChanged, this, [this] {
- if (isActive() && project() == SessionManager::startupProject()) {
+ if (isActive() && project() == ProjectManager::startupProject()) {
ProjectExplorerPlugin::updateActions();
ProjectExplorerPlugin::updateRunActions();
}
diff --git a/src/plugins/projectexplorer/buildmanager.cpp b/src/plugins/projectexplorer/buildmanager.cpp
index 57bfde6e46a..6284a960417 100644
--- a/src/plugins/projectexplorer/buildmanager.cpp
+++ b/src/plugins/projectexplorer/buildmanager.cpp
@@ -16,8 +16,8 @@
#include "projectexplorerconstants.h"
#include "projectexplorersettings.h"
#include "projectexplorertr.h"
+#include "projectmanager.h"
#include "runcontrol.h"
-#include "session.h"
#include "target.h"
#include "task.h"
#include "taskhub.h"
@@ -258,7 +258,7 @@ BuildManager::BuildManager(QObject *parent, QAction *cancelBuildAction)
m_instance = this;
d = new BuildManagerPrivate;
- connect(SessionManager::instance(), &SessionManager::aboutToRemoveProject,
+ connect(ProjectManager::instance(), &ProjectManager::aboutToRemoveProject,
this, &BuildManager::aboutToRemoveProject);
d->m_outputWindow = new Internal::CompileOutputWindow(cancelBuildAction);
@@ -318,19 +318,19 @@ void BuildManager::rebuildProjectWithoutDependencies(Project *project)
void BuildManager::buildProjectWithDependencies(Project *project, ConfigSelection configSelection)
{
- queue(SessionManager::projectOrder(project), {Id(Constants::BUILDSTEPS_BUILD)},
+ queue(ProjectManager::projectOrder(project), {Id(Constants::BUILDSTEPS_BUILD)},
configSelection);
}
void BuildManager::cleanProjectWithDependencies(Project *project, ConfigSelection configSelection)
{
- queue(SessionManager::projectOrder(project), {Id(Constants::BUILDSTEPS_CLEAN)},
+ queue(ProjectManager::projectOrder(project), {Id(Constants::BUILDSTEPS_CLEAN)},
configSelection);
}
void BuildManager::rebuildProjectWithDependencies(Project *project, ConfigSelection configSelection)
{
- queue(SessionManager::projectOrder(project),
+ queue(ProjectManager::projectOrder(project),
{Id(Constants::BUILDSTEPS_CLEAN), Id(Constants::BUILDSTEPS_BUILD)},
configSelection);
}
@@ -384,7 +384,7 @@ BuildForRunConfigStatus BuildManager::potentiallyBuildForRunConfig(RunConfigurat
}
Project * const pro = rc->target()->project();
- const int queueCount = queue(SessionManager::projectOrder(pro), stepIds,
+ const int queueCount = queue(ProjectManager::projectOrder(pro), stepIds,
ConfigSelection::Active, rc);
if (rc->target()->activeBuildConfiguration())
rc->target()->activeBuildConfiguration()->restrictNextBuild(nullptr);
diff --git a/src/plugins/projectexplorer/buildsettingspropertiespage.cpp b/src/plugins/projectexplorer/buildsettingspropertiespage.cpp
index 410449083c7..c715cff6d0f 100644
--- a/src/plugins/projectexplorer/buildsettingspropertiespage.cpp
+++ b/src/plugins/projectexplorer/buildsettingspropertiespage.cpp
@@ -185,7 +185,7 @@ void BuildSettingsWidget::currentIndexChanged(int index)
{
auto buildConfiguration = qobject_cast<BuildConfiguration *>(
m_target->buildConfigurationModel()->projectConfigurationAt(index));
- SessionManager::setActiveBuildConfiguration(m_target, buildConfiguration, SetActive::Cascade);
+ m_target->setActiveBuildConfiguration(buildConfiguration, SetActive::Cascade);
}
void BuildSettingsWidget::updateActiveConfiguration()
@@ -222,7 +222,7 @@ void BuildSettingsWidget::createConfiguration(const BuildInfo &info_)
return;
m_target->addBuildConfiguration(bc);
- SessionManager::setActiveBuildConfiguration(m_target, bc, SetActive::Cascade);
+ m_target->setActiveBuildConfiguration(bc, SetActive::Cascade);
}
QString BuildSettingsWidget::uniqueName(const QString & name)
@@ -286,7 +286,7 @@ void BuildSettingsWidget::cloneConfiguration()
bc->setDisplayName(name);
const FilePath buildDirectory = bc->buildDirectory();
if (buildDirectory != m_target->project()->projectDirectory()) {
- const std::function<bool(const FilePath &)> isBuildDirOk = [this](const FilePath &candidate) {
+ const FilePathPredicate isBuildDirOk = [this](const FilePath &candidate) {
if (candidate.exists())
return false;
return !anyOf(m_target->buildConfigurations(), [&candidate](const BuildConfiguration *bc) {
@@ -295,7 +295,7 @@ void BuildSettingsWidget::cloneConfiguration()
bc->setBuildDirectory(makeUniquelyNumbered(buildDirectory, isBuildDirOk));
}
m_target->addBuildConfiguration(bc);
- SessionManager::setActiveBuildConfiguration(m_target, bc, SetActive::Cascade);
+ m_target->setActiveBuildConfiguration(bc, SetActive::Cascade);
}
void BuildSettingsWidget::deleteConfiguration(BuildConfiguration *deleteConfiguration)
diff --git a/src/plugins/projectexplorer/buildstep.cpp b/src/plugins/projectexplorer/buildstep.cpp
index c078a73d822..84c2fcf79ee 100644
--- a/src/plugins/projectexplorer/buildstep.cpp
+++ b/src/plugins/projectexplorer/buildstep.cpp
@@ -361,7 +361,7 @@ bool BuildStepFactory::canHandle(BuildStepList *bsl) const
return false;
}
- if (!m_isRepeatable && bsl->contains(m_info.id))
+ if (!m_isRepeatable && bsl->contains(m_stepId))
return false;
if (m_supportedConfiguration.isValid()) {
@@ -375,14 +375,19 @@ bool BuildStepFactory::canHandle(BuildStepList *bsl) const
return true;
}
+QString BuildStepFactory::displayName() const
+{
+ return m_displayName;
+}
+
void BuildStepFactory::setDisplayName(const QString &displayName)
{
- m_info.displayName = displayName;
+ m_displayName = displayName;
}
-void BuildStepFactory::setFlags(BuildStepInfo::Flags flags)
+void BuildStepFactory::setFlags(BuildStep::Flags flags)
{
- m_info.flags = flags;
+ m_flags = flags;
}
void BuildStepFactory::setSupportedStepList(Id id)
@@ -415,20 +420,20 @@ void BuildStepFactory::setSupportedDeviceTypes(const QList<Id> &ids)
m_supportedDeviceTypes = ids;
}
-BuildStepInfo BuildStepFactory::stepInfo() const
+BuildStep::Flags BuildStepFactory::stepFlags() const
{
- return m_info;
+ return m_flags;
}
Id BuildStepFactory::stepId() const
{
- return m_info.id;
+ return m_stepId;
}
BuildStep *BuildStepFactory::create(BuildStepList *parent)
{
- BuildStep *step = m_info.creator(parent);
- step->setDefaultDisplayName(m_info.displayName);
+ BuildStep *step = m_creator(parent);
+ step->setDefaultDisplayName(m_displayName);
return step;
}
diff --git a/src/plugins/projectexplorer/buildstep.h b/src/plugins/projectexplorer/buildstep.h
index 547a5e2027b..d0a21910afa 100644
--- a/src/plugins/projectexplorer/buildstep.h
+++ b/src/plugins/projectexplorer/buildstep.h
@@ -76,6 +76,12 @@ public:
enum OutputNewlineSetting { DoAppendNewline, DontAppendNewline };
+ enum Flags {
+ Uncreatable = 1 << 0,
+ Unclonable = 1 << 1,
+ UniqueStep = 1 << 8 // Can't be used twice in a BuildStepList
+ };
+
bool widgetExpandedByDefault() const;
void setWidgetExpandedByDefault(bool widgetExpandedByDefault);
@@ -136,23 +142,6 @@ private:
QString m_summaryText;
};
-class PROJECTEXPLORER_EXPORT BuildStepInfo
-{
-public:
- enum Flags {
- Uncreatable = 1 << 0,
- Unclonable = 1 << 1,
- UniqueStep = 1 << 8 // Can't be used twice in a BuildStepList
- };
-
- using BuildStepCreator = std::function<BuildStep *(BuildStepList *)>;
-
- Utils::Id id;
- QString displayName;
- Flags flags = Flags();
- BuildStepCreator creator;
-};
-
class PROJECTEXPLORER_EXPORT BuildStepFactory
{
public:
@@ -163,22 +152,24 @@ public:
static const QList<BuildStepFactory *> allBuildStepFactories();
- BuildStepInfo stepInfo() const;
+ BuildStep::Flags stepFlags() const;
Utils::Id stepId() const;
BuildStep *create(BuildStepList *parent);
BuildStep *restore(BuildStepList *parent, const QVariantMap &map);
bool canHandle(BuildStepList *bsl) const;
+ QString displayName() const;
+
protected:
using BuildStepCreator = std::function<BuildStep *(BuildStepList *)>;
template <class BuildStepType>
void registerStep(Utils::Id id)
{
- QTC_CHECK(!m_info.creator);
- m_info.id = id;
- m_info.creator = [id](BuildStepList *bsl) { return new BuildStepType(bsl, id); };
+ QTC_CHECK(!m_creator);
+ m_stepId = id;
+ m_creator = [id](BuildStepList *bsl) { return new BuildStepType(bsl, id); };
}
void setSupportedStepList(Utils::Id id);
@@ -189,10 +180,13 @@ protected:
void setSupportedDeviceTypes(const QList<Utils::Id> &ids);
void setRepeatable(bool on) { m_isRepeatable = on; }
void setDisplayName(const QString &displayName);
- void setFlags(BuildStepInfo::Flags flags);
+ void setFlags(BuildStep::Flags flags);
private:
- BuildStepInfo m_info;
+ Utils::Id m_stepId;
+ QString m_displayName;
+ BuildStep::Flags m_flags = {};
+ BuildStepCreator m_creator;
Utils::Id m_supportedProjectType;
QList<Utils::Id> m_supportedDeviceTypes;
diff --git a/src/plugins/projectexplorer/buildstepspage.cpp b/src/plugins/projectexplorer/buildstepspage.cpp
index 241d2a98352..21fb4c20932 100644
--- a/src/plugins/projectexplorer/buildstepspage.cpp
+++ b/src/plugins/projectexplorer/buildstepspage.cpp
@@ -209,14 +209,14 @@ void BuildStepListWidget::updateAddBuildStepMenu()
if (!factory->canHandle(m_buildStepList))
continue;
- const BuildStepInfo &info = factory->stepInfo();
- if (info.flags & BuildStepInfo::Uncreatable)
+ const BuildStep::Flags flags = factory->stepFlags();
+ if (flags & BuildStep::Uncreatable)
continue;
- if ((info.flags & BuildStepInfo::UniqueStep) && m_buildStepList->contains(info.id))
+ if ((flags & BuildStep::UniqueStep) && m_buildStepList->contains(factory->stepId()))
continue;
- QAction *action = menu->addAction(info.displayName);
+ QAction *action = menu->addAction(factory->displayName());
connect(action, &QAction::triggered, this, [factory, this] {
BuildStep *newStep = factory->create(m_buildStepList);
QTC_ASSERT(newStep, return);
diff --git a/src/plugins/projectexplorer/buildsystem.cpp b/src/plugins/projectexplorer/buildsystem.cpp
index 0a2c2f3a85d..4316bba9102 100644
--- a/src/plugins/projectexplorer/buildsystem.cpp
+++ b/src/plugins/projectexplorer/buildsystem.cpp
@@ -7,9 +7,9 @@
#include "extracompiler.h"
#include "projectexplorer.h"
#include "projectexplorertr.h"
+#include "projectmanager.h"
#include "runconfiguration.h"
#include "runcontrol.h"
-#include "session.h"
#include "target.h"
#include <coreplugin/messagemanager.h>
@@ -64,7 +64,7 @@ BuildSystem::BuildSystem(Target *target)
connect(&d->m_delayedParsingTimer, &QTimer::timeout, this,
[this] {
- if (SessionManager::hasProject(project()))
+ if (ProjectManager::hasProject(project()))
triggerParsing();
else
requestDelayedParse();
diff --git a/src/plugins/projectexplorer/copystep.cpp b/src/plugins/projectexplorer/copystep.cpp
new file mode 100644
index 00000000000..e2b478a237a
--- /dev/null
+++ b/src/plugins/projectexplorer/copystep.cpp
@@ -0,0 +1,114 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "copystep.h"
+
+#include "projectexplorertr.h"
+
+#include <utils/aspects.h>
+
+using namespace Utils;
+
+namespace ProjectExplorer::Internal {
+
+const char SOURCE_KEY[] = "ProjectExplorer.CopyStep.Source";
+const char TARGET_KEY[] = "ProjectExplorer.CopyStep.Target";
+
+class CopyStepBase : public BuildStep
+{
+public:
+ CopyStepBase(BuildStepList *bsl, Id id)
+ : BuildStep(bsl, id)
+ {
+ m_sourceAspect = addAspect<StringAspect>();
+ m_sourceAspect->setSettingsKey(SOURCE_KEY);
+ m_sourceAspect->setDisplayStyle(StringAspect::PathChooserDisplay);
+ m_sourceAspect->setLabelText(Tr::tr("Source:"));
+
+ m_targetAspect = addAspect<StringAspect>();
+ m_targetAspect->setSettingsKey(TARGET_KEY);
+ m_targetAspect->setDisplayStyle(StringAspect::PathChooserDisplay);
+ m_targetAspect->setLabelText(Tr::tr("Target:"));
+
+ addMacroExpander();
+ }
+
+protected:
+ bool init() final
+ {
+ m_source = m_sourceAspect->filePath();
+ m_target = m_targetAspect->filePath();
+ return m_source.exists();
+ }
+
+ void doRun() final
+ {
+ // FIXME: asyncCopy does not handle directories yet.
+ QTC_ASSERT(m_source.isFile(), emit finished(false));
+ m_source.asyncCopy(m_target, this, [this](const expected_str<void> &cont) {
+ if (!cont) {
+ addOutput(cont.error(), OutputFormat::ErrorMessage);
+ addOutput(Tr::tr("Copying failed"), OutputFormat::ErrorMessage);
+ emit finished(false);
+ } else {
+ addOutput(Tr::tr("Copying finished"), OutputFormat::NormalMessage);
+ emit finished(true);
+ }
+ });
+ }
+
+ StringAspect *m_sourceAspect;
+ StringAspect *m_targetAspect;
+
+private:
+ FilePath m_source;
+ FilePath m_target;
+};
+
+class CopyFileStep final : public CopyStepBase
+{
+public:
+ CopyFileStep(BuildStepList *bsl, Id id)
+ : CopyStepBase(bsl, id)
+ {
+ m_sourceAspect->setExpectedKind(PathChooser::File);
+ m_targetAspect->setExpectedKind(PathChooser::SaveFile);
+
+ setSummaryUpdater([] {
+ return QString("<b>" + Tr::tr("Copy file") + "</b>");
+ });
+ }
+};
+
+class CopyDirectoryStep final : public CopyStepBase
+{
+public:
+ CopyDirectoryStep(BuildStepList *bsl, Id id)
+ : CopyStepBase(bsl, id)
+ {
+ m_sourceAspect->setExpectedKind(PathChooser::Directory);
+ m_targetAspect->setExpectedKind(PathChooser::Directory);
+
+ setSummaryUpdater([] {
+ return QString("<b>" + Tr::tr("Copy directory recursively") + "</b>");
+ });
+ }
+};
+
+// Factories
+
+CopyFileStepFactory::CopyFileStepFactory()
+{
+ registerStep<CopyFileStep>("ProjectExplorer.CopyFileStep");
+ //: Default CopyStep display name
+ setDisplayName(Tr::tr("Copy file"));
+}
+
+CopyDirectoryStepFactory::CopyDirectoryStepFactory()
+{
+ registerStep<CopyDirectoryStep>("ProjectExplorer.CopyDirectoryStep");
+ //: Default CopyStep display name
+ setDisplayName(Tr::tr("Copy directory recursively"));
+}
+
+} // ProjectExplorer::Internal
diff --git a/src/plugins/projectexplorer/copystep.h b/src/plugins/projectexplorer/copystep.h
new file mode 100644
index 00000000000..07940f3a89a
--- /dev/null
+++ b/src/plugins/projectexplorer/copystep.h
@@ -0,0 +1,22 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "buildstep.h"
+
+namespace ProjectExplorer::Internal {
+
+class CopyFileStepFactory final : public BuildStepFactory
+{
+public:
+ CopyFileStepFactory();
+};
+
+class CopyDirectoryStepFactory final : public BuildStepFactory
+{
+public:
+ CopyDirectoryStepFactory();
+};
+
+} // ProjectExplorer::Internal
diff --git a/src/plugins/projectexplorer/currentprojectfilter.cpp b/src/plugins/projectexplorer/currentprojectfilter.cpp
index 69b88ddf46c..8d7d699a693 100644
--- a/src/plugins/projectexplorer/currentprojectfilter.cpp
+++ b/src/plugins/projectexplorer/currentprojectfilter.cpp
@@ -8,10 +8,12 @@
#include "projecttree.h"
#include <utils/algorithm.h>
+#include <utils/tasktree.h>
using namespace Core;
using namespace ProjectExplorer;
using namespace ProjectExplorer::Internal;
+using namespace Utils;
CurrentProjectFilter::CurrentProjectFilter()
: BaseFileFilter()
@@ -23,23 +25,17 @@ CurrentProjectFilter::CurrentProjectFilter()
"\"+<number>\" or \":<number>\" to jump to the column number as well."));
setDefaultShortcutString("p");
setDefaultIncludedByDefault(false);
+ setRefreshRecipe(Tasking::Sync([this] { invalidateCache(); return true; }));
connect(ProjectTree::instance(), &ProjectTree::currentProjectChanged,
this, &CurrentProjectFilter::currentProjectChanged);
}
-void CurrentProjectFilter::markFilesAsOutOfDate()
-{
- setFileIterator(nullptr);
-}
-
void CurrentProjectFilter::prepareSearch(const QString &entry)
{
Q_UNUSED(entry)
if (!fileIterator()) {
- Utils::FilePaths paths;
- if (m_project)
- paths = m_project->files(Project::SourceFiles);
+ const FilePaths paths = m_project ? m_project->files(Project::SourceFiles) : FilePaths();
setFileIterator(new BaseFileFilter::ListIterator(paths));
}
BaseFileFilter::prepareSearch(entry);
@@ -52,19 +48,17 @@ void CurrentProjectFilter::currentProjectChanged()
return;
if (m_project)
disconnect(m_project, &Project::fileListChanged,
- this, &CurrentProjectFilter::markFilesAsOutOfDate);
+ this, &CurrentProjectFilter::invalidateCache);
if (project)
connect(project, &Project::fileListChanged,
- this, &CurrentProjectFilter::markFilesAsOutOfDate);
+ this, &CurrentProjectFilter::invalidateCache);
m_project = project;
- markFilesAsOutOfDate();
+ invalidateCache();
}
-void CurrentProjectFilter::refresh(QFutureInterface<void> &future)
+void CurrentProjectFilter::invalidateCache()
{
- Q_UNUSED(future)
- QMetaObject::invokeMethod(this, &CurrentProjectFilter::markFilesAsOutOfDate,
- Qt::QueuedConnection);
+ setFileIterator(nullptr);
}
diff --git a/src/plugins/projectexplorer/currentprojectfilter.h b/src/plugins/projectexplorer/currentprojectfilter.h
index b9d63db2930..9b74161fbd5 100644
--- a/src/plugins/projectexplorer/currentprojectfilter.h
+++ b/src/plugins/projectexplorer/currentprojectfilter.h
@@ -5,8 +5,6 @@
#include <coreplugin/locator/basefilefilter.h>
-#include <QFutureInterface>
-
namespace ProjectExplorer {
class Project;
@@ -19,12 +17,11 @@ class CurrentProjectFilter : public Core::BaseFileFilter
public:
CurrentProjectFilter();
- void refresh(QFutureInterface<void> &future) override;
void prepareSearch(const QString &entry) override;
private:
void currentProjectChanged();
- void markFilesAsOutOfDate();
+ void invalidateCache();
Project *m_project = nullptr;
};
diff --git a/src/plugins/projectexplorer/currentprojectfind.cpp b/src/plugins/projectexplorer/currentprojectfind.cpp
index 7ecbd990697..3bde2bb62f2 100644
--- a/src/plugins/projectexplorer/currentprojectfind.cpp
+++ b/src/plugins/projectexplorer/currentprojectfind.cpp
@@ -5,8 +5,8 @@
#include "project.h"
#include "projectexplorertr.h"
+#include "projectmanager.h"
#include "projecttree.h"
-#include "session.h"
#include <utils/qtcassert.h>
#include <utils/filesearch.h>
@@ -23,7 +23,7 @@ CurrentProjectFind::CurrentProjectFind()
{
connect(ProjectTree::instance(), &ProjectTree::currentProjectChanged,
this, &CurrentProjectFind::handleProjectChanged);
- connect(SessionManager::instance(), &SessionManager::projectDisplayNameChanged,
+ connect(ProjectManager::instance(), &ProjectManager::projectDisplayNameChanged,
this, [this](ProjectExplorer::Project *p) {
if (p == ProjectTree::currentProject())
emit displayNameChanged();
@@ -61,14 +61,13 @@ FileIterator *CurrentProjectFind::files(const QStringList &nameFilters,
const QStringList &exclusionFilters,
const QVariant &additionalParameters) const
{
- QTC_ASSERT(additionalParameters.isValid(),
- return new FileListIterator(FilePaths(), QList<QTextCodec *>()));
+ QTC_ASSERT(additionalParameters.isValid(), return new FileListIterator);
const FilePath projectFile = FilePath::fromVariant(additionalParameters);
- for (Project *project : SessionManager::projects()) {
+ for (Project *project : ProjectManager::projects()) {
if (project && projectFile == project->projectFilePath())
return filesForProjects(nameFilters, exclusionFilters, {project});
}
- return new FileListIterator(FilePaths(), QList<QTextCodec *>());
+ return new FileListIterator;
}
QString CurrentProjectFind::label() const
@@ -87,7 +86,7 @@ void CurrentProjectFind::handleProjectChanged()
void CurrentProjectFind::recheckEnabled(Core::SearchResult *search)
{
const FilePath projectFile = FilePath::fromVariant(getAdditionalParameters(search));
- for (Project *project : SessionManager::projects()) {
+ for (Project *project : ProjectManager::projects()) {
if (projectFile == project->projectFilePath()) {
search->setSearchAgainEnabled(true);
return;
diff --git a/src/plugins/projectexplorer/dependenciespanel.cpp b/src/plugins/projectexplorer/dependenciespanel.cpp
index 1799126a5b9..c08ae7b523b 100644
--- a/src/plugins/projectexplorer/dependenciespanel.cpp
+++ b/src/plugins/projectexplorer/dependenciespanel.cpp
@@ -5,6 +5,7 @@
#include "project.h"
#include "projectexplorertr.h"
+#include "projectmanager.h"
#include "session.h"
#include <coreplugin/icore.h>
@@ -32,19 +33,18 @@ DependenciesModel::DependenciesModel(Project *project, QObject *parent)
{
resetModel();
- SessionManager *sessionManager = SessionManager::instance();
- connect(sessionManager, &SessionManager::projectRemoved,
+ connect(ProjectManager::instance(), &ProjectManager::projectRemoved,
this, &DependenciesModel::resetModel);
- connect(sessionManager, &SessionManager::projectAdded,
+ connect(ProjectManager::instance(), &ProjectManager::projectAdded,
this, &DependenciesModel::resetModel);
- connect(sessionManager, &SessionManager::sessionLoaded,
+ connect(SessionManager::instance(), &SessionManager::sessionLoaded,
this, &DependenciesModel::resetModel);
}
void DependenciesModel::resetModel()
{
beginResetModel();
- m_projects = SessionManager::projects();
+ m_projects = ProjectManager::projects();
m_projects.removeAll(m_project);
Utils::sort(m_projects, [](Project *a, Project *b) {
return a->displayName() < b->displayName();
@@ -77,7 +77,7 @@ QVariant DependenciesModel::data(const QModelIndex &index, int role) const
case Qt::ToolTipRole:
return p->projectFilePath().toUserOutput();
case Qt::CheckStateRole:
- return SessionManager::hasDependency(m_project, p) ? Qt::Checked : Qt::Unchecked;
+ return ProjectManager::hasDependency(m_project, p) ? Qt::Checked : Qt::Unchecked;
case Qt::DecorationRole:
return Utils::FileIconProvider::icon(p->projectFilePath());
default:
@@ -92,7 +92,7 @@ bool DependenciesModel::setData(const QModelIndex &index, const QVariant &value,
const auto c = static_cast<Qt::CheckState>(value.toInt());
if (c == Qt::Checked) {
- if (SessionManager::addDependency(m_project, p)) {
+ if (ProjectManager::addDependency(m_project, p)) {
emit dataChanged(index, index);
return true;
} else {
@@ -100,8 +100,8 @@ bool DependenciesModel::setData(const QModelIndex &index, const QVariant &value,
Tr::tr("This would create a circular dependency."));
}
} else if (c == Qt::Unchecked) {
- if (SessionManager::hasDependency(m_project, p)) {
- SessionManager::removeDependency(m_project, p);
+ if (ProjectManager::hasDependency(m_project, p)) {
+ ProjectManager::removeDependency(m_project, p);
emit dataChanged(index, index);
return true;
}
@@ -215,9 +215,9 @@ DependenciesWidget::DependenciesWidget(Project *project, QWidget *parent) : Proj
m_cascadeSetActiveCheckBox = new QCheckBox;
m_cascadeSetActiveCheckBox->setText(Tr::tr("Synchronize configuration"));
m_cascadeSetActiveCheckBox->setToolTip(Tr::tr("Synchronize active kit, build, and deploy configuration between projects."));
- m_cascadeSetActiveCheckBox->setChecked(SessionManager::isProjectConfigurationCascading());
+ m_cascadeSetActiveCheckBox->setChecked(ProjectManager::isProjectConfigurationCascading());
connect(m_cascadeSetActiveCheckBox, &QCheckBox::toggled,
- SessionManager::instance(), &SessionManager::setProjectConfigurationCascading);
+ ProjectManager::instance(), &ProjectManager::setProjectConfigurationCascading);
layout->addWidget(m_cascadeSetActiveCheckBox, 1, 0, 2, 1);
}
diff --git a/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp b/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp
index a09c8bac76d..4ad0d59d328 100644
--- a/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp
+++ b/src/plugins/projectexplorer/devicesupport/desktopdevice.cpp
@@ -3,11 +3,11 @@
#include "desktopdevice.h"
-#include "desktopprocesssignaloperation.h"
-#include "deviceprocesslist.h"
-#include "localprocesslist.h"
#include "../projectexplorerconstants.h"
#include "../projectexplorertr.h"
+#include "desktopprocesssignaloperation.h"
+#include "deviceprocesslist.h"
+#include "processlist.h"
#include <coreplugin/fileutils.h>
@@ -18,6 +18,7 @@
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
#include <utils/terminalcommand.h>
+#include <utils/terminalhooks.h>
#include <utils/url.h>
#include <QCoreApplication>
@@ -34,58 +35,11 @@ using namespace Utils;
namespace ProjectExplorer {
-static void startTerminalEmulator(const QString &workingDir, const Environment &env)
-{
-#ifdef Q_OS_WIN
- STARTUPINFO si;
- ZeroMemory(&si, sizeof(si));
- si.cb = sizeof(si);
-
- PROCESS_INFORMATION pinfo;
- ZeroMemory(&pinfo, sizeof(pinfo));
-
- static const auto quoteWinCommand = [](const QString &program) {
- const QChar doubleQuote = QLatin1Char('"');
-
- // add the program as the first arg ... it works better
- QString programName = program;
- programName.replace(QLatin1Char('/'), QLatin1Char('\\'));
- if (!programName.startsWith(doubleQuote) && !programName.endsWith(doubleQuote)
- && programName.contains(QLatin1Char(' '))) {
- programName.prepend(doubleQuote);
- programName.append(doubleQuote);
- }
- return programName;
- };
- const QString cmdLine = quoteWinCommand(qtcEnvironmentVariable("COMSPEC"));
- // cmdLine is assumed to be detached -
- // https://blogs.msdn.microsoft.com/oldnewthing/20090601-00/?p=18083
-
- const QString totalEnvironment = env.toStringList().join(QChar(QChar::Null)) + QChar(QChar::Null);
- LPVOID envPtr = (env != Environment::systemEnvironment())
- ? (WCHAR *)(totalEnvironment.utf16()) : nullptr;
-
- const bool success = CreateProcessW(0, (WCHAR *)cmdLine.utf16(),
- 0, 0, FALSE, CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT,
- envPtr, workingDir.isEmpty() ? 0 : (WCHAR *)workingDir.utf16(),
- &si, &pinfo);
-
- if (success) {
- CloseHandle(pinfo.hThread);
- CloseHandle(pinfo.hProcess);
- }
-#else
- const TerminalCommand term = TerminalCommand::terminalEmulator();
- QProcess process;
- process.setProgram(term.command.nativePath());
- process.setArguments(ProcessArgs::splitArgs(term.openArgs));
- process.setProcessEnvironment(env.toProcessEnvironment());
- process.setWorkingDirectory(workingDir);
- process.startDetached();
-#endif
-}
+class DesktopDevicePrivate : public QObject
+{};
DesktopDevice::DesktopDevice()
+ : d(new DesktopDevicePrivate())
{
setFileAccess(DesktopDeviceFileAccess::instance());
@@ -102,16 +56,24 @@ DesktopDevice::DesktopDevice()
QString::fromLatin1("%1-%2").arg(DESKTOP_PORT_START).arg(DESKTOP_PORT_END);
setFreePorts(Utils::PortList::fromString(portRange));
- setOpenTerminal([](const Environment &env, const FilePath &path) {
- const QFileInfo fileInfo = path.toFileInfo();
- const QString workingDir = QDir::toNativeSeparators(fileInfo.isDir() ?
- fileInfo.absoluteFilePath() :
- fileInfo.absolutePath());
+ setOpenTerminal([this](const Environment &env, const FilePath &path) {
const Environment realEnv = env.hasChanges() ? env : Environment::systemEnvironment();
- startTerminalEmulator(workingDir, realEnv);
+
+ const FilePath shell = Terminal::defaultShellForDevice(path);
+
+ QtcProcess *process = new QtcProcess(d.get());
+ QObject::connect(process, &QtcProcess::done, process, &QtcProcess::deleteLater);
+
+ process->setTerminalMode(TerminalMode::On);
+ process->setEnvironment(realEnv);
+ process->setCommand({shell, {}});
+ process->setWorkingDirectory(path);
+ process->start();
});
}
+DesktopDevice::~DesktopDevice() = default;
+
IDevice::DeviceInfo DesktopDevice::deviceInformation() const
{
return DeviceInfo();
@@ -137,7 +99,7 @@ bool DesktopDevice::canCreateProcessModel() const
DeviceProcessList *DesktopDevice::createProcessListModel(QObject *parent) const
{
- return new Internal::LocalProcessList(sharedFromThis(), parent);
+ return new ProcessList(sharedFromThis(), parent);
}
DeviceProcessSignalOperation::Ptr DesktopDevice::signalOperation() const
diff --git a/src/plugins/projectexplorer/devicesupport/desktopdevice.h b/src/plugins/projectexplorer/devicesupport/desktopdevice.h
index ee5ac1ca5eb..28220f21ed6 100644
--- a/src/plugins/projectexplorer/devicesupport/desktopdevice.h
+++ b/src/plugins/projectexplorer/devicesupport/desktopdevice.h
@@ -6,18 +6,22 @@
#include "../projectexplorer_export.h"
#include "idevice.h"
-#include "idevicefactory.h"
#include <QApplication>
+#include <memory>
+
namespace ProjectExplorer {
class ProjectExplorerPlugin;
+class DesktopDevicePrivate;
namespace Internal { class DesktopDeviceFactory; }
class PROJECTEXPLORER_EXPORT DesktopDevice : public IDevice
{
public:
+ ~DesktopDevice() override;
+
IDevice::DeviceInfo deviceInformation() const override;
IDeviceWidget *createWidget() override;
@@ -40,6 +44,8 @@ protected:
friend class ProjectExplorerPlugin;
friend class Internal::DesktopDeviceFactory;
+
+ std::unique_ptr<DesktopDevicePrivate> d;
};
} // namespace ProjectExplorer
diff --git a/src/plugins/projectexplorer/devicesupport/devicemanager.cpp b/src/plugins/projectexplorer/devicesupport/devicemanager.cpp
index 9f8e0594fd8..ff3baf6148a 100644
--- a/src/plugins/projectexplorer/devicesupport/devicemanager.cpp
+++ b/src/plugins/projectexplorer/devicesupport/devicemanager.cpp
@@ -17,6 +17,7 @@
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
#include <utils/stringutils.h>
+#include <utils/terminalhooks.h>
#include <QHash>
#include <QMutex>
@@ -442,6 +443,13 @@ DeviceManager::DeviceManager(bool isInstance) : d(std::make_unique<DeviceManager
device->openTerminal(env, filePath);
};
+ deviceHooks.osType = [](const FilePath &filePath) {
+ auto device = DeviceManager::deviceForPath(filePath);
+ if (!device)
+ return OsTypeLinux;
+ return device->osType();
+ };
+
DeviceProcessHooks processHooks;
processHooks.processImplHook = [](const FilePath &filePath) -> ProcessInterface * {
@@ -457,6 +465,22 @@ DeviceManager::DeviceManager(bool isInstance) : d(std::make_unique<DeviceManager
};
QtcProcess::setRemoteProcessHooks(processHooks);
+
+ Terminal::Hooks::instance().getTerminalCommandsForDevicesHook().set(
+ [this]() -> QList<Terminal::NameAndCommandLine> {
+ QList<Terminal::NameAndCommandLine> result;
+ for (const IDevice::ConstPtr device : d->devices) {
+ if (device->type() == Constants::DESKTOP_DEVICE_TYPE)
+ continue;
+
+ const FilePath shell = Terminal::defaultShellForDevice(device->rootPath());
+
+ if (!shell.isEmpty())
+ result << Terminal::NameAndCommandLine{device->displayName(),
+ CommandLine{shell, {}}};
+ }
+ return result;
+ });
}
DeviceManager::~DeviceManager()
diff --git a/src/plugins/projectexplorer/devicesupport/devicesettingswidget.cpp b/src/plugins/projectexplorer/devicesupport/devicesettingswidget.cpp
index 39af4713455..f51a0607bd1 100644
--- a/src/plugins/projectexplorer/devicesupport/devicesettingswidget.cpp
+++ b/src/plugins/projectexplorer/devicesupport/devicesettingswidget.cpp
@@ -17,13 +17,16 @@
#include <coreplugin/icore.h>
#include <utils/algorithm.h>
+#include <utils/asynctask.h>
#include <utils/layoutbuilder.h>
+#include <utils/optionpushbutton.h>
#include <utils/qtcassert.h>
#include <QComboBox>
#include <QGroupBox>
#include <QLabel>
#include <QLineEdit>
+#include <QMenu>
#include <QPushButton>
#include <QScrollArea>
#include <QTextStream>
@@ -100,14 +103,40 @@ void DeviceSettingsWidget::initGui()
m_deviceStateTextLabel = new QLabel;
m_osSpecificGroupBox = new QGroupBox(Tr::tr("Type Specific"));
m_osSpecificGroupBox->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
- m_addConfigButton = new QPushButton(Tr::tr("&Add..."));
m_removeConfigButton = new QPushButton(Tr::tr("&Remove"));
m_defaultDeviceButton = new QPushButton(Tr::tr("Set As Default"));
- auto line = new QFrame;
- line->setFrameShape(QFrame::HLine);
- line->setFrameShadow(QFrame::Sunken);
- auto customButtonsContainer = new QWidget;
- m_buttonsLayout = new QVBoxLayout(customButtonsContainer);
+
+ OptionPushButton *addButton = new OptionPushButton(Tr::tr("&Add..."));
+ connect(addButton, &OptionPushButton::clicked, this, &DeviceSettingsWidget::addDevice);
+
+ QMenu *deviceTypeMenu = new QMenu(addButton);
+ QAction *defaultAction = new QAction(Tr::tr("&Start Wizard to Add Device..."));
+ connect(defaultAction, &QAction::triggered, this, &DeviceSettingsWidget::addDevice);
+ deviceTypeMenu->addAction(defaultAction);
+ deviceTypeMenu->addSeparator();
+
+ for (IDeviceFactory *factory : IDeviceFactory::allDeviceFactories()) {
+ if (!factory->canCreate())
+ continue;
+ if (!factory->quickCreationAllowed())
+ continue;
+
+ QAction *action = new QAction(Tr::tr("Add %1").arg(factory->displayName()));
+ deviceTypeMenu->addAction(action);
+
+ connect(action, &QAction::triggered, this, [factory, this] {
+ IDevice::Ptr device = factory->construct();
+ QTC_ASSERT(device, return);
+ m_deviceManager->addDevice(device);
+ m_removeConfigButton->setEnabled(true);
+ m_configurationComboBox->setCurrentIndex(m_deviceManagerModel->indexOf(device));
+ saveSettings();
+ });
+ }
+
+ addButton->setOptionalMenu(deviceTypeMenu);
+
+ m_buttonsLayout = new QVBoxLayout;
m_buttonsLayout->setContentsMargins({});
auto scrollAreaWidget = new QWidget;
auto scrollArea = new QScrollArea;
@@ -127,25 +156,27 @@ void DeviceSettingsWidget::initGui()
Tr::tr("Current state:"), Row { m_deviceStateIconLabel, m_deviceStateTextLabel, st, }, br,
}.attachTo(m_generalGroupBox);
+ // clang-format off
Row {
Column {
Form { m_configurationLabel, m_configurationComboBox, br, },
scrollArea,
},
Column {
- m_addConfigButton,
+ addButton,
+ Space(30),
m_removeConfigButton,
m_defaultDeviceButton,
- line,
- customButtonsContainer,
+ m_buttonsLayout,
st,
},
}.attachTo(this);
+ // clang-format on
bool hasDeviceFactories = Utils::anyOf(IDeviceFactory::allDeviceFactories(),
&IDeviceFactory::canCreate);
- m_addConfigButton->setEnabled(hasDeviceFactories);
+ addButton->setEnabled(hasDeviceFactories);
int lastIndex = ICore::settings()
->value(QLatin1String(LastDeviceIndexKey), 0).toInt();
@@ -160,10 +191,10 @@ void DeviceSettingsWidget::initGui()
this, &DeviceSettingsWidget::setDefaultDevice);
connect(m_removeConfigButton, &QAbstractButton::clicked,
this, &DeviceSettingsWidget::removeDevice);
- connect(m_nameLineEdit, &QLineEdit::editingFinished,
- this, &DeviceSettingsWidget::deviceNameEditingFinished);
- connect(m_addConfigButton, &QAbstractButton::clicked,
- this, &DeviceSettingsWidget::addDevice);
+ connect(m_nameLineEdit,
+ &QLineEdit::editingFinished,
+ this,
+ &DeviceSettingsWidget::deviceNameEditingFinished);
}
void DeviceSettingsWidget::addDevice()
@@ -182,6 +213,8 @@ void DeviceSettingsWidget::addDevice()
if (device.isNull())
return;
+ Utils::asyncRun([device] { device->checkOsType(); });
+
m_deviceManager->addDevice(device);
m_removeConfigButton->setEnabled(true);
m_configurationComboBox->setCurrentIndex(m_deviceManagerModel->indexOf(device));
diff --git a/src/plugins/projectexplorer/devicesupport/devicesettingswidget.h b/src/plugins/projectexplorer/devicesupport/devicesettingswidget.h
index 5cd5cec85e2..207b60cf749 100644
--- a/src/plugins/projectexplorer/devicesupport/devicesettingswidget.h
+++ b/src/plugins/projectexplorer/devicesupport/devicesettingswidget.h
@@ -76,7 +76,6 @@ private:
QLabel *m_deviceStateIconLabel;
QLabel *m_deviceStateTextLabel;
QGroupBox *m_osSpecificGroupBox;
- QPushButton *m_addConfigButton;
QPushButton *m_removeConfigButton;
QPushButton *m_defaultDeviceButton;
QVBoxLayout *m_buttonsLayout;
diff --git a/src/plugins/projectexplorer/devicesupport/idevice.cpp b/src/plugins/projectexplorer/devicesupport/idevice.cpp
index b884237b3b2..2e6e80801b5 100644
--- a/src/plugins/projectexplorer/devicesupport/idevice.cpp
+++ b/src/plugins/projectexplorer/devicesupport/idevice.cpp
@@ -15,6 +15,7 @@
#include <coreplugin/icore.h>
+#include <utils/commandline.h>
#include <utils/devicefileaccess.h>
#include <utils/displayname.h>
#include <utils/icon.h>
@@ -92,6 +93,7 @@ static Id newId()
const char DisplayNameKey[] = "Name";
const char TypeKey[] = "OsType";
+const char ClientOsTypeKey[] = "ClientOsType";
const char IdKey[] = "InternalId";
const char OriginKey[] = "Origin";
const char MachineTypeKey[] = "Type";
@@ -425,6 +427,8 @@ void IDevice::fromMap(const QVariantMap &map)
d->type = typeFromMap(map);
d->displayName.fromMap(map, DisplayNameKey);
d->id = Id::fromSetting(map.value(QLatin1String(IdKey)));
+ d->osType = osTypeFromString(
+ map.value(QLatin1String(ClientOsTypeKey), osTypeToString(OsTypeLinux)).toString());
if (!d->id.isValid())
d->id = newId();
d->origin = static_cast<Origin>(map.value(QLatin1String(OriginKey), ManuallyAdded).toInt());
@@ -471,6 +475,7 @@ QVariantMap IDevice::toMap() const
QVariantMap map;
d->displayName.toMap(map, DisplayNameKey);
map.insert(QLatin1String(TypeKey), d->type.toString());
+ map.insert(QLatin1String(ClientOsTypeKey), osTypeToString(d->osType));
map.insert(QLatin1String(IdKey), d->id.toSetting());
map.insert(QLatin1String(OriginKey), d->origin);
@@ -503,9 +508,6 @@ IDevice::Ptr IDevice::clone() const
device->d->deviceState = d->deviceState;
device->d->deviceActions = d->deviceActions;
device->d->deviceIcons = d->deviceIcons;
- // Os type is only set in the constructor, always to the same value.
- // But make sure we notice if that changes in the future (which it shouldn't).
- QTC_CHECK(device->d->osType == d->osType);
device->d->osType = d->osType;
device->fromMap(toMap());
return device;
diff --git a/src/plugins/projectexplorer/devicesupport/idevice.h b/src/plugins/projectexplorer/devicesupport/idevice.h
index 6323b8589bb..2dc75a36980 100644
--- a/src/plugins/projectexplorer/devicesupport/idevice.h
+++ b/src/plugins/projectexplorer/devicesupport/idevice.h
@@ -221,6 +221,8 @@ public:
virtual bool prepareForBuild(const Target *target);
virtual std::optional<Utils::FilePath> clangdExecutable() const;
+ virtual void checkOsType() {}
+
protected:
IDevice();
diff --git a/src/plugins/projectexplorer/devicesupport/idevicefactory.cpp b/src/plugins/projectexplorer/devicesupport/idevicefactory.cpp
index c33bc125bb5..e88a1e61367 100644
--- a/src/plugins/projectexplorer/devicesupport/idevicefactory.cpp
+++ b/src/plugins/projectexplorer/devicesupport/idevicefactory.cpp
@@ -63,12 +63,24 @@ bool IDeviceFactory::canCreate() const
IDevice::Ptr IDeviceFactory::create() const
{
- return m_creator ? m_creator() : IDevice::Ptr();
+ if (!m_creator)
+ return {};
+
+ IDevice::Ptr device = m_creator();
+ QTC_ASSERT(device, return {});
+ device->setDefaultDisplayName(displayName());
+ return device;
}
IDevice::Ptr IDeviceFactory::construct() const
{
- return m_constructor ? m_constructor() : IDevice::Ptr();
+ if (!m_constructor)
+ return {};
+
+ IDevice::Ptr device = m_constructor();
+ QTC_ASSERT(device, return {});
+ device->setDefaultDisplayName(displayName());
+ return device;
}
static QList<IDeviceFactory *> g_deviceFactories;
@@ -105,6 +117,16 @@ void IDeviceFactory::setCreator(const std::function<IDevice::Ptr ()> &creator)
m_creator = creator;
}
+void IDeviceFactory::setQuickCreationAllowed(bool on)
+{
+ m_quickCreationAllowed = on;
+}
+
+bool IDeviceFactory::quickCreationAllowed() const
+{
+ return m_quickCreationAllowed;
+}
+
void IDeviceFactory::setConstructionFunction(const std::function<IDevice::Ptr ()> &constructor)
{
m_constructor = constructor;
diff --git a/src/plugins/projectexplorer/devicesupport/idevicefactory.h b/src/plugins/projectexplorer/devicesupport/idevicefactory.h
index 87a54244adb..665059f5b2c 100644
--- a/src/plugins/projectexplorer/devicesupport/idevicefactory.h
+++ b/src/plugins/projectexplorer/devicesupport/idevicefactory.h
@@ -26,6 +26,7 @@ public:
bool canCreate() const;
IDevicePtr construct() const;
IDevicePtr create() const;
+ bool quickCreationAllowed() const;
virtual bool canRestore(const QVariantMap &) const { return true; }
@@ -41,6 +42,7 @@ protected:
void setCombinedIcon(const Utils::FilePath &smallIcon, const Utils::FilePath &largeIcon);
void setConstructionFunction(const std::function<IDevicePtr ()> &constructor);
void setCreator(const std::function<IDevicePtr()> &creator);
+ void setQuickCreationAllowed(bool on);
private:
std::function<IDevicePtr()> m_creator;
@@ -48,6 +50,7 @@ private:
QString m_displayName;
QIcon m_icon;
std::function<IDevicePtr()> m_constructor;
+ bool m_quickCreationAllowed = false;
};
} // namespace ProjectExplorer
diff --git a/src/plugins/projectexplorer/devicesupport/localprocesslist.cpp b/src/plugins/projectexplorer/devicesupport/localprocesslist.cpp
deleted file mode 100644
index c6fd49012fb..00000000000
--- a/src/plugins/projectexplorer/devicesupport/localprocesslist.cpp
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#include "localprocesslist.h"
-
-#include <projectexplorer/devicesupport/idevice.h>
-#include <utils/processinfo.h>
-
-#include <QTimer>
-
-#if defined(Q_OS_UNIX)
-#include <unistd.h>
-#elif defined(Q_OS_WIN)
-#include <windows.h>
-#endif
-
-using namespace Utils;
-
-namespace ProjectExplorer {
-namespace Internal {
-
-LocalProcessList::LocalProcessList(const IDevice::ConstPtr &device, QObject *parent)
- : DeviceProcessList(device, parent)
-{
-#if defined(Q_OS_UNIX)
- setOwnPid(getpid());
-#elif defined(Q_OS_WIN)
- setOwnPid(GetCurrentProcessId());
-#endif
-}
-
-void LocalProcessList::doKillProcess(const ProcessInfo &processInfo)
-{
- DeviceProcessSignalOperation::Ptr signalOperation = device()->signalOperation();
- connect(signalOperation.data(), &DeviceProcessSignalOperation::finished,
- this, &LocalProcessList::reportDelayedKillStatus);
- signalOperation->killProcess(processInfo.processId);
-}
-
-void LocalProcessList::handleUpdate()
-{
- reportProcessListUpdated(ProcessInfo::processInfoList());
-}
-
-void LocalProcessList::doUpdate()
-{
- QTimer::singleShot(0, this, &LocalProcessList::handleUpdate);
-}
-
-void LocalProcessList::reportDelayedKillStatus(const QString &errorMessage)
-{
- if (errorMessage.isEmpty())
- reportProcessKilled();
- else
- reportError(errorMessage);
-}
-
-} // namespace Internal
-} // namespace ProjectExplorer
diff --git a/src/plugins/projectexplorer/devicesupport/processlist.cpp b/src/plugins/projectexplorer/devicesupport/processlist.cpp
new file mode 100644
index 00000000000..11e0932832d
--- /dev/null
+++ b/src/plugins/projectexplorer/devicesupport/processlist.cpp
@@ -0,0 +1,61 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "processlist.h"
+
+#include <projectexplorer/devicesupport/idevice.h>
+#include <utils/processinfo.h>
+
+#include <QTimer>
+
+#if defined(Q_OS_UNIX)
+#include <unistd.h>
+#elif defined(Q_OS_WIN)
+#include <windows.h>
+#endif
+
+using namespace Utils;
+
+namespace ProjectExplorer {
+
+ProcessList::ProcessList(const IDevice::ConstPtr &device, QObject *parent)
+ : DeviceProcessList(device, parent)
+{
+#if defined(Q_OS_UNIX)
+ setOwnPid(getpid());
+#elif defined(Q_OS_WIN)
+ setOwnPid(GetCurrentProcessId());
+#endif
+}
+
+void ProcessList::doKillProcess(const ProcessInfo &processInfo)
+{
+ m_signalOperation = device()->signalOperation();
+ connect(m_signalOperation.data(),
+ &DeviceProcessSignalOperation::finished,
+ this,
+ &ProcessList::reportDelayedKillStatus);
+ m_signalOperation->killProcess(processInfo.processId);
+}
+
+void ProcessList::handleUpdate()
+{
+ reportProcessListUpdated(ProcessInfo::processInfoList(DeviceProcessList::device()->rootPath()));
+}
+
+void ProcessList::doUpdate()
+{
+ QTimer::singleShot(0, this, &ProcessList::handleUpdate);
+}
+
+void ProcessList::reportDelayedKillStatus(const QString &errorMessage)
+{
+ if (errorMessage.isEmpty())
+ reportProcessKilled();
+ else
+ reportError(errorMessage);
+
+ m_signalOperation.reset();
+}
+
+} // namespace ProjectExplorer
diff --git a/src/plugins/projectexplorer/devicesupport/localprocesslist.h b/src/plugins/projectexplorer/devicesupport/processlist.h
index e3445f47b20..caebaf22f97 100644
--- a/src/plugins/projectexplorer/devicesupport/localprocesslist.h
+++ b/src/plugins/projectexplorer/devicesupport/processlist.h
@@ -4,16 +4,16 @@
#pragma once
#include "deviceprocesslist.h"
+#include "idevice.h"
namespace ProjectExplorer {
-namespace Internal {
-class LocalProcessList : public DeviceProcessList
+class PROJECTEXPLORER_EXPORT ProcessList : public DeviceProcessList
{
Q_OBJECT
public:
- explicit LocalProcessList(const IDeviceConstPtr &device, QObject *parent = nullptr);
+ explicit ProcessList(const IDeviceConstPtr &device, QObject *parent = nullptr);
private:
void doUpdate() override;
@@ -22,7 +22,9 @@ private:
private:
void handleUpdate();
void reportDelayedKillStatus(const QString &errorMessage);
+
+private:
+ DeviceProcessSignalOperation::Ptr m_signalOperation;
};
-} // namespace Internal
} // namespace ProjectExplorer
diff --git a/src/plugins/projectexplorer/devicesupport/sshdeviceprocesslist.cpp b/src/plugins/projectexplorer/devicesupport/sshdeviceprocesslist.cpp
deleted file mode 100644
index d64cde6e3ed..00000000000
--- a/src/plugins/projectexplorer/devicesupport/sshdeviceprocesslist.cpp
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#include "sshdeviceprocesslist.h"
-
-#include "idevice.h"
-#include "../projectexplorertr.h"
-
-#include <utils/processinfo.h>
-#include <utils/qtcassert.h>
-#include <utils/qtcprocess.h>
-#include <utils/stringutils.h>
-
-using namespace Utils;
-
-namespace ProjectExplorer {
-
-class SshDeviceProcessListPrivate
-{
-public:
- QtcProcess m_process;
- DeviceProcessSignalOperation::Ptr m_signalOperation;
-};
-
-SshDeviceProcessList::SshDeviceProcessList(const IDevice::ConstPtr &device, QObject *parent) :
- DeviceProcessList(device, parent), d(std::make_unique<SshDeviceProcessListPrivate>())
-{
- connect(&d->m_process, &QtcProcess::done, this, &SshDeviceProcessList::handleProcessDone);
-}
-
-SshDeviceProcessList::~SshDeviceProcessList() = default;
-
-void SshDeviceProcessList::doUpdate()
-{
- d->m_process.close();
- d->m_process.setCommand({device()->filePath("/bin/sh"), {"-c", listProcessesCommandLine()}});
- d->m_process.start();
-}
-
-void SshDeviceProcessList::doKillProcess(const ProcessInfo &process)
-{
- d->m_signalOperation = device()->signalOperation();
- QTC_ASSERT(d->m_signalOperation, return);
- connect(d->m_signalOperation.data(), &DeviceProcessSignalOperation::finished,
- this, &SshDeviceProcessList::handleKillProcessFinished);
- d->m_signalOperation->killProcess(process.processId);
-}
-
-void SshDeviceProcessList::handleProcessDone()
-{
- if (d->m_process.result() == ProcessResult::FinishedWithSuccess) {
- reportProcessListUpdated(buildProcessList(d->m_process.cleanedStdOut()));
- } else {
- const QString errorString = d->m_process.exitStatus() == QProcess::NormalExit
- ? Tr::tr("Process listing command failed with exit code %1.").arg(d->m_process.exitCode())
- : d->m_process.errorString();
- const QString stdErr = d->m_process.cleanedStdErr();
- const QString outputString
- = stdErr.isEmpty() ? stdErr : Tr::tr("Remote stderr was: %1").arg(stdErr);
- reportError(Utils::joinStrings({errorString, outputString}, '\n'));
- }
- setFinished();
-}
-
-void SshDeviceProcessList::handleKillProcessFinished(const QString &errorString)
-{
- if (errorString.isEmpty())
- reportProcessKilled();
- else
- reportError(Tr::tr("Error: Kill process failed: %1").arg(errorString));
- setFinished();
-}
-
-void SshDeviceProcessList::setFinished()
-{
- d->m_process.close();
- if (d->m_signalOperation) {
- d->m_signalOperation->disconnect(this);
- d->m_signalOperation.clear();
- }
-}
-
-} // namespace ProjectExplorer
diff --git a/src/plugins/projectexplorer/devicesupport/sshdeviceprocesslist.h b/src/plugins/projectexplorer/devicesupport/sshdeviceprocesslist.h
deleted file mode 100644
index fd560f53755..00000000000
--- a/src/plugins/projectexplorer/devicesupport/sshdeviceprocesslist.h
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#pragma once
-
-#include "deviceprocesslist.h"
-
-#include <memory>
-
-namespace ProjectExplorer {
-
-class SshDeviceProcessListPrivate;
-
-class PROJECTEXPLORER_EXPORT SshDeviceProcessList : public DeviceProcessList
-{
- Q_OBJECT
-public:
- explicit SshDeviceProcessList(const IDeviceConstPtr &device, QObject *parent = nullptr);
- ~SshDeviceProcessList() override;
-
-private:
- void handleProcessDone();
- void handleKillProcessFinished(const QString &errorString);
-
- virtual QString listProcessesCommandLine() const = 0;
- virtual QList<Utils::ProcessInfo> buildProcessList(const QString &listProcessesReply) const = 0;
-
- void doUpdate() override;
- void doKillProcess(const Utils::ProcessInfo &process) override;
-
- void setFinished();
-
- const std::unique_ptr<SshDeviceProcessListPrivate> d;
-};
-
-} // namespace ProjectExplorer
diff --git a/src/plugins/projectexplorer/devicesupport/sshparameters.cpp b/src/plugins/projectexplorer/devicesupport/sshparameters.cpp
index 3cc6364a677..2ab8aa006ea 100644
--- a/src/plugins/projectexplorer/devicesupport/sshparameters.cpp
+++ b/src/plugins/projectexplorer/devicesupport/sshparameters.cpp
@@ -19,9 +19,17 @@ using namespace Utils;
namespace ProjectExplorer {
-SshParameters::SshParameters()
+SshParameters::SshParameters() = default;
+
+QString SshParameters::userAtHost() const
{
- url.setPort(0);
+ QString res;
+ if (!m_userName.isEmpty())
+ res = m_userName + '@';
+ res += m_host;
+ if (m_port != 22)
+ res += QString(":%1").arg(m_port);
+ return res;
}
QStringList SshParameters::connectionOptions(const FilePath &binary) const
@@ -81,10 +89,11 @@ bool SshParameters::setupSshEnvironment(QtcProcess *process)
return hasDisplay;
}
-
-static inline bool equals(const SshParameters &p1, const SshParameters &p2)
+bool operator==(const SshParameters &p1, const SshParameters &p2)
{
- return p1.url == p2.url
+ return p1.m_host == p2.m_host
+ && p1.m_port == p2.m_port
+ && p1.m_userName == p2.m_userName
&& p1.authenticationType == p2.authenticationType
&& p1.privateKeyFile == p2.privateKeyFile
&& p1.hostKeyCheckingMode == p2.hostKeyCheckingMode
@@ -92,16 +101,6 @@ static inline bool equals(const SshParameters &p1, const SshParameters &p2)
&& p1.timeout == p2.timeout;
}
-bool operator==(const SshParameters &p1, const SshParameters &p2)
-{
- return equals(p1, p2);
-}
-
-bool operator!=(const SshParameters &p1, const SshParameters &p2)
-{
- return !equals(p1, p2);
-}
-
#ifdef WITH_TESTS
namespace SshTest {
const QString getHostFromEnvironment()
diff --git a/src/plugins/projectexplorer/devicesupport/sshparameters.h b/src/plugins/projectexplorer/devicesupport/sshparameters.h
index 00b63e3aacf..8bdd936d275 100644
--- a/src/plugins/projectexplorer/devicesupport/sshparameters.h
+++ b/src/plugins/projectexplorer/devicesupport/sshparameters.h
@@ -7,8 +7,6 @@
#include <utils/filepath.h>
-#include <QUrl>
-
namespace Utils { class QtcProcess; }
namespace ProjectExplorer {
@@ -29,17 +27,18 @@ public:
SshParameters();
- QString host() const { return url.host(); }
- quint16 port() const { return url.port(); }
- QString userName() const { return url.userName(); }
- QString userAtHost() const { return userName().isEmpty() ? host() : userName() + '@' + host(); }
- void setHost(const QString &host) { url.setHost(host); }
- void setPort(int port) { url.setPort(port); }
- void setUserName(const QString &name) { url.setUserName(name); }
+ QString host() const { return m_host; }
+ quint16 port() const { return m_port; }
+ QString userName() const { return m_userName; }
+
+ QString userAtHost() const;
+
+ void setHost(const QString &host) { m_host = host; }
+ void setPort(int port) { m_port = port; }
+ void setUserName(const QString &name) { m_userName = name; }
QStringList connectionOptions(const Utils::FilePath &binary) const;
- QUrl url;
Utils::FilePath privateKeyFile;
QString x11DisplayName;
int timeout = 0; // In seconds.
@@ -47,10 +46,15 @@ public:
SshHostKeyCheckingMode hostKeyCheckingMode = SshHostKeyCheckingAllowNoMatch;
static bool setupSshEnvironment(Utils::QtcProcess *process);
-};
-PROJECTEXPLORER_EXPORT bool operator==(const SshParameters &p1, const SshParameters &p2);
-PROJECTEXPLORER_EXPORT bool operator!=(const SshParameters &p1, const SshParameters &p2);
+ friend PROJECTEXPLORER_EXPORT bool operator==(const SshParameters &p1, const SshParameters &p2);
+ friend bool operator!=(const SshParameters &p1, const SshParameters &p2) { return !(p1 == p2); }
+
+private:
+ QString m_host;
+ quint16 m_port = 22;
+ QString m_userName;
+};
#ifdef WITH_TESTS
namespace SshTest {
diff --git a/src/plugins/projectexplorer/editorconfiguration.cpp b/src/plugins/projectexplorer/editorconfiguration.cpp
index fc3933d7344..ff99bc25e83 100644
--- a/src/plugins/projectexplorer/editorconfiguration.cpp
+++ b/src/plugins/projectexplorer/editorconfiguration.cpp
@@ -5,7 +5,7 @@
#include "project.h"
#include "projectexplorertr.h"
-#include "session.h"
+#include "projectmanager.h"
#include <utils/algorithm.h>
@@ -88,7 +88,7 @@ EditorConfiguration::EditorConfiguration() : d(std::make_unique<EditorConfigurat
// if setCurrentDelegate is 0 values are read from *this prefs
d->m_defaultCodeStyle->setCurrentDelegate(TextEditorSettings::codeStyle());
- connect(SessionManager::instance(), &SessionManager::aboutToRemoveProject,
+ connect(ProjectManager::instance(), &ProjectManager::aboutToRemoveProject,
this, &EditorConfiguration::slotAboutToRemoveProject);
}
@@ -263,7 +263,7 @@ void EditorConfiguration::setUseGlobalSettings(bool use)
const QList<Core::IEditor *> editors = Core::DocumentModel::editorsForOpenedDocuments();
for (Core::IEditor *editor : editors) {
if (auto widget = TextEditorWidget::fromEditor(editor)) {
- Project *project = SessionManager::projectForFile(editor->document()->filePath());
+ Project *project = ProjectManager::projectForFile(editor->document()->filePath());
if (project && project->editorConfiguration() == this)
switchSettings(widget);
}
@@ -399,7 +399,7 @@ TabSettings actualTabSettings(const Utils::FilePath &file,
{
if (baseTextdocument)
return baseTextdocument->tabSettings();
- if (Project *project = SessionManager::projectForFile(file))
+ if (Project *project = ProjectManager::projectForFile(file))
return project->editorConfiguration()->codeStyle()->tabSettings();
return TextEditorSettings::codeStyle()->tabSettings();
}
diff --git a/src/plugins/projectexplorer/extracompiler.cpp b/src/plugins/projectexplorer/extracompiler.cpp
index ab72d3608ff..ccf2971774c 100644
--- a/src/plugins/projectexplorer/extracompiler.cpp
+++ b/src/plugins/projectexplorer/extracompiler.cpp
@@ -5,7 +5,7 @@
#include "buildmanager.h"
#include "kitinformation.h"
-#include "session.h"
+#include "projectmanager.h"
#include "target.h"
#include <coreplugin/editormanager/editormanager.h>
@@ -17,11 +17,11 @@
#include <utils/qtcprocess.h>
#include <QDateTime>
-#include <QFutureInterface>
#include <QLoggingCategory>
#include <QThreadPool>
#include <QTimer>
+using namespace Core;
using namespace Utils;
namespace ProjectExplorer {
@@ -37,10 +37,10 @@ public:
FilePath source;
FileNameToContentsHash contents;
QDateTime compileTime;
- Core::IEditor *lastEditor = nullptr;
+ IEditor *lastEditor = nullptr;
QMetaObject::Connection activeBuildConfigConnection;
QMetaObject::Connection activeEnvironmentConnection;
- Utils::Guard lock;
+ Guard lock;
bool dirty = false;
QTimer timer;
@@ -63,16 +63,16 @@ ExtraCompiler::ExtraCompiler(const Project *project, const FilePath &source,
connect(BuildManager::instance(), &BuildManager::buildStateChanged,
this, &ExtraCompiler::onTargetsBuilt);
- connect(SessionManager::instance(), &SessionManager::projectRemoved,
+ connect(ProjectManager::instance(), &ProjectManager::projectRemoved,
this, [this](Project *project) {
if (project == d->project)
deleteLater();
});
- Core::EditorManager *editorManager = Core::EditorManager::instance();
- connect(editorManager, &Core::EditorManager::currentEditorChanged,
+ EditorManager *editorManager = EditorManager::instance();
+ connect(editorManager, &EditorManager::currentEditorChanged,
this, &ExtraCompiler::onEditorChanged);
- connect(editorManager, &Core::EditorManager::editorAboutToClose,
+ connect(editorManager, &EditorManager::editorAboutToClose,
this, &ExtraCompiler::onEditorAboutToClose);
// Use existing target files, where possible. Otherwise run the compiler.
@@ -228,12 +228,12 @@ void ExtraCompiler::onTargetsBuilt(Project *project)
});
}
-void ExtraCompiler::onEditorChanged(Core::IEditor *editor)
+void ExtraCompiler::onEditorChanged(IEditor *editor)
{
// Handle old editor
if (d->lastEditor) {
- Core::IDocument *doc = d->lastEditor->document();
- disconnect(doc, &Core::IDocument::contentsChanged,
+ IDocument *doc = d->lastEditor->document();
+ disconnect(doc, &IDocument::contentsChanged,
this, &ExtraCompiler::setDirty);
if (d->dirty) {
@@ -246,7 +246,7 @@ void ExtraCompiler::onEditorChanged(Core::IEditor *editor)
d->lastEditor = editor;
// Handle new editor
- connect(d->lastEditor->document(), &Core::IDocument::contentsChanged,
+ connect(d->lastEditor->document(), &IDocument::contentsChanged,
this, &ExtraCompiler::setDirty);
} else {
d->lastEditor = nullptr;
@@ -259,15 +259,15 @@ void ExtraCompiler::setDirty()
d->timer.start(1000);
}
-void ExtraCompiler::onEditorAboutToClose(Core::IEditor *editor)
+void ExtraCompiler::onEditorAboutToClose(IEditor *editor)
{
if (d->lastEditor != editor)
return;
// Oh no our editor is going to be closed
// get the content first
- Core::IDocument *doc = d->lastEditor->document();
- disconnect(doc, &Core::IDocument::contentsChanged,
+ IDocument *doc = d->lastEditor->document();
+ disconnect(doc, &IDocument::contentsChanged,
this, &ExtraCompiler::setDirty);
if (d->dirty) {
d->dirty = false;
@@ -278,22 +278,20 @@ void ExtraCompiler::onEditorAboutToClose(Core::IEditor *editor)
Environment ExtraCompiler::buildEnvironment() const
{
- if (Target *target = project()->activeTarget()) {
- if (BuildConfiguration *bc = target->activeBuildConfiguration()) {
- return bc->environment();
- } else {
- EnvironmentItems changes =
- EnvironmentKitAspect::environmentChanges(target->kit());
- Environment env = Environment::systemEnvironment();
- env.modify(changes);
- return env;
- }
- }
+ Target *target = project()->activeTarget();
+ if (!target)
+ return Environment::systemEnvironment();
+
+ if (BuildConfiguration *bc = target->activeBuildConfiguration())
+ return bc->environment();
- return Environment::systemEnvironment();
+ const EnvironmentItems changes = EnvironmentKitAspect::environmentChanges(target->kit());
+ Environment env = Environment::systemEnvironment();
+ env.modify(changes);
+ return env;
}
-Utils::FutureSynchronizer *ExtraCompiler::futureSynchronizer() const
+FutureSynchronizer *ExtraCompiler::futureSynchronizer() const
{
return &d->m_futureSynchronizer;
}
@@ -335,8 +333,8 @@ Tasking::TaskItem ProcessExtraCompiler::taskItemImpl(const ContentProvider &prov
{
const auto setupTask = [=](AsyncTask<FileNameToContentsHash> &async) {
async.setThreadPool(extraCompilerThreadPool());
- async.setAsyncCallData(&ProcessExtraCompiler::runInThread, this, command(),
- workingDirectory(), arguments(), provider, buildEnvironment());
+ async.setConcurrentCallData(&ProcessExtraCompiler::runInThread, this, command(),
+ workingDirectory(), arguments(), provider, buildEnvironment());
async.setFutureSynchronizer(futureSynchronizer());
};
const auto taskDone = [=](const AsyncTask<FileNameToContentsHash> &async) {
@@ -374,7 +372,7 @@ Tasks ProcessExtraCompiler::parseIssues(const QByteArray &stdErr)
return {};
}
-void ProcessExtraCompiler::runInThread(QFutureInterface<FileNameToContentsHash> &futureInterface,
+void ProcessExtraCompiler::runInThread(QPromise<FileNameToContentsHash> &promise,
const FilePath &cmd, const FilePath &workDir,
const QStringList &args, const ContentProvider &provider,
const Environment &env)
@@ -397,15 +395,15 @@ void ProcessExtraCompiler::runInThread(QFutureInterface<FileNameToContentsHash>
if (!process.waitForStarted())
return;
- while (!futureInterface.isCanceled()) {
+ while (!promise.isCanceled()) {
if (process.waitForFinished(200))
break;
}
- if (futureInterface.isCanceled())
+ if (promise.isCanceled())
return;
- futureInterface.reportResult(handleProcessFinished(&process));
+ promise.addResult(handleProcessFinished(&process));
}
} // namespace ProjectExplorer
diff --git a/src/plugins/projectexplorer/extracompiler.h b/src/plugins/projectexplorer/extracompiler.h
index 34993f39406..4f0e6da47b3 100644
--- a/src/plugins/projectexplorer/extracompiler.h
+++ b/src/plugins/projectexplorer/extracompiler.h
@@ -21,7 +21,7 @@
QT_BEGIN_NAMESPACE
template <typename T>
-class QFutureInterface;
+class QPromise;
class QThreadPool;
QT_END_NAMESPACE
@@ -106,7 +106,7 @@ protected:
private:
Utils::Tasking::TaskItem taskItemImpl(const ContentProvider &provider) final;
- void runInThread(QFutureInterface<FileNameToContentsHash> &futureInterface,
+ void runInThread(QPromise<FileNameToContentsHash> &promise,
const Utils::FilePath &cmd, const Utils::FilePath &workDir,
const QStringList &args, const ContentProvider &provider,
const Utils::Environment &env);
diff --git a/src/plugins/projectexplorer/fileinsessionfinder.cpp b/src/plugins/projectexplorer/fileinsessionfinder.cpp
index 78f75ce1d52..d8df6c74306 100644
--- a/src/plugins/projectexplorer/fileinsessionfinder.cpp
+++ b/src/plugins/projectexplorer/fileinsessionfinder.cpp
@@ -4,7 +4,7 @@
#include "fileinsessionfinder.h"
#include "project.h"
-#include "session.h"
+#include "projectmanager.h"
#include <utils/fileinprojectfinder.h>
@@ -30,12 +30,12 @@ private:
FileInSessionFinder::FileInSessionFinder()
{
- connect(SessionManager::instance(), &SessionManager::projectAdded,
+ connect(ProjectManager::instance(), &ProjectManager::projectAdded,
this, [this](const Project *p) {
invalidateFinder();
connect(p, &Project::fileListChanged, this, &FileInSessionFinder::invalidateFinder);
});
- connect(SessionManager::instance(), &SessionManager::projectRemoved,
+ connect(ProjectManager::instance(), &ProjectManager::projectRemoved,
this, [this](const Project *p) {
invalidateFinder();
p->disconnect(this);
@@ -45,11 +45,11 @@ FileInSessionFinder::FileInSessionFinder()
FilePaths FileInSessionFinder::doFindFile(const FilePath &filePath)
{
if (!m_finderIsUpToDate) {
- m_finder.setProjectDirectory(SessionManager::startupProject()
- ? SessionManager::startupProject()->projectDirectory()
+ m_finder.setProjectDirectory(ProjectManager::startupProject()
+ ? ProjectManager::startupProject()->projectDirectory()
: FilePath());
FilePaths allFiles;
- for (const Project * const p : SessionManager::projects())
+ for (const Project * const p : ProjectManager::projects())
allFiles << p->files(Project::SourceFiles);
m_finder.setProjectFiles(allFiles);
m_finderIsUpToDate = true;
diff --git a/src/plugins/projectexplorer/filesinallprojectsfind.cpp b/src/plugins/projectexplorer/filesinallprojectsfind.cpp
index 720cfbabae8..0f9d6c40e25 100644
--- a/src/plugins/projectexplorer/filesinallprojectsfind.cpp
+++ b/src/plugins/projectexplorer/filesinallprojectsfind.cpp
@@ -5,7 +5,7 @@
#include "project.h"
#include "projectexplorertr.h"
-#include "session.h"
+#include "projectmanager.h"
#include <coreplugin/editormanager/editormanager.h>
#include <utils/algorithm.h>
@@ -52,7 +52,7 @@ Utils::FileIterator *FilesInAllProjectsFind::files(const QStringList &nameFilter
const QVariant &additionalParameters) const
{
Q_UNUSED(additionalParameters)
- const QSet<FilePath> dirs = Utils::transform<QSet>(SessionManager::projects(), [](Project *p) {
+ const QSet<FilePath> dirs = Utils::transform<QSet>(ProjectManager::projects(), [](Project *p) {
return p->projectFilePath().parentDir();
});
return new SubDirFileIterator(FilePaths(dirs.constBegin(), dirs.constEnd()),
diff --git a/src/plugins/projectexplorer/gcctoolchain.cpp b/src/plugins/projectexplorer/gcctoolchain.cpp
index d2449bd69ae..f8e99c32444 100644
--- a/src/plugins/projectexplorer/gcctoolchain.cpp
+++ b/src/plugins/projectexplorer/gcctoolchain.cpp
@@ -569,7 +569,7 @@ WarningFlags GccToolChain::warningFlags(const QStringList &cflags) const
return flags;
}
-QStringList GccToolChain::includedFiles(const QStringList &flags, const QString &directoryPath) const
+FilePaths GccToolChain::includedFiles(const QStringList &flags, const FilePath &directoryPath) const
{
return ToolChain::includedFiles("-include", flags, directoryPath, PossiblyConcatenatedFlag::No);
}
@@ -1385,8 +1385,10 @@ void GccToolChainConfigWidget::setFromToolchain()
QSignalBlocker blocker(this);
auto tc = static_cast<GccToolChain *>(toolChain());
m_compilerCommand->setFilePath(tc->compilerCommand());
- m_platformCodeGenFlagsLineEdit->setText(ProcessArgs::joinArgs(tc->platformCodeGenFlags()));
- m_platformLinkerFlagsLineEdit->setText(ProcessArgs::joinArgs(tc->platformLinkerFlags()));
+ m_platformCodeGenFlagsLineEdit->setText(ProcessArgs::joinArgs(tc->platformCodeGenFlags(),
+ HostOsInfo::hostOs()));
+ m_platformLinkerFlagsLineEdit->setText(ProcessArgs::joinArgs(tc->platformLinkerFlags(),
+ HostOsInfo::hostOs()));
if (m_abiWidget) {
m_abiWidget->setAbis(tc->supportedAbis(), tc->targetAbi());
if (!m_isReadOnly && !m_compilerCommand->filePath().toString().isEmpty())
diff --git a/src/plugins/projectexplorer/gcctoolchain.h b/src/plugins/projectexplorer/gcctoolchain.h
index 55ba61e9fb1..1e6353d0a75 100644
--- a/src/plugins/projectexplorer/gcctoolchain.h
+++ b/src/plugins/projectexplorer/gcctoolchain.h
@@ -55,8 +55,8 @@ public:
Utils::LanguageExtensions languageExtensions(const QStringList &cxxflags) const override;
Utils::WarningFlags warningFlags(const QStringList &cflags) const override;
- QStringList includedFiles(const QStringList &flags,
- const QString &directoryPath) const override;
+ Utils::FilePaths includedFiles(const QStringList &flags,
+ const Utils::FilePath &directoryPath) const override;
MacroInspectionRunner createMacroInspectionRunner() const override;
BuiltInHeaderPathsRunner createBuiltInHeaderPathsRunner(const Utils::Environment &env) const override;
diff --git a/src/plugins/projectexplorer/jsonwizard/jsonfieldpage.cpp b/src/plugins/projectexplorer/jsonwizard/jsonfieldpage.cpp
index f155c5656e7..c5a82b62a8a 100644
--- a/src/plugins/projectexplorer/jsonwizard/jsonfieldpage.cpp
+++ b/src/plugins/projectexplorer/jsonwizard/jsonfieldpage.cpp
@@ -666,7 +666,6 @@ void LineEditField::setupCompletion(FancyLineEdit *lineEdit)
const QList<LocatorFilterEntry> matches = classesFilter->matchesFor(f, {});
if (!matches.isEmpty())
f.reportResults(QVector<LocatorFilterEntry>(matches.cbegin(), matches.cend()));
- f.reportFinished();
}));
}
diff --git a/src/plugins/projectexplorer/jsonwizard/jsonsummarypage.cpp b/src/plugins/projectexplorer/jsonwizard/jsonsummarypage.cpp
index 024044d7a0a..ec55e3218fd 100644
--- a/src/plugins/projectexplorer/jsonwizard/jsonsummarypage.cpp
+++ b/src/plugins/projectexplorer/jsonwizard/jsonsummarypage.cpp
@@ -8,8 +8,8 @@
#include "../projectexplorerconstants.h"
#include "../projectexplorertr.h"
#include "../projectnodes.h"
+#include "../projectmanager.h"
#include "../projecttree.h"
-#include "../session.h"
#include <coreplugin/coreconstants.h>
#include <coreplugin/iversioncontrol.h>
@@ -209,7 +209,7 @@ Node *JsonSummaryPage::findWizardContextNode(Node *contextNode) const
// Static cast from void * to avoid qobject_cast (which needs a valid object) in value().
auto project = static_cast<Project *>(m_wizard->value(Constants::PROJECT_POINTER).value<void *>());
- if (SessionManager::projects().contains(project) && project->rootProjectNode()) {
+ if (ProjectManager::projects().contains(project) && project->rootProjectNode()) {
const FilePath path = FilePath::fromVariant(m_wizard->value(Constants::PREFERRED_PROJECT_NODE_PATH));
contextNode = project->rootProjectNode()->findNode([path](const Node *n) {
return path == n->filePath();
diff --git a/src/plugins/projectexplorer/kitchooser.cpp b/src/plugins/projectexplorer/kitchooser.cpp
index 14c848da740..9e45cacd7dd 100644
--- a/src/plugins/projectexplorer/kitchooser.cpp
+++ b/src/plugins/projectexplorer/kitchooser.cpp
@@ -6,7 +6,7 @@
#include "kitmanager.h"
#include "projectexplorerconstants.h"
#include "projectexplorertr.h"
-#include "session.h"
+#include "projectmanager.h"
#include "target.h"
#include <coreplugin/icore.h>
@@ -88,7 +88,7 @@ void KitChooser::populate()
const Id lastKit = Id::fromSetting(ICore::settings()->value(lastKitKey));
bool didActivate = false;
- if (Target *target = SessionManager::startupTarget()) {
+ if (Target *target = ProjectManager::startupTarget()) {
Kit *kit = target->kit();
if (m_kitPredicate(kit)) {
QString display = Tr::tr("Kit of Active Project: %1").arg(kitText(kit));
diff --git a/src/plugins/projectexplorer/makestep.cpp b/src/plugins/projectexplorer/makestep.cpp
index 57ebc2490b3..c50e2091cb0 100644
--- a/src/plugins/projectexplorer/makestep.cpp
+++ b/src/plugins/projectexplorer/makestep.cpp
@@ -383,22 +383,6 @@ QWidget *MakeStep::createConfigWidget()
return widget;
}
-bool MakeStep::buildsTarget(const QString &target) const
-{
- return m_buildTargetsAspect->value().contains(target);
-}
-
-void MakeStep::setBuildTarget(const QString &target, bool on)
-{
- QStringList old = m_buildTargetsAspect->value();
- if (on && !old.contains(target))
- old << target;
- else if (!on && old.contains(target))
- old.removeOne(target);
-
- m_buildTargetsAspect->setValue(old);
-}
-
QStringList MakeStep::availableTargets() const
{
return m_buildTargetsAspect->allValues();
diff --git a/src/plugins/projectexplorer/makestep.h b/src/plugins/projectexplorer/makestep.h
index b08462db2ff..73c4e9b7e79 100644
--- a/src/plugins/projectexplorer/makestep.h
+++ b/src/plugins/projectexplorer/makestep.h
@@ -55,11 +55,6 @@ public:
Utils::Environment makeEnvironment() const;
- // FIXME: All unused, remove in 4.15.
- void setBuildTarget(const QString &buildTarget) { setSelectedBuildTarget(buildTarget); }
- bool buildsTarget(const QString &target) const;
- void setBuildTarget(const QString &target, bool on);
-
protected:
void supportDisablingForSubdirs() { m_disablingForSubDirsSupported = true; }
virtual QStringList displayArguments() const;
diff --git a/src/plugins/projectexplorer/miniprojecttargetselector.cpp b/src/plugins/projectexplorer/miniprojecttargetselector.cpp
index b169697cd8c..af9fdf5c50b 100644
--- a/src/plugins/projectexplorer/miniprojecttargetselector.cpp
+++ b/src/plugins/projectexplorer/miniprojecttargetselector.cpp
@@ -13,8 +13,8 @@
#include "projectexplorerconstants.h"
#include "projectexplorericons.h"
#include "projectexplorertr.h"
+#include "projectmanager.h"
#include "runconfiguration.h"
-#include "session.h"
#include "target.h"
#include <utils/algorithm.h>
@@ -253,9 +253,9 @@ public:
explicit ProjectListView(QWidget *parent = nullptr) : SelectorView(parent)
{
const auto model = new GenericModel(this);
- model->rebuild(transform<QList<QObject *>>(SessionManager::projects(),
+ model->rebuild(transform<QList<QObject *>>(ProjectManager::projects(),
[](Project *p) { return p; }));
- connect(SessionManager::instance(), &SessionManager::projectAdded,
+ connect(ProjectManager::instance(), &ProjectManager::projectAdded,
this, [this, model](Project *project) {
const GenericItem *projectItem = model->addItemForObject(project);
QFontMetrics fn(font());
@@ -264,7 +264,7 @@ public:
setOptimalWidth(width);
restoreCurrentIndex();
});
- connect(SessionManager::instance(), &SessionManager::aboutToRemoveProject,
+ connect(ProjectManager::instance(), &ProjectManager::aboutToRemoveProject,
this, [this, model](const Project *project) {
GenericItem * const item = model->itemForObject(project);
if (!item)
@@ -272,7 +272,7 @@ public:
model->destroyItem(item);
resetOptimalWidth();
});
- connect(SessionManager::instance(), &SessionManager::startupProjectChanged,
+ connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged,
this, [this, model](const Project *project) {
const GenericItem * const item = model->itemForObject(project);
if (item)
@@ -288,7 +288,7 @@ public:
this, [model](const QModelIndex &index) {
const GenericItem * const item = model->itemForIndex(index);
if (item && item->object())
- SessionManager::setStartupProject(qobject_cast<Project *>(item->object()));
+ ProjectManager::setStartupProject(qobject_cast<Project *>(item->object()));
});
}
@@ -296,7 +296,7 @@ private:
void restoreCurrentIndex()
{
const GenericItem * const itemForStartupProject
- = theModel()->itemForObject(SessionManager::startupProject());
+ = theModel()->itemForObject(ProjectManager::startupProject());
if (itemForStartupProject)
setCurrentIndex(theModel()->indexForItem(itemForStartupProject));
}
@@ -696,22 +696,22 @@ MiniProjectTargetSelector::MiniProjectTargetSelector(QAction *targetSelectorActi
m_listWidgets[RUN]->viewport()->setAttribute(Qt::WA_Hover);
// Validate state: At this point the session is still empty!
- Project *startup = SessionManager::startupProject();
+ Project *startup = ProjectManager::startupProject();
QTC_CHECK(!startup);
- QTC_CHECK(SessionManager::projects().isEmpty());
+ QTC_CHECK(ProjectManager::projects().isEmpty());
connect(m_summaryLabel, &QLabel::linkActivated,
this, &MiniProjectTargetSelector::switchToProjectsMode);
- SessionManager *sessionManager = SessionManager::instance();
- connect(sessionManager, &SessionManager::startupProjectChanged,
+ ProjectManager *sessionManager = ProjectManager::instance();
+ connect(sessionManager, &ProjectManager::startupProjectChanged,
this, &MiniProjectTargetSelector::changeStartupProject);
- connect(sessionManager, &SessionManager::projectAdded,
+ connect(sessionManager, &ProjectManager::projectAdded,
this, &MiniProjectTargetSelector::projectAdded);
- connect(sessionManager, &SessionManager::projectRemoved,
+ connect(sessionManager, &ProjectManager::projectRemoved,
this, &MiniProjectTargetSelector::projectRemoved);
- connect(sessionManager, &SessionManager::projectDisplayNameChanged,
+ connect(sessionManager, &ProjectManager::projectDisplayNameChanged,
this, &MiniProjectTargetSelector::updateActionAndSummary);
// for icon changes:
@@ -720,17 +720,17 @@ MiniProjectTargetSelector::MiniProjectTargetSelector(QAction *targetSelectorActi
connect(m_listWidgets[TARGET], &GenericListWidget::changeActiveProjectConfiguration,
this, [this](QObject *pc) {
- SessionManager::setActiveTarget(m_project, static_cast<Target *>(pc), SetActive::Cascade);
+ m_project->setActiveTarget(static_cast<Target *>(pc), SetActive::Cascade);
});
connect(m_listWidgets[BUILD], &GenericListWidget::changeActiveProjectConfiguration,
this, [this](QObject *pc) {
- SessionManager::setActiveBuildConfiguration(m_project->activeTarget(),
- static_cast<BuildConfiguration *>(pc), SetActive::Cascade);
+ m_project->activeTarget()->setActiveBuildConfiguration(
+ static_cast<BuildConfiguration *>(pc), SetActive::Cascade);
});
connect(m_listWidgets[DEPLOY], &GenericListWidget::changeActiveProjectConfiguration,
this, [this](QObject *pc) {
- SessionManager::setActiveDeployConfiguration(m_project->activeTarget(),
- static_cast<DeployConfiguration *>(pc), SetActive::Cascade);
+ m_project->activeTarget()->setActiveDeployConfiguration(
+ static_cast<DeployConfiguration *>(pc), SetActive::Cascade);
});
connect(m_listWidgets[RUN], &GenericListWidget::changeActiveProjectConfiguration,
this, [this](QObject *pc) {
@@ -881,7 +881,7 @@ void MiniProjectTargetSelector::doLayout(bool keepSize)
onlySummary = true;
} else {
if (visibleLineCount < 3) {
- if (Utils::anyOf(SessionManager::projects(), &Project::needsConfiguration))
+ if (Utils::anyOf(ProjectManager::projects(), &Project::needsConfiguration))
visibleLineCount = 3;
}
if (visibleLineCount)
@@ -1126,7 +1126,7 @@ void MiniProjectTargetSelector::removedRunConfiguration(RunConfiguration *rc, bo
void MiniProjectTargetSelector::updateProjectListVisible()
{
- int count = SessionManager::projects().size();
+ int count = ProjectManager::projects().size();
bool visible = count > 1;
m_projectListWidget->setVisible(visible);
@@ -1139,7 +1139,7 @@ void MiniProjectTargetSelector::updateProjectListVisible()
void MiniProjectTargetSelector::updateTargetListVisible()
{
int maxCount = 0;
- for (Project *p : SessionManager::projects())
+ for (Project *p : ProjectManager::projects())
maxCount = qMax(p->targets().size(), maxCount);
bool visible = maxCount > 1;
@@ -1152,7 +1152,7 @@ void MiniProjectTargetSelector::updateTargetListVisible()
void MiniProjectTargetSelector::updateBuildListVisible()
{
int maxCount = 0;
- for (Project *p : SessionManager::projects()) {
+ for (Project *p : ProjectManager::projects()) {
const QList<Target *> targets = p->targets();
for (Target *t : targets)
maxCount = qMax(t->buildConfigurations().size(), maxCount);
@@ -1168,7 +1168,7 @@ void MiniProjectTargetSelector::updateBuildListVisible()
void MiniProjectTargetSelector::updateDeployListVisible()
{
int maxCount = 0;
- for (Project *p : SessionManager::projects()) {
+ for (Project *p : ProjectManager::projects()) {
const QList<Target *> targets = p->targets();
for (Target *t : targets)
maxCount = qMax(t->deployConfigurations().size(), maxCount);
@@ -1184,7 +1184,7 @@ void MiniProjectTargetSelector::updateDeployListVisible()
void MiniProjectTargetSelector::updateRunListVisible()
{
int maxCount = 0;
- for (Project *p : SessionManager::projects()) {
+ for (Project *p : ProjectManager::projects()) {
const QList<Target *> targets = p->targets();
for (Target *t : targets)
maxCount = qMax(t->runConfigurations().size(), maxCount);
@@ -1460,10 +1460,10 @@ void MiniProjectTargetSelector::updateActionAndSummary()
? Icons::DESKTOP_DEVICE.icon()
: style()->standardIcon(QStyle::SP_ComputerIcon);
- Project *project = SessionManager::startupProject();
+ Project *project = ProjectManager::startupProject();
if (project) {
projectName = project->displayName();
- for (Project *p : SessionManager::projects()) {
+ for (Project *p : ProjectManager::projects()) {
if (p != project && p->displayName() == projectName) {
fileName = project->projectFilePath().toUserOutput();
break;
@@ -1515,7 +1515,7 @@ void MiniProjectTargetSelector::updateActionAndSummary()
void MiniProjectTargetSelector::updateSummary()
{
QString summary;
- if (Project *startupProject = SessionManager::startupProject()) {
+ if (Project *startupProject = ProjectManager::startupProject()) {
if (!m_projectListWidget->isVisibleTo(this))
summary.append(Tr::tr("Project: <b>%1</b><br/>").arg(startupProject->displayName()));
if (Target *activeTarget = startupProject->activeTarget()) {
diff --git a/src/plugins/projectexplorer/msvctoolchain.cpp b/src/plugins/projectexplorer/msvctoolchain.cpp
index d0856e29d44..1eebcc68069 100644
--- a/src/plugins/projectexplorer/msvctoolchain.cpp
+++ b/src/plugins/projectexplorer/msvctoolchain.cpp
@@ -14,12 +14,12 @@
#include <coreplugin/icore.h>
#include <utils/algorithm.h>
+#include <utils/asynctask.h>
#include <utils/environment.h>
#include <utils/hostosinfo.h>
#include <utils/pathchooser.h>
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
-#include <utils/runextensions.h>
#include <utils/temporarydirectory.h>
#include <utils/winutils.h>
@@ -747,10 +747,8 @@ static QString winExpandDelayedEnvReferences(QString in, const Utils::Environmen
return in;
}
-void MsvcToolChain::environmentModifications(
- QFutureInterface<MsvcToolChain::GenerateEnvResult> &future,
- QString vcvarsBat,
- QString varsBatArg)
+void MsvcToolChain::environmentModifications(QPromise<MsvcToolChain::GenerateEnvResult> &promise,
+ QString vcvarsBat, QString varsBatArg)
{
const Utils::Environment inEnv = Utils::Environment::systemEnvironment();
Utils::Environment outEnv;
@@ -776,7 +774,7 @@ void MsvcToolChain::environmentModifications(
}
}
- future.reportResult({error, diff});
+ promise.addResult(MsvcToolChain::GenerateEnvResult{error, diff});
}
void MsvcToolChain::initEnvModWatcher(const QFuture<GenerateEnvResult> &future)
@@ -1004,10 +1002,8 @@ bool MsvcToolChain::fromMap(const QVariantMap &data)
data.value(QLatin1String(environModsKeyC)).toList());
rescanForCompiler();
- initEnvModWatcher(Utils::runAsync(envModThreadPool(),
- &MsvcToolChain::environmentModifications,
- m_vcvarsBat,
- m_varsBatArg));
+ initEnvModWatcher(Utils::asyncRun(envModThreadPool(), &MsvcToolChain::environmentModifications,
+ m_vcvarsBat, m_varsBatArg));
const bool valid = !m_vcvarsBat.isEmpty() && targetAbi().isValid();
if (!valid)
@@ -1128,8 +1124,8 @@ WarningFlags MsvcToolChain::warningFlags(const QStringList &cflags) const
return flags;
}
-QStringList MsvcToolChain::includedFiles(const QStringList &flags,
- const QString &directoryPath) const
+FilePaths MsvcToolChain::includedFiles(const QStringList &flags,
+ const FilePath &directoryPath) const
{
return ToolChain::includedFiles("/FI", flags, directoryPath, PossiblyConcatenatedFlag::Yes);
}
@@ -1236,10 +1232,8 @@ void MsvcToolChain::setupVarsBat(const Abi &abi, const QString &varsBat, const Q
m_varsBatArg = varsBatArg;
if (!varsBat.isEmpty()) {
- initEnvModWatcher(Utils::runAsync(envModThreadPool(),
- &MsvcToolChain::environmentModifications,
- varsBat,
- varsBatArg));
+ initEnvModWatcher(Utils::asyncRun(envModThreadPool(),
+ &MsvcToolChain::environmentModifications, varsBat, varsBatArg));
}
}
diff --git a/src/plugins/projectexplorer/msvctoolchain.h b/src/plugins/projectexplorer/msvctoolchain.h
index 042bbfe38f8..68f8b9dcf19 100644
--- a/src/plugins/projectexplorer/msvctoolchain.h
+++ b/src/plugins/projectexplorer/msvctoolchain.h
@@ -55,8 +55,8 @@ public:
MacroInspectionRunner createMacroInspectionRunner() const override;
Utils::LanguageExtensions languageExtensions(const QStringList &cxxflags) const override;
Utils::WarningFlags warningFlags(const QStringList &cflags) const override;
- QStringList includedFiles(const QStringList &flags,
- const QString &directoryPath) const override;
+ Utils::FilePaths includedFiles(const QStringList &flags,
+ const Utils::FilePath &directoryPath) const override;
BuiltInHeaderPathsRunner createBuiltInHeaderPathsRunner(
const Utils::Environment &env) const override;
void addToEnvironment(Utils::Environment &env) const override;
@@ -81,6 +81,8 @@ public:
const QString &batchFile,
const QString &batchArgs,
QMap<QString, QString> &envPairs);
+ bool environmentInitialized() const { return !m_environmentModifications.isEmpty(); }
+
protected:
class WarningFlagAdder
{
@@ -111,9 +113,8 @@ protected:
std::optional<QString> error;
Utils::EnvironmentItems environmentItems;
};
- static void environmentModifications(QFutureInterface<GenerateEnvResult> &future,
- QString vcvarsBat,
- QString varsBatArg);
+ static void environmentModifications(QPromise<GenerateEnvResult> &future,
+ QString vcvarsBat, QString varsBatArg);
void initEnvModWatcher(const QFuture<GenerateEnvResult> &future);
protected:
diff --git a/src/plugins/projectexplorer/project.cpp b/src/plugins/projectexplorer/project.cpp
index fbd4f9e28fd..3b32e3c2650 100644
--- a/src/plugins/projectexplorer/project.cpp
+++ b/src/plugins/projectexplorer/project.cpp
@@ -11,15 +11,17 @@
#include "environmentaspect.h"
#include "kit.h"
#include "kitinformation.h"
+#include "msvctoolchain.h"
#include "projectexplorer.h"
#include "projectexplorerconstants.h"
#include "projectexplorertr.h"
+#include "projectmanager.h"
#include "projectnodes.h"
#include "runconfiguration.h"
#include "runconfigurationaspects.h"
-#include "session.h"
#include "target.h"
#include "taskhub.h"
+#include "toolchainmanager.h"
#include "userfileaccessor.h"
#include <coreplugin/idocument.h>
@@ -273,7 +275,7 @@ void Project::addTarget(std::unique_ptr<Target> &&t)
// check activeTarget:
if (!activeTarget())
- SessionManager::setActiveTarget(this, pointer, SetActive::Cascade);
+ setActiveTarget(pointer, SetActive::Cascade);
}
Target *Project::addTargetForDefaultKit()
@@ -309,7 +311,7 @@ bool Project::removeTarget(Target *target)
auto keep = take(d->m_targets, target);
if (target == d->m_activeTarget) {
Target *newActiveTarget = (d->m_targets.size() == 0 ? nullptr : d->m_targets.at(0).get());
- SessionManager::setActiveTarget(this, newActiveTarget, SetActive::Cascade);
+ setActiveTarget(newActiveTarget, SetActive::Cascade);
}
emit removedTarget(target);
@@ -326,7 +328,7 @@ Target *Project::activeTarget() const
return d->m_activeTarget;
}
-void Project::setActiveTarget(Target *target)
+void Project::setActiveTargetHelper(Target *target)
{
if (d->m_activeTarget == target)
return;
@@ -414,6 +416,29 @@ Target *Project::target(Kit *k) const
return findOrDefault(d->m_targets, equal(&Target::kit, k));
}
+void Project::setActiveTarget(Target *target, SetActive cascade)
+{
+ if (isShuttingDown())
+ return;
+
+ setActiveTargetHelper(target);
+
+ if (!target) // never cascade setting no target
+ return;
+
+ if (cascade != SetActive::Cascade || !ProjectManager::isProjectConfigurationCascading())
+ return;
+
+ Utils::Id kitId = target->kit()->id();
+ for (Project *otherProject : ProjectManager::projects()) {
+ if (otherProject == this)
+ continue;
+ if (Target *otherTarget = Utils::findOrDefault(otherProject->targets(),
+ [kitId](Target *t) { return t->kit()->id() == kitId; }))
+ otherProject->setActiveTargetHelper(otherTarget);
+ }
+}
+
Tasks Project::projectIssues(const Kit *k) const
{
Tasks result;
@@ -445,12 +470,12 @@ bool Project::copySteps(Target *sourceTarget, Target *newTarget)
sourceBc->buildSystem()->name()));
newTarget->addBuildConfiguration(newBc);
if (sourceTarget->activeBuildConfiguration() == sourceBc)
- SessionManager::setActiveBuildConfiguration(newTarget, newBc, SetActive::NoCascade);
+ newTarget->setActiveBuildConfiguration(newBc, SetActive::NoCascade);
}
if (!newTarget->activeBuildConfiguration()) {
QList<BuildConfiguration *> bcs = newTarget->buildConfigurations();
if (!bcs.isEmpty())
- SessionManager::setActiveBuildConfiguration(newTarget, bcs.first(), SetActive::NoCascade);
+ newTarget->setActiveBuildConfiguration(bcs.first(), SetActive::NoCascade);
}
for (DeployConfiguration *sourceDc : sourceTarget->deployConfigurations()) {
@@ -462,12 +487,12 @@ bool Project::copySteps(Target *sourceTarget, Target *newTarget)
newDc->setDisplayName(sourceDc->displayName());
newTarget->addDeployConfiguration(newDc);
if (sourceTarget->activeDeployConfiguration() == sourceDc)
- SessionManager::setActiveDeployConfiguration(newTarget, newDc, SetActive::NoCascade);
+ newTarget->setActiveDeployConfiguration(newDc, SetActive::NoCascade);
}
if (!newTarget->activeBuildConfiguration()) {
QList<DeployConfiguration *> dcs = newTarget->deployConfigurations();
if (!dcs.isEmpty())
- SessionManager::setActiveDeployConfiguration(newTarget, dcs.first(), SetActive::NoCascade);
+ newTarget->setActiveDeployConfiguration(dcs.first(), SetActive::NoCascade);
}
for (RunConfiguration *sourceRc : sourceTarget->runConfigurations()) {
@@ -846,6 +871,34 @@ const Node *Project::nodeForFilePath(const FilePath &filePath,
return nullptr;
}
+FilePaths Project::binariesForSourceFile(const FilePath &sourceFile) const
+{
+ if (!rootProjectNode())
+ return {};
+ const QList<Node *> fileNodes = rootProjectNode()->findNodes([&sourceFile](Node *n) {
+ return n->filePath() == sourceFile;
+ });
+ FilePaths binaries;
+ for (const Node * const fileNode : fileNodes) {
+ for (ProjectNode *projectNode = fileNode->parentProjectNode(); projectNode;
+ projectNode = projectNode->parentProjectNode()) {
+ if (!projectNode->isProduct())
+ continue;
+ if (projectNode->productType() == ProductType::App
+ || projectNode->productType() == ProductType::Lib) {
+ const QList<Node *> binaryNodes = projectNode->findNodes([](Node *n) {
+ return n->asFileNode() && (n->asFileNode()->fileType() == FileType::App
+ || n->asFileNode()->fileType() == FileType::Lib);
+
+ });
+ binaries << Utils::transform(binaryNodes, &Node::filePath);
+ }
+ break;
+ }
+ }
+ return binaries;
+}
+
void Project::setProjectLanguages(Context language)
{
if (d->m_projectLanguages == language)
@@ -1435,8 +1488,7 @@ void ProjectExplorerPlugin::testProject_multipleBuildConfigs()
Target * const target = theProject.project()->activeTarget();
QVERIFY(target);
QCOMPARE(target->buildConfigurations().size(), 6);
- SessionManager::setActiveBuildConfiguration(target, target->buildConfigurations().at(1),
- SetActive::Cascade);
+ target->setActiveBuildConfiguration(target->buildConfigurations().at(1), SetActive::Cascade);
BuildSystem * const bs = theProject.project()->activeTarget()->buildSystem();
QVERIFY(bs);
QCOMPARE(bs, target->activeBuildConfiguration()->buildSystem());
@@ -1452,12 +1504,94 @@ void ProjectExplorerPlugin::testProject_multipleBuildConfigs()
}
QVERIFY(!bs->isWaitingForParse() && !bs->isParsing());
- QCOMPARE(SessionManager::startupProject(), theProject.project());
+ QCOMPARE(ProjectManager::startupProject(), theProject.project());
QCOMPARE(ProjectTree::currentProject(), theProject.project());
QVERIFY(EditorManager::openEditor(projectDir.pathAppended("main.cpp")));
QVERIFY(ProjectTree::currentNode());
ProjectTree::instance()->expandAll();
- SessionManager::closeAllProjects(); // QTCREATORBUG-25655
+ ProjectManager::closeAllProjects(); // QTCREATORBUG-25655
+}
+
+void ProjectExplorerPlugin::testSourceToBinaryMapping()
+{
+ // Find suitable kit.
+ Kit * const kit = findOr(KitManager::kits(), nullptr, [](const Kit *k) {
+ return k->isValid() && ToolChainKitAspect::cxxToolChain(k);
+ });
+ if (!kit)
+ QSKIP("The test requires at least one kit with a toolchain.");
+
+ const auto toolchain = ToolChainKitAspect::cxxToolChain(kit);
+ QVERIFY(toolchain);
+ if (const auto msvcToolchain = dynamic_cast<Internal::MsvcToolChain *>(toolchain)) {
+ while (!msvcToolchain->environmentInitialized()) {
+ QSignalSpy parsingFinishedSpy(ToolChainManager::instance(),
+ &ToolChainManager::toolChainUpdated);
+ QVERIFY(parsingFinishedSpy.wait(10000));
+ }
+ }
+
+ // Copy project from qrc.
+ QTemporaryDir * const tempDir = TemporaryDirectory::masterTemporaryDirectory();
+ QVERIFY(tempDir->isValid());
+ const FilePath projectDir = FilePath::fromString(tempDir->path() + "/multi-target-project");
+ if (!projectDir.exists()) {
+ const auto result = FilePath(":/projectexplorer/testdata/multi-target-project")
+ .copyRecursively(projectDir);
+ QVERIFY2(result, qPrintable(result.error()));
+ const QFileInfoList files = QDir(projectDir.toString()).entryInfoList(QDir::Files);
+ for (const QFileInfo &f : files)
+ QFile(f.absoluteFilePath()).setPermissions(f.permissions() | QFile::WriteUser);
+ }
+
+ // Load Project.
+ QFETCH(QString, projectFileName);
+ const auto theProject = openProject(projectDir.pathAppended(projectFileName));
+ if (theProject.errorMessage().contains("text/")) {
+ QSKIP("This test requires the presence of the qmake/cmake/qbs project managers "
+ "to be fully functional");
+ }
+
+ QVERIFY2(theProject, qPrintable(theProject.errorMessage()));
+ theProject.project()->configureAsExampleProject(kit);
+ QCOMPARE(theProject.project()->targets().size(), 1);
+ Target * const target = theProject.project()->activeTarget();
+ QVERIFY(target);
+ BuildSystem * const bs = target->buildSystem();
+ QVERIFY(bs);
+ QCOMPARE(bs, target->activeBuildConfiguration()->buildSystem());
+ if (bs->isWaitingForParse() || bs->isParsing()) {
+ QSignalSpy parsingFinishedSpy(bs, &BuildSystem::parsingFinished);
+ QVERIFY(parsingFinishedSpy.wait(10000));
+ }
+ QVERIFY(!bs->isWaitingForParse() && !bs->isParsing());
+
+ if (QLatin1String(QTest::currentDataTag()) == QLatin1String("qbs")) {
+ BuildManager::buildProjectWithoutDependencies(theProject.project());
+ if (BuildManager::isBuilding()) {
+ QSignalSpy buildingFinishedSpy(BuildManager::instance(), &BuildManager::buildQueueFinished);
+ QVERIFY(buildingFinishedSpy.wait(10000));
+ }
+ QVERIFY(!BuildManager::isBuilding());
+ QSignalSpy projectUpdateSpy(theProject.project(), &Project::fileListChanged);
+ QVERIFY(projectUpdateSpy.wait(5000));
+ }
+
+ // Check mapping
+ const auto binariesForSource = [&](const QString &fileName) {
+ return theProject.project()->binariesForSourceFile(projectDir.pathAppended(fileName));
+ };
+ QCOMPARE(binariesForSource("multi-target-project-main.cpp").size(), 1);
+ QCOMPARE(binariesForSource("multi-target-project-lib.cpp").size(), 1);
+ QCOMPARE(binariesForSource("multi-target-project-shared.h").size(), 2);
+}
+
+void ProjectExplorerPlugin::testSourceToBinaryMapping_data()
+{
+ QTest::addColumn<QString>("projectFileName");
+ QTest::addRow("cmake") << "CMakeLists.txt";
+ QTest::addRow("qbs") << "multi-target-project.qbs";
+ QTest::addRow("qmake") << "multi-target-project.pro";
}
#endif // WITH_TESTS
diff --git a/src/plugins/projectexplorer/project.h b/src/plugins/projectexplorer/project.h
index ed336b5f7f3..d753048aff5 100644
--- a/src/plugins/projectexplorer/project.h
+++ b/src/plugins/projectexplorer/project.h
@@ -36,6 +36,7 @@ class ProjectImporter;
class ProjectNode;
class ProjectPrivate;
class Target;
+enum class SetActive : int;
// Documentation inside.
class PROJECTEXPLORER_EXPORT Project : public QObject
@@ -89,6 +90,8 @@ public:
Target *activeTarget() const;
Target *target(Utils::Id id) const;
Target *target(Kit *k) const;
+ void setActiveTarget(Target *target, SetActive cascade);
+
virtual Tasks projectIssues(const Kit *k) const;
static bool copySteps(Target *sourceTarget, Target *newTarget);
@@ -107,6 +110,7 @@ public:
bool isKnownFile(const Utils::FilePath &filename) const;
const Node *nodeForFilePath(const Utils::FilePath &filePath,
const NodeMatcher &extraMatcher = {}) const;
+ Utils::FilePaths binariesForSourceFile(const Utils::FilePath &sourceFile) const;
virtual QVariantMap toMap() const;
@@ -226,7 +230,7 @@ private:
void removeProjectLanguage(Utils::Id id);
void handleSubTreeChanged(FolderNode *node);
- void setActiveTarget(Target *target);
+ void setActiveTargetHelper(Target *target);
friend class ContainerNode;
ProjectPrivate *d;
diff --git a/src/plugins/projectexplorer/projectexplorer.cpp b/src/plugins/projectexplorer/projectexplorer.cpp
index f76f10175b6..6956d7862a3 100644
--- a/src/plugins/projectexplorer/projectexplorer.cpp
+++ b/src/plugins/projectexplorer/projectexplorer.cpp
@@ -8,6 +8,7 @@
#include "buildsystem.h"
#include "compileoutputwindow.h"
#include "configtaskhandler.h"
+#include "copystep.h"
#include "customexecutablerunconfiguration.h"
#include "customparserssettingspage.h"
#include "customwizard/customwizard.h"
@@ -127,6 +128,7 @@
#include <utils/qtcassert.h>
#include <utils/removefiledialog.h>
#include <utils/stringutils.h>
+#include <utils/terminalhooks.h>
#include <utils/tooltip/tooltip.h>
#include <utils/utilsicons.h>
@@ -337,7 +339,7 @@ static BuildConfiguration *currentBuildConfiguration()
static Target *activeTarget()
{
- const Project * const project = SessionManager::startupProject();
+ const Project * const project = ProjectManager::startupProject();
return project ? project->activeTarget() : nullptr;
}
@@ -667,7 +669,8 @@ public:
RemoveTaskHandler m_removeTaskHandler;
ConfigTaskHandler m_configTaskHandler{Task::compilerMissingTask(), Constants::KITS_SETTINGS_PAGE_ID};
- SessionManager m_sessionManager;
+ SessionManager m_sessionBase;
+ ProjectManager m_sessionManager;
AppOutputPane m_outputPane;
ProjectTree m_projectTree;
@@ -678,6 +681,8 @@ public:
RunRunConfigurationLocatorFilter m_runConfigurationLocatorFilter;
SwitchToRunConfigurationLocatorFilter m_switchRunConfigurationLocatorFilter;
+ CopyFileStepFactory m_copyFileStepFactory;
+ CopyDirectoryStepFactory m_copyDirectoryFactory;
ProcessStepFactory m_processStepFactory;
AllProjectsFind m_allProjectsFind;
@@ -834,33 +839,33 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er
connect(&dd->m_welcomePage, &ProjectWelcomePage::manageSessions,
dd, &ProjectExplorerPluginPrivate::showSessionManager);
- SessionManager *sessionManager = &dd->m_sessionManager;
- connect(sessionManager, &SessionManager::projectAdded,
+ ProjectManager *sessionManager = &dd->m_sessionManager;
+ connect(sessionManager, &ProjectManager::projectAdded,
this, &ProjectExplorerPlugin::fileListChanged);
- connect(sessionManager, &SessionManager::aboutToRemoveProject,
+ connect(sessionManager, &ProjectManager::aboutToRemoveProject,
dd, &ProjectExplorerPluginPrivate::invalidateProject);
- connect(sessionManager, &SessionManager::projectRemoved,
+ connect(sessionManager, &ProjectManager::projectRemoved,
this, &ProjectExplorerPlugin::fileListChanged);
- connect(sessionManager, &SessionManager::projectAdded,
+ connect(sessionManager, &ProjectManager::projectAdded,
dd, &ProjectExplorerPluginPrivate::projectAdded);
- connect(sessionManager, &SessionManager::projectRemoved,
+ connect(sessionManager, &ProjectManager::projectRemoved,
dd, &ProjectExplorerPluginPrivate::projectRemoved);
- connect(sessionManager, &SessionManager::projectDisplayNameChanged,
+ connect(sessionManager, &ProjectManager::projectDisplayNameChanged,
dd, &ProjectExplorerPluginPrivate::projectDisplayNameChanged);
- connect(sessionManager, &SessionManager::dependencyChanged,
+ connect(sessionManager, &ProjectManager::dependencyChanged,
dd, &ProjectExplorerPluginPrivate::updateActions);
- connect(sessionManager, &SessionManager::sessionLoaded,
+ connect(SessionManager::instance(), &SessionManager::sessionLoaded,
dd, &ProjectExplorerPluginPrivate::updateActions);
- connect(sessionManager, &SessionManager::sessionLoaded,
+ connect(SessionManager::instance(), &SessionManager::sessionLoaded,
dd, &ProjectExplorerPluginPrivate::updateWelcomePage);
- connect(sessionManager, &SessionManager::sessionLoaded,
+ connect(SessionManager::instance(), &SessionManager::sessionLoaded,
dd, &ProjectExplorerPluginPrivate::loadSesssionTasks);
- connect(sessionManager, &SessionManager::projectAdded, dd, [](ProjectExplorer::Project *project) {
+ connect(sessionManager, &ProjectManager::projectAdded, dd, [](ProjectExplorer::Project *project) {
dd->m_allProjectDirectoriesFilter.addDirectory(project->projectDirectory());
});
connect(sessionManager,
- &SessionManager::projectRemoved,
+ &ProjectManager::projectRemoved,
dd,
[](ProjectExplorer::Project *project) {
dd->m_allProjectDirectoriesFilter.removeDirectory(project->projectDirectory());
@@ -903,7 +908,7 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er
ICore::addPreCloseListener([]() -> bool { return coreAboutToClose(); });
- connect(SessionManager::instance(), &SessionManager::projectRemoved,
+ connect(ProjectManager::instance(), &ProjectManager::projectRemoved,
&dd->m_outputPane, &AppOutputPane::projectRemoved);
// ProjectPanelFactories
@@ -1345,7 +1350,7 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er
// without a project loaded.
connect(generatorContainer->menu(), &QMenu::aboutToShow, [menu = generatorContainer->menu()] {
menu->clear();
- if (Project * const project = SessionManager::startupProject()) {
+ if (Project * const project = ProjectManager::startupProject()) {
for (const auto &generator : project->allGenerators()) {
menu->addAction(generator.second, [project, id = generator.first] {
project->runGenerator(id);
@@ -1659,7 +1664,7 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er
dd, &ProjectExplorerPluginPrivate::savePersistentSettings);
connect(EditorManager::instance(), &EditorManager::autoSaved, this, [] {
if (!dd->m_shuttingDown && !SessionManager::loadingSession())
- SessionManager::save();
+ ProjectManager::save();
});
connect(qApp, &QApplication::applicationStateChanged, this, [](Qt::ApplicationState state) {
if (!dd->m_shuttingDown && state == Qt::ApplicationActive)
@@ -1764,20 +1769,20 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er
connect(dd->m_loadAction, &QAction::triggered,
dd, &ProjectExplorerPluginPrivate::loadAction);
connect(dd->m_buildProjectOnlyAction, &QAction::triggered, dd, [] {
- BuildManager::buildProjectWithoutDependencies(SessionManager::startupProject());
+ BuildManager::buildProjectWithoutDependencies(ProjectManager::startupProject());
});
connect(dd->m_buildAction, &QAction::triggered, dd, [] {
- BuildManager::buildProjectWithDependencies(SessionManager::startupProject());
+ BuildManager::buildProjectWithDependencies(ProjectManager::startupProject());
});
connect(dd->m_buildProjectForAllConfigsAction, &QAction::triggered, dd, [] {
- BuildManager::buildProjectWithDependencies(SessionManager::startupProject(),
+ BuildManager::buildProjectWithDependencies(ProjectManager::startupProject(),
ConfigSelection::All);
});
connect(dd->m_buildActionContextMenu, &QAction::triggered, dd, [] {
BuildManager::buildProjectWithoutDependencies(ProjectTree::currentProject());
});
connect(dd->m_buildForRunConfigAction, &QAction::triggered, dd, [] {
- const Project * const project = SessionManager::startupProject();
+ const Project * const project = ProjectManager::startupProject();
QTC_ASSERT(project, return);
const Target * const target = project->activeTarget();
QTC_ASSERT(target, return);
@@ -1792,20 +1797,20 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er
BuildManager::buildProjectWithDependencies(ProjectTree::currentProject());
});
connect(dd->m_buildSessionAction, &QAction::triggered, dd, [] {
- BuildManager::buildProjects(SessionManager::projectOrder(), ConfigSelection::Active);
+ BuildManager::buildProjects(ProjectManager::projectOrder(), ConfigSelection::Active);
});
connect(dd->m_buildSessionForAllConfigsAction, &QAction::triggered, dd, [] {
- BuildManager::buildProjects(SessionManager::projectOrder(), ConfigSelection::All);
+ BuildManager::buildProjects(ProjectManager::projectOrder(), ConfigSelection::All);
});
connect(dd->m_rebuildProjectOnlyAction, &QAction::triggered, dd, [] {
- BuildManager::rebuildProjectWithoutDependencies(SessionManager::startupProject());
+ BuildManager::rebuildProjectWithoutDependencies(ProjectManager::startupProject());
});
connect(dd->m_rebuildAction, &QAction::triggered, dd, [] {
- BuildManager::rebuildProjectWithDependencies(SessionManager::startupProject(),
+ BuildManager::rebuildProjectWithDependencies(ProjectManager::startupProject(),
ConfigSelection::Active);
});
connect(dd->m_rebuildProjectForAllConfigsAction, &QAction::triggered, dd, [] {
- BuildManager::rebuildProjectWithDependencies(SessionManager::startupProject(),
+ BuildManager::rebuildProjectWithDependencies(ProjectManager::startupProject(),
ConfigSelection::All);
});
connect(dd->m_rebuildActionContextMenu, &QAction::triggered, dd, [] {
@@ -1816,32 +1821,32 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er
ConfigSelection::Active);
});
connect(dd->m_rebuildSessionAction, &QAction::triggered, dd, [] {
- BuildManager::rebuildProjects(SessionManager::projectOrder(), ConfigSelection::Active);
+ BuildManager::rebuildProjects(ProjectManager::projectOrder(), ConfigSelection::Active);
});
connect(dd->m_rebuildSessionForAllConfigsAction, &QAction::triggered, dd, [] {
- BuildManager::rebuildProjects(SessionManager::projectOrder(), ConfigSelection::All);
+ BuildManager::rebuildProjects(ProjectManager::projectOrder(), ConfigSelection::All);
});
connect(dd->m_deployProjectOnlyAction, &QAction::triggered, dd, [] {
- BuildManager::deployProjects({SessionManager::startupProject()});
+ BuildManager::deployProjects({ProjectManager::startupProject()});
});
connect(dd->m_deployAction, &QAction::triggered, dd, [] {
- BuildManager::deployProjects(SessionManager::projectOrder(SessionManager::startupProject()));
+ BuildManager::deployProjects(ProjectManager::projectOrder(ProjectManager::startupProject()));
});
connect(dd->m_deployActionContextMenu, &QAction::triggered, dd, [] {
BuildManager::deployProjects({ProjectTree::currentProject()});
});
connect(dd->m_deploySessionAction, &QAction::triggered, dd, [] {
- BuildManager::deployProjects(SessionManager::projectOrder());
+ BuildManager::deployProjects(ProjectManager::projectOrder());
});
connect(dd->m_cleanProjectOnlyAction, &QAction::triggered, dd, [] {
- BuildManager::cleanProjectWithoutDependencies(SessionManager::startupProject());
+ BuildManager::cleanProjectWithoutDependencies(ProjectManager::startupProject());
});
connect(dd->m_cleanAction, &QAction::triggered, dd, [] {
- BuildManager::cleanProjectWithDependencies(SessionManager::startupProject(),
+ BuildManager::cleanProjectWithDependencies(ProjectManager::startupProject(),
ConfigSelection::Active);
});
connect(dd->m_cleanProjectForAllConfigsAction, &QAction::triggered, dd, [] {
- BuildManager::cleanProjectWithDependencies(SessionManager::startupProject(),
+ BuildManager::cleanProjectWithDependencies(ProjectManager::startupProject(),
ConfigSelection::All);
});
connect(dd->m_cleanActionContextMenu, &QAction::triggered, dd, [] {
@@ -1852,10 +1857,10 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er
ConfigSelection::Active);
});
connect(dd->m_cleanSessionAction, &QAction::triggered, dd, [] {
- BuildManager::cleanProjects(SessionManager::projectOrder(), ConfigSelection::Active);
+ BuildManager::cleanProjects(ProjectManager::projectOrder(), ConfigSelection::Active);
});
connect(dd->m_cleanSessionForAllConfigsAction, &QAction::triggered, dd, [] {
- BuildManager::cleanProjects(SessionManager::projectOrder(), ConfigSelection::All);
+ BuildManager::cleanProjects(ProjectManager::projectOrder(), ConfigSelection::All);
});
connect(dd->m_runAction, &QAction::triggered,
dd, [] { runStartupProject(Constants::NORMAL_RUN_MODE); });
@@ -1920,7 +1925,7 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er
connect(dd->m_setStartupProjectAction, &QAction::triggered,
dd, &ProjectExplorerPluginPrivate::handleSetStartupProject);
connect(dd->m_closeProjectFilesActionFileMenu, &QAction::triggered,
- dd, [] { dd->closeAllFilesInProject(SessionManager::projects().first()); });
+ dd, [] { dd->closeAllFilesInProject(ProjectManager::projects().first()); });
connect(dd->m_closeProjectFilesActionContextMenu, &QAction::triggered,
dd, [] { dd->closeAllFilesInProject(ProjectTree::currentProject()); });
connect(dd->m_projectTreeCollapseAllAction, &QAction::triggered,
@@ -1965,7 +1970,7 @@ bool ProjectExplorerPlugin::initialize(const QStringList &arguments, QString *er
Project::addVariablesToMacroExpander("ActiveProject:",
"Active project",
expander,
- &SessionManager::startupProject);
+ &ProjectManager::startupProject);
EnvironmentProvider::addProvider(
{"ActiveProject:BuildConfig:Env", Tr::tr("Active build environment of the active project."), [] {
if (const BuildConfiguration * const bc = activeBuildConfiguration())
@@ -2032,7 +2037,7 @@ void ProjectExplorerPluginPrivate::unloadProjectContextMenu()
void ProjectExplorerPluginPrivate::unloadOtherProjectsContextMenu()
{
if (Project *currentProject = ProjectTree::currentProject()) {
- const QList<Project *> projects = SessionManager::projects();
+ const QList<Project *> projects = ProjectManager::projects();
QTC_ASSERT(!projects.isEmpty(), return);
for (Project *p : projects) {
@@ -2045,7 +2050,7 @@ void ProjectExplorerPluginPrivate::unloadOtherProjectsContextMenu()
void ProjectExplorerPluginPrivate::handleUnloadProject()
{
- QList<Project *> projects = SessionManager::projects();
+ QList<Project *> projects = ProjectManager::projects();
QTC_ASSERT(!projects.isEmpty(), return);
ProjectExplorerPlugin::unloadProject(projects.first());
@@ -2072,7 +2077,7 @@ void ProjectExplorerPlugin::unloadProject(Project *project)
dd->addToRecentProjects(project->projectFilePath(), project->displayName());
- SessionManager::removeProject(project);
+ ProjectManager::removeProject(project);
dd->updateActions();
}
@@ -2081,7 +2086,7 @@ void ProjectExplorerPluginPrivate::closeAllProjects()
if (!EditorManager::closeAllDocuments())
return; // Action has been cancelled
- SessionManager::closeAllProjects();
+ ProjectManager::closeAllProjects();
updateActions();
ModeManager::activateMode(Core::Constants::MODE_WELCOME);
@@ -2195,7 +2200,7 @@ ExtensionSystem::IPlugin::ShutdownFlag ProjectExplorerPlugin::aboutToShutdown()
dd, &ProjectExplorerPluginPrivate::currentModeChanged);
ProjectTree::aboutToShutDown();
ToolChainManager::aboutToShutdown();
- SessionManager::closeAllProjects();
+ ProjectManager::closeAllProjects();
dd->m_shuttingDown = true;
@@ -2228,7 +2233,7 @@ void ProjectExplorerPlugin::openNewProjectDialog()
void ProjectExplorerPluginPrivate::showSessionManager()
{
- SessionManager::save();
+ ProjectManager::save();
SessionDialog sessionDialog(ICore::dialogParent());
sessionDialog.setAutoLoadSession(dd->m_projectExplorerSettings.autorestoreLastSession);
sessionDialog.exec();
@@ -2244,7 +2249,7 @@ void ProjectExplorerPluginPrivate::setStartupProject(Project *project)
{
if (!project)
return;
- SessionManager::setStartupProject(project);
+ ProjectManager::setStartupProject(project);
updateActions();
}
@@ -2255,7 +2260,7 @@ bool ProjectExplorerPluginPrivate::closeAllFilesInProject(const Project *project
Utils::erase(openFiles, [project](const DocumentModel::Entry *entry) {
return entry->pinned || !project->isKnownFile(entry->filePath());
});
- for (const Project * const otherProject : SessionManager::projects()) {
+ for (const Project * const otherProject : ProjectManager::projects()) {
if (otherProject == project)
continue;
Utils::erase(openFiles, [otherProject](const DocumentModel::Entry *entry) {
@@ -2271,10 +2276,10 @@ void ProjectExplorerPluginPrivate::savePersistentSettings()
return;
if (!SessionManager::loadingSession()) {
- for (Project *pro : SessionManager::projects())
+ for (Project *pro : ProjectManager::projects())
pro->saveSettings();
- SessionManager::save();
+ ProjectManager::save();
}
QtcSettings *s = ICore::settings();
@@ -2368,7 +2373,7 @@ ProjectExplorerPlugin::OpenProjectResult ProjectExplorerPlugin::openProject(cons
if (!project)
return result;
dd->addToRecentProjects(filePath, project->displayName());
- SessionManager::setStartupProject(project);
+ ProjectManager::setStartupProject(project);
return result;
}
@@ -2420,11 +2425,11 @@ ProjectExplorerPlugin::OpenProjectResult ProjectExplorerPlugin::openProjects(con
QTC_ASSERT(!fileName.isEmpty(), continue);
const FilePath filePath = fileName.absoluteFilePath();
- Project *found = Utils::findOrDefault(SessionManager::projects(),
+ Project *found = Utils::findOrDefault(ProjectManager::projects(),
Utils::equal(&Project::projectFilePath, filePath));
if (found) {
alreadyOpen.append(found);
- SessionManager::reportProjectLoadingProgress();
+ ProjectManager::reportProjectLoadingProgress();
continue;
}
@@ -2439,7 +2444,7 @@ ProjectExplorerPlugin::OpenProjectResult ProjectExplorerPlugin::openProjects(con
if (restoreResult == Project::RestoreResult::Ok) {
connect(pro, &Project::fileListChanged,
m_instance, &ProjectExplorerPlugin::fileListChanged);
- SessionManager::addProject(pro);
+ ProjectManager::addProject(pro);
openedPro += pro;
} else {
if (restoreResult == Project::RestoreResult::Error)
@@ -2453,7 +2458,7 @@ ProjectExplorerPlugin::OpenProjectResult ProjectExplorerPlugin::openProjects(con
.arg(mt.name()));
}
if (filePaths.size() > 1)
- SessionManager::reportProjectLoadingProgress();
+ ProjectManager::reportProjectLoadingProgress();
}
dd->updateActions();
@@ -2594,7 +2599,7 @@ void ProjectExplorerPluginPrivate::restoreSession()
} // !arguments.isEmpty()
// Restore latest session or what was passed on the command line
- SessionManager::loadSession(!dd->m_sessionToRestoreAtStartup.isEmpty()
+ ProjectManager::loadSession(!dd->m_sessionToRestoreAtStartup.isEmpty()
? dd->m_sessionToRestoreAtStartup : QString(), true);
// update welcome page
@@ -2732,7 +2737,7 @@ RecentProjectsEntries ProjectExplorerPluginPrivate::recentProjects() const
void ProjectExplorerPluginPrivate::updateActions()
{
- const Project *const project = SessionManager::startupProject();
+ const Project *const project = ProjectManager::startupProject();
const Project *const currentProject = ProjectTree::currentProject(); // for context menu actions
const QPair<bool, QString> buildActionState = buildSettingsEnabled(project);
@@ -2743,10 +2748,10 @@ void ProjectExplorerPluginPrivate::updateActions()
const QString projectName = project ? project->displayName() : QString();
const QString projectNameContextMenu = currentProject ? currentProject->displayName() : QString();
- m_unloadAction->setParameter(SessionManager::projects().size() == 1 ? projectName : QString());
+ m_unloadAction->setParameter(ProjectManager::projects().size() == 1 ? projectName : QString());
m_unloadActionContextMenu->setParameter(projectNameContextMenu);
m_unloadOthersActionContextMenu->setParameter(projectNameContextMenu);
- m_closeProjectFilesActionFileMenu->setParameter(SessionManager::projects().size() == 1
+ m_closeProjectFilesActionFileMenu->setParameter(ProjectManager::projects().size() == 1
? projectName : QString());
m_closeProjectFilesActionContextMenu->setParameter(projectNameContextMenu);
@@ -2791,7 +2796,7 @@ void ProjectExplorerPluginPrivate::updateActions()
m_setStartupProjectAction->setParameter(projectNameContextMenu);
m_setStartupProjectAction->setVisible(currentProject != project);
- const bool hasDependencies = SessionManager::projectOrder(currentProject).size() > 1;
+ const bool hasDependencies = ProjectManager::projectOrder(currentProject).size() > 1;
m_buildActionContextMenu->setVisible(hasDependencies);
m_rebuildActionContextMenu->setVisible(hasDependencies);
m_cleanActionContextMenu->setVisible(hasDependencies);
@@ -2818,17 +2823,17 @@ void ProjectExplorerPluginPrivate::updateActions()
m_cleanProjectOnlyAction->setToolTip(buildActionState.second);
// Session actions
- m_closeAllProjects->setEnabled(SessionManager::hasProjects());
- m_unloadAction->setEnabled(SessionManager::projects().size() <= 1);
- m_unloadAction->setEnabled(SessionManager::projects().size() == 1);
- m_unloadActionContextMenu->setEnabled(SessionManager::hasProjects());
- m_unloadOthersActionContextMenu->setEnabled(SessionManager::projects().size() >= 2);
- m_closeProjectFilesActionFileMenu->setEnabled(SessionManager::projects().size() == 1);
- m_closeProjectFilesActionContextMenu->setEnabled(SessionManager::hasProjects());
+ m_closeAllProjects->setEnabled(ProjectManager::hasProjects());
+ m_unloadAction->setEnabled(ProjectManager::projects().size() <= 1);
+ m_unloadAction->setEnabled(ProjectManager::projects().size() == 1);
+ m_unloadActionContextMenu->setEnabled(ProjectManager::hasProjects());
+ m_unloadOthersActionContextMenu->setEnabled(ProjectManager::projects().size() >= 2);
+ m_closeProjectFilesActionFileMenu->setEnabled(ProjectManager::projects().size() == 1);
+ m_closeProjectFilesActionContextMenu->setEnabled(ProjectManager::hasProjects());
ActionContainer *aci =
ActionManager::actionContainer(Constants::M_UNLOADPROJECTS);
- aci->menu()->menuAction()->setEnabled(SessionManager::hasProjects());
+ aci->menu()->menuAction()->setEnabled(ProjectManager::hasProjects());
m_buildSessionAction->setEnabled(buildSessionState.first);
m_buildSessionForAllConfigsAction->setEnabled(buildSessionState.first);
@@ -2846,7 +2851,7 @@ void ProjectExplorerPluginPrivate::updateActions()
m_cancelBuildAction->setEnabled(BuildManager::isBuilding());
- const bool hasProjects = SessionManager::hasProjects();
+ const bool hasProjects = ProjectManager::hasProjects();
m_projectSelectorAction->setEnabled(hasProjects);
m_projectSelectorActionMenu->setEnabled(hasProjects);
m_projectSelectorActionQuick->setEnabled(hasProjects);
@@ -2967,7 +2972,7 @@ void ProjectExplorerPluginPrivate::runProjectContextMenu(RunConfiguration *rc)
static bool hasBuildSettings(const Project *pro)
{
- return Utils::anyOf(SessionManager::projectOrder(pro), [](const Project *project) {
+ return Utils::anyOf(ProjectManager::projectOrder(pro), [](const Project *project) {
return project
&& project->activeTarget()
&& project->activeTarget()->activeBuildConfiguration();
@@ -2979,7 +2984,7 @@ static QPair<bool, QString> subprojectEnabledState(const Project *pro)
QPair<bool, QString> result;
result.first = true;
- const QList<Project *> &projects = SessionManager::projectOrder(pro);
+ const QList<Project *> &projects = ProjectManager::projectOrder(pro);
for (const Project *project : projects) {
if (project && project->activeTarget()
&& project->activeTarget()->activeBuildConfiguration()
@@ -3021,7 +3026,7 @@ QPair<bool, QString> ProjectExplorerPluginPrivate::buildSettingsEnabledForSessio
{
QPair<bool, QString> result;
result.first = true;
- if (!SessionManager::hasProjects()) {
+ if (!ProjectManager::hasProjects()) {
result.first = false;
result.second = Tr::tr("No project loaded.");
} else if (BuildManager::isBuilding()) {
@@ -3078,7 +3083,7 @@ void ProjectExplorerPlugin::handleCommandLineArguments(const QStringList &argume
static bool hasDeploySettings(Project *pro)
{
- return Utils::anyOf(SessionManager::projectOrder(pro), [](Project *project) {
+ return Utils::anyOf(ProjectManager::projectOrder(pro), [](Project *project) {
return project->activeTarget()
&& project->activeTarget()->activeDeployConfiguration();
});
@@ -3096,7 +3101,7 @@ void ProjectExplorerPlugin::runProject(Project *pro, Id mode, const bool forceSk
void ProjectExplorerPlugin::runStartupProject(Id runMode, bool forceSkipDeploy)
{
- runProject(SessionManager::startupProject(), runMode, forceSkipDeploy);
+ runProject(ProjectManager::startupProject(), runMode, forceSkipDeploy);
}
void ProjectExplorerPlugin::runRunConfiguration(RunConfiguration *rc,
@@ -3157,7 +3162,7 @@ void ProjectExplorerPluginPrivate::projectAdded(Project *pro)
void ProjectExplorerPluginPrivate::projectRemoved(Project *pro)
{
Q_UNUSED(pro)
- m_projectsMode.setEnabled(SessionManager::hasProjects());
+ m_projectsMode.setEnabled(ProjectManager::hasProjects());
}
void ProjectExplorerPluginPrivate::projectDisplayNameChanged(Project *pro)
@@ -3168,7 +3173,7 @@ void ProjectExplorerPluginPrivate::projectDisplayNameChanged(Project *pro)
void ProjectExplorerPluginPrivate::updateDeployActions()
{
- Project *project = SessionManager::startupProject();
+ Project *project = ProjectManager::startupProject();
bool enableDeployActions = project
&& !BuildManager::isBuilding(project)
@@ -3187,7 +3192,7 @@ void ProjectExplorerPluginPrivate::updateDeployActions()
enableDeployActionsContextMenu = false;
}
- bool hasProjects = SessionManager::hasProjects();
+ bool hasProjects = ProjectManager::hasProjects();
m_deployAction->setEnabled(enableDeployActions);
@@ -3203,7 +3208,7 @@ void ProjectExplorerPluginPrivate::updateDeployActions()
&& !project->activeTarget()->activeBuildConfiguration()->isEnabled();
};
- if (Utils::anyOf(SessionManager::projectOrder(nullptr), hasDisabledBuildConfiguration))
+ if (Utils::anyOf(ProjectManager::projectOrder(nullptr), hasDisabledBuildConfiguration))
enableDeploySessionAction = false;
}
if (!hasProjects || !hasDeploySettings(nullptr) || BuildManager::isBuilding())
@@ -3215,7 +3220,7 @@ void ProjectExplorerPluginPrivate::updateDeployActions()
bool ProjectExplorerPlugin::canRunStartupProject(Id runMode, QString *whyNot)
{
- Project *project = SessionManager::startupProject();
+ Project *project = ProjectManager::startupProject();
if (!project) {
if (whyNot)
*whyNot = Tr::tr("No active project.");
@@ -3320,7 +3325,7 @@ void ProjectExplorerPluginPrivate::updateUnloadProjectMenu()
ActionContainer *aci = ActionManager::actionContainer(Constants::M_UNLOADPROJECTS);
QMenu *menu = aci->menu();
menu->clear();
- for (Project *project : SessionManager::projects()) {
+ for (Project *project : ProjectManager::projects()) {
QAction *action = menu->addAction(Tr::tr("Close Project \"%1\"").arg(project->displayName()));
connect(action, &QAction::triggered,
[project] { ProjectExplorerPlugin::unloadProject(project); } );
@@ -3795,6 +3800,13 @@ void ProjectExplorerPluginPrivate::showInFileSystemPane()
Core::FileUtils::showInFileSystemView(currentNode->filePath());
}
+static BuildConfiguration *activeBuildConfiguration(Project *project)
+{
+ if (!project || !project->activeTarget() || !project->activeTarget()->activeBuildConfiguration())
+ return {};
+ return project->activeTarget()->activeBuildConfiguration();
+}
+
void ProjectExplorerPluginPrivate::openTerminalHere(const EnvironmentGetter &env)
{
const Node *currentNode = ProjectTree::currentNode();
@@ -3804,7 +3816,29 @@ void ProjectExplorerPluginPrivate::openTerminalHere(const EnvironmentGetter &env
if (!environment)
return;
- Core::FileUtils::openTerminal(currentNode->directory(), environment.value());
+ BuildConfiguration *bc = activeBuildConfiguration(ProjectTree::projectForNode(currentNode));
+ if (!bc) {
+ Terminal::Hooks::instance().openTerminal({{}, currentNode->directory(), environment});
+ return;
+ }
+
+ IDeviceConstPtr buildDevice = BuildDeviceKitAspect::device(bc->target()->kit());
+
+ if (!buildDevice)
+ return;
+
+ FilePath workingDir = currentNode->directory();
+ if (!buildDevice->filePath(workingDir.path()).exists()
+ && !buildDevice->ensureReachable(workingDir))
+ workingDir.clear();
+
+ const FilePath shell = Terminal::defaultShellForDevice(buildDevice->rootPath());
+
+ if (!shell.isEmpty() && buildDevice->rootPath().needsDevice()) {
+ Terminal::Hooks::instance().openTerminal({CommandLine{shell, {}}, workingDir, environment});
+ } else {
+ Terminal::Hooks::instance().openTerminal({std::nullopt, workingDir, environment});
+ }
}
void ProjectExplorerPluginPrivate::openTerminalHereWithRunEnv()
@@ -3825,9 +3859,21 @@ void ProjectExplorerPluginPrivate::openTerminalHereWithRunEnv()
if (!device)
device = DeviceKitAspect::device(target->kit());
QTC_ASSERT(device && device->canOpenTerminal(), return);
- const FilePath workingDir = device->type() == Constants::DESKTOP_DEVICE_TYPE
- ? currentNode->directory() : runnable.workingDirectory;
- device->openTerminal(runnable.environment, workingDir);
+
+ FilePath workingDir = device->type() == Constants::DESKTOP_DEVICE_TYPE
+ ? currentNode->directory()
+ : runnable.workingDirectory;
+
+ if (!device->filePath(workingDir.path()).exists() && !device->ensureReachable(workingDir))
+ workingDir.clear();
+
+ const FilePath shell = Terminal::defaultShellForDevice(device->rootPath());
+ if (!shell.isEmpty() && device->rootPath().needsDevice()) {
+ Terminal::Hooks::instance().openTerminal(
+ {CommandLine{shell, {}}, workingDir, runnable.environment});
+ } else {
+ Terminal::Hooks::instance().openTerminal({std::nullopt, workingDir, runnable.environment});
+ }
}
void ProjectExplorerPluginPrivate::removeFile()
@@ -4090,7 +4136,7 @@ void ProjectExplorerPluginPrivate::updateSessionMenu()
void ProjectExplorerPluginPrivate::setSession(QAction *action)
{
- SessionManager::loadSession(action->data().toString());
+ ProjectManager::loadSession(action->data().toString());
}
void ProjectExplorerPlugin::setProjectExplorerSettings(const ProjectExplorerSettings &pes)
@@ -4348,7 +4394,7 @@ void AllProjectFilesFilter::restoreState(const QJsonObject &object)
RunConfigurationLocatorFilter::RunConfigurationLocatorFilter()
{
- connect(SessionManager::instance(), &SessionManager::startupProjectChanged,
+ connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged,
this, &RunConfigurationLocatorFilter::targetListUpdated);
targetListUpdated();
@@ -4357,7 +4403,7 @@ RunConfigurationLocatorFilter::RunConfigurationLocatorFilter()
void RunConfigurationLocatorFilter::prepareSearch(const QString &entry)
{
m_result.clear();
- const Target *target = SessionManager::startupTarget();
+ const Target *target = ProjectManager::startupTarget();
if (!target)
return;
for (auto rc : target->runConfigurations()) {
@@ -4376,12 +4422,12 @@ QList<Core::LocatorFilterEntry> RunConfigurationLocatorFilter::matchesFor(
void RunConfigurationLocatorFilter::targetListUpdated()
{
- setEnabled(SessionManager::startupProject()); // at least one project opened
+ setEnabled(ProjectManager::startupProject()); // at least one project opened
}
static RunConfiguration *runConfigurationForDisplayName(const QString &displayName)
{
- const Project *project = SessionManager::instance()->startupProject();
+ const Project *project = ProjectManager::instance()->startupProject();
if (!project)
return nullptr;
const QList<RunConfiguration *> runconfigs = project->activeTarget()->runConfigurations();
@@ -4434,7 +4480,7 @@ void SwitchToRunConfigurationLocatorFilter::accept(const LocatorFilterEntry &sel
if (!toSwitchTo)
return;
- SessionManager::startupTarget()->setActiveRunConfiguration(toSwitchTo);
+ ProjectManager::startupTarget()->setActiveRunConfiguration(toSwitchTo);
QTimer::singleShot(200, this, [displayName = selection.displayName] {
if (auto ks = ICore::mainWindow()->findChild<QWidget *>("KitSelector.Button")) {
Utils::ToolTip::show(ks->mapToGlobal(QPoint{25, 25}),
diff --git a/src/plugins/projectexplorer/projectexplorer.h b/src/plugins/projectexplorer/projectexplorer.h
index baa8bb83a6b..e5f91bd334d 100644
--- a/src/plugins/projectexplorer/projectexplorer.h
+++ b/src/plugins/projectexplorer/projectexplorer.h
@@ -261,6 +261,9 @@ private slots:
void testProject_projectTree();
void testProject_multipleBuildConfigs();
+ void testSourceToBinaryMapping();
+ void testSourceToBinaryMapping_data();
+
void testSessionSwitch();
#endif // WITH_TESTS
};
diff --git a/src/plugins/projectexplorer/projectexplorer.qbs b/src/plugins/projectexplorer/projectexplorer.qbs
index e65cc1bdc3f..23870334857 100644
--- a/src/plugins/projectexplorer/projectexplorer.qbs
+++ b/src/plugins/projectexplorer/projectexplorer.qbs
@@ -45,6 +45,7 @@ Project {
"codestylesettingspropertiespage.cpp", "codestylesettingspropertiespage.h",
"compileoutputwindow.cpp", "compileoutputwindow.h",
"configtaskhandler.cpp", "configtaskhandler.h",
+ "copystep.cpp", "copystep.h",
"copytaskhandler.cpp", "copytaskhandler.h",
"currentprojectfilter.cpp", "currentprojectfilter.h",
"currentprojectfind.cpp", "currentprojectfind.h",
@@ -116,7 +117,7 @@ Project {
"projectfilewizardextension.cpp", "projectfilewizardextension.h",
"projectimporter.cpp", "projectimporter.h",
"projectmacro.cpp", "projectmacro.h",
- "projectmanager.h",
+ "projectmanager.cpp", "projectmanager.h",
"projectmodels.cpp", "projectmodels.h",
"projectnodes.cpp", "projectnodes.h",
"projectpanelfactory.cpp", "projectpanelfactory.h",
@@ -134,7 +135,7 @@ Project {
"runsettingspropertiespage.cpp", "runsettingspropertiespage.h",
"sanitizerparser.cpp", "sanitizerparser.h",
"selectablefilesmodel.cpp", "selectablefilesmodel.h",
- "session.cpp", "session.h",
+ "session.cpp", "session.h", "session_p.h",
"sessionmodel.cpp", "sessionmodel.h",
"sessionview.cpp", "sessionview.h",
"sessiondialog.cpp", "sessiondialog.h",
@@ -226,8 +227,7 @@ Project {
"idevicefactory.cpp", "idevicefactory.h",
"idevicefwd.h",
"idevicewidget.h",
- "localprocesslist.cpp", "localprocesslist.h",
- "sshdeviceprocesslist.cpp", "sshdeviceprocesslist.h",
+ "processlist.cpp", "processlist.h",
"sshparameters.cpp", "sshparameters.h",
"sshsettings.cpp", "sshsettings.h",
"sshsettingspage.cpp", "sshsettingspage.h",
@@ -250,9 +250,7 @@ Project {
]
}
- Group {
- name: "Tests"
- condition: qtc.testsEnabled
+ QtcTestFiles {
files: ["outputparser_test.h", "outputparser_test.cpp"]
}
diff --git a/src/plugins/projectexplorer/projectexplorer.qrc b/src/plugins/projectexplorer/projectexplorer.qrc
index ececb0854b3..0cc88e3331a 100644
--- a/src/plugins/projectexplorer/projectexplorer.qrc
+++ b/src/plugins/projectexplorer/projectexplorer.qrc
@@ -86,5 +86,13 @@
<file>images/[email protected]</file>
<file>images/importasproject.png</file>
<file>images/[email protected]</file>
+ <file>testdata/multi-target-project/CMakeLists.txt</file>
+ <file>testdata/multi-target-project/multi-target-project-app.pro</file>
+ <file>testdata/multi-target-project/multi-target-project-lib.cpp</file>
+ <file>testdata/multi-target-project/multi-target-project-lib.pro</file>
+ <file>testdata/multi-target-project/multi-target-project-main.cpp</file>
+ <file>testdata/multi-target-project/multi-target-project-shared.h</file>
+ <file>testdata/multi-target-project/multi-target-project.pro</file>
+ <file>testdata/multi-target-project/multi-target-project.qbs</file>
</qresource>
</RCC>
diff --git a/src/plugins/projectexplorer/projectfilewizardextension.cpp b/src/plugins/projectexplorer/projectfilewizardextension.cpp
index 53d3eb15781..134db6ea484 100644
--- a/src/plugins/projectexplorer/projectfilewizardextension.cpp
+++ b/src/plugins/projectexplorer/projectfilewizardextension.cpp
@@ -7,10 +7,10 @@
#include "project.h"
#include "projectexplorerconstants.h"
#include "projectexplorertr.h"
+#include "projectmanager.h"
#include "projectnodes.h"
#include "projecttree.h"
#include "projectwizardpage.h"
-#include "session.h"
#include <coreplugin/icore.h>
@@ -27,7 +27,6 @@
#include <utils/stringutils.h>
#include <QDebug>
-#include <QFileInfo>
#include <QMessageBox>
#include <QPointer>
#include <QTextCursor>
@@ -134,7 +133,7 @@ Node *ProjectFileWizardExtension::findWizardContextNode(Node *contextNode, Proje
const FilePath &path)
{
if (contextNode && !ProjectTree::hasNode(contextNode)) {
- if (SessionManager::projects().contains(project) && project->rootProjectNode()) {
+ if (ProjectManager::projects().contains(project) && project->rootProjectNode()) {
contextNode = project->rootProjectNode()->findNode([path](const Node *n) {
return path == n->filePath();
});
diff --git a/src/plugins/projectexplorer/projectmanager.cpp b/src/plugins/projectexplorer/projectmanager.cpp
new file mode 100644
index 00000000000..09f6ad2cf41
--- /dev/null
+++ b/src/plugins/projectexplorer/projectmanager.cpp
@@ -0,0 +1,931 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "projectmanager.h"
+
+#include "session_p.h"
+#include "session.h"
+
+#include "buildconfiguration.h"
+#include "editorconfiguration.h"
+#include "project.h"
+#include "projectexplorer.h"
+#include "projectexplorerconstants.h"
+#include "projectexplorertr.h"
+#include "projectmanager.h"
+#include "projectnodes.h"
+#include "target.h"
+
+#include <coreplugin/coreconstants.h>
+#include <coreplugin/editormanager/editormanager.h>
+#include <coreplugin/foldernavigationwidget.h>
+#include <coreplugin/icore.h>
+#include <coreplugin/idocument.h>
+#include <coreplugin/imode.h>
+#include <coreplugin/modemanager.h>
+#include <coreplugin/progressmanager/progressmanager.h>
+
+#include <texteditor/texteditor.h>
+
+#include <utils/algorithm.h>
+#include <utils/filepath.h>
+#include <utils/qtcassert.h>
+#include <utils/stylehelper.h>
+#include <utils/qtcassert.h>
+
+#include <QDebug>
+#include <QMessageBox>
+#include <QPushButton>
+
+#ifdef WITH_TESTS
+#include <QTemporaryFile>
+#include <QTest>
+#include <vector>
+#endif
+
+using namespace Core;
+using namespace Utils;
+using namespace ProjectExplorer::Internal;
+
+namespace ProjectExplorer {
+
+const char DEFAULT_SESSION[] = "default";
+
+class ProjectManagerPrivate
+{
+public:
+ void restoreDependencies(const PersistentSettingsReader &reader);
+ void restoreStartupProject(const PersistentSettingsReader &reader);
+ void restoreProjects(const FilePaths &fileList);
+ void askUserAboutFailedProjects();
+
+ bool recursiveDependencyCheck(const FilePath &newDep, const FilePath &checkDep) const;
+ FilePaths dependencies(const FilePath &proName) const;
+ FilePaths dependenciesOrder() const;
+ void dependencies(const FilePath &proName, FilePaths &result) const;
+
+ static QString windowTitleAddition(const FilePath &filePath);
+ static QString sessionTitle(const FilePath &filePath);
+
+ bool hasProjects() const { return !m_projects.isEmpty(); }
+
+ bool m_casadeSetActive = false;
+
+ Project *m_startupProject = nullptr;
+ QList<Project *> m_projects;
+ FilePaths m_failedProjects;
+ QMap<FilePath, FilePaths> m_depMap;
+
+private:
+ static QString locationInProject(const FilePath &filePath);
+};
+
+static ProjectManager *m_instance = nullptr;
+static ProjectManagerPrivate *d = nullptr;
+
+static QString projectFolderId(Project *pro)
+{
+ return pro->projectFilePath().toString();
+}
+
+const int PROJECT_SORT_VALUE = 100;
+
+ProjectManager::ProjectManager()
+{
+ m_instance = this;
+ d = new ProjectManagerPrivate;
+
+ connect(EditorManager::instance(), &EditorManager::editorCreated,
+ this, &ProjectManager::configureEditor);
+ connect(this, &ProjectManager::projectAdded,
+ EditorManager::instance(), &EditorManager::updateWindowTitles);
+ connect(this, &ProjectManager::projectRemoved,
+ EditorManager::instance(), &EditorManager::updateWindowTitles);
+ connect(this, &ProjectManager::projectDisplayNameChanged,
+ EditorManager::instance(), &EditorManager::updateWindowTitles);
+
+ EditorManager::setWindowTitleAdditionHandler(&ProjectManagerPrivate::windowTitleAddition);
+ EditorManager::setSessionTitleHandler(&ProjectManagerPrivate::sessionTitle);
+}
+
+ProjectManager::~ProjectManager()
+{
+ EditorManager::setWindowTitleAdditionHandler({});
+ EditorManager::setSessionTitleHandler({});
+ delete d;
+ d = nullptr;
+}
+
+ProjectManager *ProjectManager::instance()
+{
+ return m_instance;
+}
+
+bool ProjectManagerPrivate::recursiveDependencyCheck(const FilePath &newDep,
+ const FilePath &checkDep) const
+{
+ if (newDep == checkDep)
+ return false;
+
+ const FilePaths depList = m_depMap.value(checkDep);
+ for (const FilePath &dependency : depList) {
+ if (!recursiveDependencyCheck(newDep, dependency))
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * The dependency management exposes an interface based on projects, but
+ * is internally purely string based. This is suboptimal. Probably it would be
+ * nicer to map the filenames to projects on load and only map it back to
+ * filenames when saving.
+ */
+
+QList<Project *> ProjectManager::dependencies(const Project *project)
+{
+ const FilePath proName = project->projectFilePath();
+ const FilePaths proDeps = d->m_depMap.value(proName);
+
+ QList<Project *> projects;
+ for (const FilePath &dep : proDeps) {
+ Project *pro = Utils::findOrDefault(d->m_projects, [&dep](Project *p) {
+ return p->projectFilePath() == dep;
+ });
+ if (pro)
+ projects += pro;
+ }
+
+ return projects;
+}
+
+bool ProjectManager::hasDependency(const Project *project, const Project *depProject)
+{
+ const FilePath proName = project->projectFilePath();
+ const FilePath depName = depProject->projectFilePath();
+
+ const FilePaths proDeps = d->m_depMap.value(proName);
+ return proDeps.contains(depName);
+}
+
+bool ProjectManager::canAddDependency(const Project *project, const Project *depProject)
+{
+ const FilePath newDep = project->projectFilePath();
+ const FilePath checkDep = depProject->projectFilePath();
+
+ return d->recursiveDependencyCheck(newDep, checkDep);
+}
+
+bool ProjectManager::addDependency(Project *project, Project *depProject)
+{
+ const FilePath proName = project->projectFilePath();
+ const FilePath depName = depProject->projectFilePath();
+
+ // check if this dependency is valid
+ if (!d->recursiveDependencyCheck(proName, depName))
+ return false;
+
+ FilePaths proDeps = d->m_depMap.value(proName);
+ if (!proDeps.contains(depName)) {
+ proDeps.append(depName);
+ d->m_depMap[proName] = proDeps;
+ }
+ emit m_instance->dependencyChanged(project, depProject);
+
+ return true;
+}
+
+void ProjectManager::removeDependency(Project *project, Project *depProject)
+{
+ const FilePath proName = project->projectFilePath();
+ const FilePath depName = depProject->projectFilePath();
+
+ FilePaths proDeps = d->m_depMap.value(proName);
+ proDeps.removeAll(depName);
+ if (proDeps.isEmpty())
+ d->m_depMap.remove(proName);
+ else
+ d->m_depMap[proName] = proDeps;
+ emit m_instance->dependencyChanged(project, depProject);
+}
+
+bool ProjectManager::isProjectConfigurationCascading()
+{
+ return d->m_casadeSetActive;
+}
+
+void ProjectManager::setProjectConfigurationCascading(bool b)
+{
+ d->m_casadeSetActive = b;
+ SessionManager::markSessionFileDirty();
+}
+
+void ProjectManager::setStartupProject(Project *startupProject)
+{
+ QTC_ASSERT((!startupProject && d->m_projects.isEmpty())
+ || (startupProject && d->m_projects.contains(startupProject)), return);
+
+ if (d->m_startupProject == startupProject)
+ return;
+
+ d->m_startupProject = startupProject;
+ if (d->m_startupProject && d->m_startupProject->needsConfiguration()) {
+ ModeManager::activateMode(Constants::MODE_SESSION);
+ ModeManager::setFocusToCurrentMode();
+ }
+ FolderNavigationWidgetFactory::setFallbackSyncFilePath(
+ startupProject ? startupProject->projectFilePath().parentDir() : FilePath());
+ emit m_instance->startupProjectChanged(startupProject);
+}
+
+Project *ProjectManager::startupProject()
+{
+ return d->m_startupProject;
+}
+
+Target *ProjectManager::startupTarget()
+{
+ return d->m_startupProject ? d->m_startupProject->activeTarget() : nullptr;
+}
+
+BuildSystem *ProjectManager::startupBuildSystem()
+{
+ Target *t = startupTarget();
+ return t ? t->buildSystem() : nullptr;
+}
+
+/*!
+ * Returns the RunConfiguration of the currently active target
+ * of the startup project, if such exists, or \c nullptr otherwise.
+ */
+
+
+RunConfiguration *ProjectManager::startupRunConfiguration()
+{
+ Target *t = startupTarget();
+ return t ? t->activeRunConfiguration() : nullptr;
+}
+
+void ProjectManager::addProject(Project *pro)
+{
+ QTC_ASSERT(pro, return);
+ QTC_CHECK(!pro->displayName().isEmpty());
+ QTC_CHECK(pro->id().isValid());
+
+ sb_d->m_virginSession = false;
+ QTC_ASSERT(!d->m_projects.contains(pro), return);
+
+ d->m_projects.append(pro);
+
+ connect(pro, &Project::displayNameChanged,
+ m_instance, [pro]() { emit m_instance->projectDisplayNameChanged(pro); });
+
+ emit m_instance->projectAdded(pro);
+ const auto updateFolderNavigation = [pro] {
+ // destructing projects might trigger changes, so check if the project is actually there
+ if (QTC_GUARD(d->m_projects.contains(pro))) {
+ const QIcon icon = pro->rootProjectNode() ? pro->rootProjectNode()->icon() : QIcon();
+ FolderNavigationWidgetFactory::insertRootDirectory({projectFolderId(pro),
+ PROJECT_SORT_VALUE,
+ pro->displayName(),
+ pro->projectFilePath().parentDir(),
+ icon});
+ }
+ };
+ updateFolderNavigation();
+ configureEditors(pro);
+ connect(pro, &Project::fileListChanged, m_instance, [pro, updateFolderNavigation]() {
+ configureEditors(pro);
+ updateFolderNavigation(); // update icon
+ });
+ connect(pro, &Project::displayNameChanged, m_instance, updateFolderNavigation);
+
+ if (!startupProject())
+ setStartupProject(pro);
+}
+
+void ProjectManager::removeProject(Project *project)
+{
+ sb_d->m_virginSession = false;
+ QTC_ASSERT(project, return);
+ removeProjects({project});
+}
+
+bool ProjectManager::save()
+{
+ emit SessionManager::instance()->aboutToSaveSession();
+
+ const FilePath filePath = SessionManager::sessionNameToFileName(sb_d->m_sessionName);
+ QVariantMap data;
+
+ // See the explanation at loadSession() for how we handle the implicit default session.
+ if (SessionManager::isDefaultVirgin()) {
+ if (filePath.exists()) {
+ PersistentSettingsReader reader;
+ if (!reader.load(filePath)) {
+ QMessageBox::warning(ICore::dialogParent(), Tr::tr("Error while saving session"),
+ Tr::tr("Could not save session %1").arg(filePath.toUserOutput()));
+ return false;
+ }
+ data = reader.restoreValues();
+ }
+ } else {
+ // save the startup project
+ if (d->m_startupProject)
+ data.insert("StartupProject", d->m_startupProject->projectFilePath().toSettings());
+
+ const QColor c = StyleHelper::requestedBaseColor();
+ if (c.isValid()) {
+ QString tmp = QString::fromLatin1("#%1%2%3")
+ .arg(c.red(), 2, 16, QLatin1Char('0'))
+ .arg(c.green(), 2, 16, QLatin1Char('0'))
+ .arg(c.blue(), 2, 16, QLatin1Char('0'));
+ data.insert(QLatin1String("Color"), tmp);
+ }
+
+ FilePaths projectFiles = Utils::transform(projects(), &Project::projectFilePath);
+ // Restore information on projects that failed to load:
+ // don't read projects to the list, which the user loaded
+ for (const FilePath &failed : std::as_const(d->m_failedProjects)) {
+ if (!projectFiles.contains(failed))
+ projectFiles << failed;
+ }
+
+ data.insert("ProjectList", Utils::transform<QStringList>(projectFiles,
+ &FilePath::toString));
+ data.insert("CascadeSetActive", d->m_casadeSetActive);
+
+ QVariantMap depMap;
+ auto i = d->m_depMap.constBegin();
+ while (i != d->m_depMap.constEnd()) {
+ QString key = i.key().toString();
+ QStringList values;
+ const FilePaths valueList = i.value();
+ for (const FilePath &value : valueList)
+ values << value.toString();
+ depMap.insert(key, values);
+ ++i;
+ }
+ data.insert(QLatin1String("ProjectDependencies"), QVariant(depMap));
+ data.insert(QLatin1String("EditorSettings"), EditorManager::saveState().toBase64());
+ }
+
+ const auto end = sb_d->m_values.constEnd();
+ QStringList keys;
+ for (auto it = sb_d->m_values.constBegin(); it != end; ++it) {
+ data.insert(QLatin1String("value-") + it.key(), it.value());
+ keys << it.key();
+ }
+ data.insert(QLatin1String("valueKeys"), keys);
+
+ if (!sb_d->m_writer || sb_d->m_writer->fileName() != filePath) {
+ delete sb_d->m_writer;
+ sb_d->m_writer = new PersistentSettingsWriter(filePath, "QtCreatorSession");
+ }
+ const bool result = sb_d->m_writer->save(data, ICore::dialogParent());
+ if (result) {
+ if (!SessionManager::isDefaultVirgin())
+ sb_d->m_sessionDateTimes.insert(SessionManager::activeSession(), QDateTime::currentDateTime());
+ } else {
+ QMessageBox::warning(ICore::dialogParent(), Tr::tr("Error while saving session"),
+ Tr::tr("Could not save session to file %1").arg(sb_d->m_writer->fileName().toUserOutput()));
+ }
+
+ return result;
+}
+
+/*!
+ Closes all projects
+ */
+void ProjectManager::closeAllProjects()
+{
+ removeProjects(projects());
+}
+
+const QList<Project *> ProjectManager::projects()
+{
+ return d->m_projects;
+}
+
+bool ProjectManager::hasProjects()
+{
+ return d->hasProjects();
+}
+
+bool ProjectManager::hasProject(Project *p)
+{
+ return d->m_projects.contains(p);
+}
+
+FilePaths ProjectManagerPrivate::dependencies(const FilePath &proName) const
+{
+ FilePaths result;
+ dependencies(proName, result);
+ return result;
+}
+
+void ProjectManagerPrivate::dependencies(const FilePath &proName, FilePaths &result) const
+{
+ const FilePaths depends = m_depMap.value(proName);
+
+ for (const FilePath &dep : depends)
+ dependencies(dep, result);
+
+ if (!result.contains(proName))
+ result.append(proName);
+}
+
+QString ProjectManagerPrivate::sessionTitle(const FilePath &filePath)
+{
+ if (SessionManager::isDefaultSession(sb_d->m_sessionName)) {
+ if (filePath.isEmpty()) {
+ // use single project's name if there is only one loaded.
+ const QList<Project *> projects = ProjectManager::projects();
+ if (projects.size() == 1)
+ return projects.first()->displayName();
+ }
+ } else {
+ QString sessionName = sb_d->m_sessionName;
+ if (sessionName.isEmpty())
+ sessionName = Tr::tr("Untitled");
+ return sessionName;
+ }
+ return QString();
+}
+
+QString ProjectManagerPrivate::locationInProject(const FilePath &filePath)
+{
+ const Project *project = ProjectManager::projectForFile(filePath);
+ if (!project)
+ return QString();
+
+ const FilePath parentDir = filePath.parentDir();
+ if (parentDir == project->projectDirectory())
+ return "@ " + project->displayName();
+
+ if (filePath.isChildOf(project->projectDirectory())) {
+ const FilePath dirInProject = parentDir.relativeChildPath(project->projectDirectory());
+ return "(" + dirInProject.toUserOutput() + " @ " + project->displayName() + ")";
+ }
+
+ // For a file that is "outside" the project it belongs to, we display its
+ // dir's full path because it is easier to read than a series of "../../.".
+ // Example: /home/hugo/GenericProject/App.files lists /home/hugo/lib/Bar.cpp
+ return "(" + parentDir.toUserOutput() + " @ " + project->displayName() + ")";
+}
+
+QString ProjectManagerPrivate::windowTitleAddition(const FilePath &filePath)
+{
+ return filePath.isEmpty() ? QString() : locationInProject(filePath);
+}
+
+FilePaths ProjectManagerPrivate::dependenciesOrder() const
+{
+ QList<QPair<FilePath, FilePaths>> unordered;
+ FilePaths ordered;
+
+ // copy the map to a temporary list
+ for (const Project *pro : m_projects) {
+ const FilePath proName = pro->projectFilePath();
+ const FilePaths depList = filtered(m_depMap.value(proName),
+ [this](const FilePath &proPath) {
+ return contains(m_projects, [proPath](const Project *p) {
+ return p->projectFilePath() == proPath;
+ });
+ });
+ unordered.push_back({proName, depList});
+ }
+
+ while (!unordered.isEmpty()) {
+ for (int i = (unordered.count() - 1); i >= 0; --i) {
+ if (unordered.at(i).second.isEmpty()) {
+ ordered << unordered.at(i).first;
+ unordered.removeAt(i);
+ }
+ }
+
+ // remove the handled projects from the dependency lists
+ // of the remaining unordered projects
+ for (int i = 0; i < unordered.count(); ++i) {
+ for (const FilePath &pro : std::as_const(ordered)) {
+ FilePaths depList = unordered.at(i).second;
+ depList.removeAll(pro);
+ unordered[i].second = depList;
+ }
+ }
+ }
+
+ return ordered;
+}
+
+QList<Project *> ProjectManager::projectOrder(const Project *project)
+{
+ QList<Project *> result;
+
+ FilePaths pros;
+ if (project)
+ pros = d->dependencies(project->projectFilePath());
+ else
+ pros = d->dependenciesOrder();
+
+ for (const FilePath &proFile : std::as_const(pros)) {
+ for (Project *pro : projects()) {
+ if (pro->projectFilePath() == proFile) {
+ result << pro;
+ break;
+ }
+ }
+ }
+
+ return result;
+}
+
+Project *ProjectManager::projectForFile(const FilePath &fileName)
+{
+ if (Project * const project = Utils::findOrDefault(ProjectManager::projects(),
+ [&fileName](const Project *p) { return p->isKnownFile(fileName); })) {
+ return project;
+ }
+ return Utils::findOrDefault(ProjectManager::projects(),
+ [&fileName](const Project *p) {
+ for (const Target * const target : p->targets()) {
+ for (const BuildConfiguration * const bc : target->buildConfigurations()) {
+ if (fileName.isChildOf(bc->buildDirectory()))
+ return false;
+ }
+ }
+ return fileName.isChildOf(p->projectDirectory());
+ });
+}
+
+Project *ProjectManager::projectWithProjectFilePath(const FilePath &filePath)
+{
+ return Utils::findOrDefault(ProjectManager::projects(),
+ [&filePath](const Project *p) { return p->projectFilePath() == filePath; });
+}
+
+void ProjectManager::configureEditor(IEditor *editor, const QString &fileName)
+{
+ if (auto textEditor = qobject_cast<TextEditor::BaseTextEditor*>(editor)) {
+ Project *project = projectForFile(Utils::FilePath::fromString(fileName));
+ // Global settings are the default.
+ if (project)
+ project->editorConfiguration()->configureEditor(textEditor);
+ }
+}
+
+void ProjectManager::configureEditors(Project *project)
+{
+ const QList<IDocument *> documents = DocumentModel::openedDocuments();
+ for (IDocument *document : documents) {
+ if (project->isKnownFile(document->filePath())) {
+ const QList<IEditor *> editors = DocumentModel::editorsForDocument(document);
+ for (IEditor *editor : editors) {
+ if (auto textEditor = qobject_cast<TextEditor::BaseTextEditor*>(editor)) {
+ project->editorConfiguration()->configureEditor(textEditor);
+ }
+ }
+ }
+ }
+}
+
+void ProjectManager::removeProjects(const QList<Project *> &remove)
+{
+ for (Project *pro : remove)
+ emit m_instance->aboutToRemoveProject(pro);
+
+ bool changeStartupProject = false;
+
+ // Delete projects
+ for (Project *pro : remove) {
+ pro->saveSettings();
+ pro->markAsShuttingDown();
+
+ // Remove the project node:
+ d->m_projects.removeOne(pro);
+
+ if (pro == d->m_startupProject)
+ changeStartupProject = true;
+
+ FolderNavigationWidgetFactory::removeRootDirectory(projectFolderId(pro));
+ disconnect(pro, nullptr, m_instance, nullptr);
+ emit m_instance->projectRemoved(pro);
+ }
+
+ if (changeStartupProject)
+ setStartupProject(hasProjects() ? projects().first() : nullptr);
+
+ qDeleteAll(remove);
+}
+
+void ProjectManagerPrivate::restoreDependencies(const PersistentSettingsReader &reader)
+{
+ QMap<QString, QVariant> depMap = reader.restoreValue(QLatin1String("ProjectDependencies")).toMap();
+ auto i = depMap.constBegin();
+ while (i != depMap.constEnd()) {
+ const QString &key = i.key();
+ FilePaths values;
+ const QStringList valueList = i.value().toStringList();
+ for (const QString &value : valueList)
+ values << FilePath::fromString(value);
+ m_depMap.insert(FilePath::fromString(key), values);
+ ++i;
+ }
+}
+
+void ProjectManagerPrivate::askUserAboutFailedProjects()
+{
+ FilePaths failedProjects = m_failedProjects;
+ if (!failedProjects.isEmpty()) {
+ QString fileList = FilePath::formatFilePaths(failedProjects, "<br>");
+ QMessageBox box(QMessageBox::Warning,
+ Tr::tr("Failed to restore project files"),
+ Tr::tr("Could not restore the following project files:<br><b>%1</b>").
+ arg(fileList));
+ auto keepButton = new QPushButton(Tr::tr("Keep projects in Session"), &box);
+ auto removeButton = new QPushButton(Tr::tr("Remove projects from Session"), &box);
+ box.addButton(keepButton, QMessageBox::AcceptRole);
+ box.addButton(removeButton, QMessageBox::DestructiveRole);
+
+ box.exec();
+
+ if (box.clickedButton() == removeButton)
+ m_failedProjects.clear();
+ }
+}
+
+void ProjectManagerPrivate::restoreStartupProject(const PersistentSettingsReader &reader)
+{
+ const FilePath startupProject = FilePath::fromSettings(reader.restoreValue("StartupProject"));
+ if (!startupProject.isEmpty()) {
+ for (Project *pro : std::as_const(m_projects)) {
+ if (pro->projectFilePath() == startupProject) {
+ m_instance->setStartupProject(pro);
+ break;
+ }
+ }
+ }
+ if (!m_startupProject) {
+ if (!startupProject.isEmpty())
+ qWarning() << "Could not find startup project" << startupProject;
+ if (hasProjects())
+ m_instance->setStartupProject(m_projects.first());
+ }
+}
+
+/*!
+ Loads a session, takes a session name (not filename).
+*/
+void ProjectManagerPrivate::restoreProjects(const FilePaths &fileList)
+{
+ // indirectly adds projects to session
+ // Keep projects that failed to load in the session!
+ m_failedProjects = fileList;
+ if (!fileList.isEmpty()) {
+ ProjectExplorerPlugin::OpenProjectResult result = ProjectExplorerPlugin::openProjects(fileList);
+ if (!result)
+ ProjectExplorerPlugin::showOpenProjectError(result);
+ const QList<Project *> projects = result.projects();
+ for (const Project *p : projects)
+ m_failedProjects.removeAll(p->projectFilePath());
+ }
+}
+
+/*
+ * ========== Notes on storing and loading the default session ==========
+ * The default session comes in two flavors: implicit and explicit. The implicit one,
+ * also referred to as "default virgin" in the code base, is the one that is active
+ * at start-up, if no session has been explicitly loaded due to command-line arguments
+ * or the "restore last session" setting in the session manager.
+ * The implicit default session silently turns into the explicit default session
+ * by loading a project or a file or changing settings in the Dependencies panel. The explicit
+ * default session can also be loaded by the user via the Welcome Screen.
+ * This mechanism somewhat complicates the handling of session-specific settings such as
+ * the ones in the task pane: Users expect that changes they make there become persistent, even
+ * when they are in the implicit default session. However, we can't just blindly store
+ * the implicit default session, because then we'd overwrite the project list of the explicit
+ * default session. Therefore, we use the following logic:
+ * - Upon start-up, if no session is to be explicitly loaded, we restore the parts of the
+ * explicit default session that are not related to projects, editors etc; the
+ * "general settings" of the session, so to speak.
+ * - When storing the implicit default session, we overwrite only these "general settings"
+ * of the explicit default session and keep the others as they are.
+ * - When switching from the implicit to the explicit default session, we keep the
+ * "general settings" and load everything else from the session file.
+ * This guarantees that user changes are properly transferred and nothing gets lost from
+ * either the implicit or the explicit default session.
+ *
+ */
+bool ProjectManager::loadSession(const QString &session, bool initial)
+{
+ const bool loadImplicitDefault = session.isEmpty();
+ const bool switchFromImplicitToExplicitDefault = session == DEFAULT_SESSION
+ && sb_d->m_sessionName == DEFAULT_SESSION && !initial;
+
+ // Do nothing if we have that session already loaded,
+ // exception if the session is the default virgin session
+ // we still want to be able to load the default session
+ if (session == sb_d->m_sessionName && !SessionManager::isDefaultVirgin())
+ return true;
+
+ if (!loadImplicitDefault && !SessionManager::sessions().contains(session))
+ return false;
+
+ FilePaths fileList;
+ // Try loading the file
+ FilePath fileName = SessionManager::sessionNameToFileName(loadImplicitDefault ? DEFAULT_SESSION : session);
+ PersistentSettingsReader reader;
+ if (fileName.exists()) {
+ if (!reader.load(fileName)) {
+ QMessageBox::warning(ICore::dialogParent(), Tr::tr("Error while restoring session"),
+ Tr::tr("Could not restore session %1").arg(fileName.toUserOutput()));
+
+ return false;
+ }
+
+ if (loadImplicitDefault) {
+ sb_d->restoreValues(reader);
+ emit SessionManager::instance()->sessionLoaded(DEFAULT_SESSION);
+ return true;
+ }
+
+ fileList = FileUtils::toFilePathList(reader.restoreValue("ProjectList").toStringList());
+ } else if (loadImplicitDefault) {
+ return true;
+ }
+
+ sb_d->m_loadingSession = true;
+
+ // Allow everyone to set something in the session and before saving
+ emit SessionManager::instance()->aboutToUnloadSession(sb_d->m_sessionName);
+
+ if (!save()) {
+ sb_d->m_loadingSession = false;
+ return false;
+ }
+
+ // Clean up
+ if (!EditorManager::closeAllEditors()) {
+ sb_d->m_loadingSession = false;
+ return false;
+ }
+
+ // find a list of projects to close later
+ const QList<Project *> projectsToRemove = Utils::filtered(projects(), [&fileList](Project *p) {
+ return !fileList.contains(p->projectFilePath());
+ });
+ const QList<Project *> openProjects = projects();
+ const FilePaths projectPathsToLoad = Utils::filtered(fileList, [&openProjects](const FilePath &path) {
+ return !Utils::contains(openProjects, [&path](Project *p) {
+ return p->projectFilePath() == path;
+ });
+ });
+ d->m_failedProjects.clear();
+ d->m_depMap.clear();
+ if (!switchFromImplicitToExplicitDefault)
+ sb_d->m_values.clear();
+ d->m_casadeSetActive = false;
+
+ sb_d->m_sessionName = session;
+ delete sb_d->m_writer;
+ sb_d->m_writer = nullptr;
+ EditorManager::updateWindowTitles();
+
+ if (fileName.exists()) {
+ sb_d->m_virginSession = false;
+
+ ProgressManager::addTask(sb_d->m_future.future(), Tr::tr("Loading Session"),
+ "ProjectExplorer.SessionFile.Load");
+
+ sb_d->m_future.setProgressRange(0, 1);
+ sb_d->m_future.setProgressValue(0);
+
+ if (!switchFromImplicitToExplicitDefault)
+ sb_d->restoreValues(reader);
+ emit SessionManager::instance()->aboutToLoadSession(session);
+
+ // retrieve all values before the following code could change them again
+ Id modeId = Id::fromSetting(SessionManager::value(QLatin1String("ActiveMode")));
+ if (!modeId.isValid())
+ modeId = Id(Core::Constants::MODE_EDIT);
+
+ QColor c = QColor(reader.restoreValue(QLatin1String("Color")).toString());
+ if (c.isValid())
+ StyleHelper::setBaseColor(c);
+
+ sb_d->m_future.setProgressRange(0, projectPathsToLoad.count() + 1/*initialization above*/ + 1/*editors*/);
+ sb_d->m_future.setProgressValue(1);
+ QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
+
+ d->restoreProjects(projectPathsToLoad);
+ sb_d->sessionLoadingProgress();
+ d->restoreDependencies(reader);
+ d->restoreStartupProject(reader);
+
+ removeProjects(projectsToRemove); // only remove old projects now that the startup project is set!
+
+ sb_d->restoreEditors(reader);
+
+ sb_d->m_future.reportFinished();
+ sb_d->m_future = QFutureInterface<void>();
+
+ // Fall back to Project mode if the startup project is unconfigured and
+ // use the mode saved in the session otherwise
+ if (d->m_startupProject && d->m_startupProject->needsConfiguration())
+ modeId = Id(Constants::MODE_SESSION);
+
+ ModeManager::activateMode(modeId);
+ ModeManager::setFocusToCurrentMode();
+ } else {
+ removeProjects(projects());
+ ModeManager::activateMode(Id(Core::Constants::MODE_EDIT));
+ ModeManager::setFocusToCurrentMode();
+ }
+
+ d->m_casadeSetActive = reader.restoreValue(QLatin1String("CascadeSetActive"), false).toBool();
+ sb_d->m_lastActiveTimes.insert(session, QDateTime::currentDateTime());
+
+ emit SessionManager::instance()->sessionLoaded(session);
+
+ // Starts a event loop, better do that at the very end
+ d->askUserAboutFailedProjects();
+ sb_d->m_loadingSession = false;
+ return true;
+}
+
+void ProjectManager::reportProjectLoadingProgress()
+{
+ sb_d->sessionLoadingProgress();
+}
+
+FilePaths ProjectManager::projectsForSessionName(const QString &session)
+{
+ const FilePath fileName = SessionManager::sessionNameToFileName(session);
+ PersistentSettingsReader reader;
+ if (fileName.exists()) {
+ if (!reader.load(fileName)) {
+ qWarning() << "Could not restore session" << fileName.toUserOutput();
+ return {};
+ }
+ }
+ return transform(reader.restoreValue(QLatin1String("ProjectList")).toStringList(),
+ &FilePath::fromUserInput);
+}
+
+#ifdef WITH_TESTS
+
+void ProjectExplorerPlugin::testSessionSwitch()
+{
+ QVERIFY(SessionManager::createSession("session1"));
+ QVERIFY(SessionManager::createSession("session2"));
+ QTemporaryFile cppFile("main.cpp");
+ QVERIFY(cppFile.open());
+ cppFile.close();
+ QTemporaryFile projectFile1("XXXXXX.pro");
+ QTemporaryFile projectFile2("XXXXXX.pro");
+ struct SessionSpec {
+ SessionSpec(const QString &n, QTemporaryFile &f) : name(n), projectFile(f) {}
+ const QString name;
+ QTemporaryFile &projectFile;
+ };
+ std::vector<SessionSpec> sessionSpecs{SessionSpec("session1", projectFile1),
+ SessionSpec("session2", projectFile2)};
+ for (const SessionSpec &sessionSpec : sessionSpecs) {
+ static const QByteArray proFileContents
+ = "TEMPLATE = app\n"
+ "CONFIG -= qt\n"
+ "SOURCES = " + cppFile.fileName().toLocal8Bit();
+ QVERIFY(sessionSpec.projectFile.open());
+ sessionSpec.projectFile.write(proFileContents);
+ sessionSpec.projectFile.close();
+ QVERIFY(ProjectManager::loadSession(sessionSpec.name));
+ const OpenProjectResult openResult
+ = ProjectExplorerPlugin::openProject(
+ FilePath::fromString(sessionSpec.projectFile.fileName()));
+ if (openResult.errorMessage().contains("text/plain"))
+ QSKIP("This test requires the presence of QmakeProjectManager to be fully functional");
+ QVERIFY(openResult);
+ QCOMPARE(openResult.projects().count(), 1);
+ QVERIFY(openResult.project());
+ QCOMPARE(ProjectManager::projects().count(), 1);
+ }
+ for (int i = 0; i < 30; ++i) {
+ QVERIFY(ProjectManager::loadSession("session1"));
+ QCOMPARE(SessionManager::activeSession(), "session1");
+ QCOMPARE(ProjectManager::projects().count(), 1);
+ QVERIFY(ProjectManager::loadSession("session2"));
+ QCOMPARE(SessionManager::activeSession(), "session2");
+ QCOMPARE(ProjectManager::projects().count(), 1);
+ }
+ QVERIFY(ProjectManager::loadSession("session1"));
+ ProjectManager::closeAllProjects();
+ QVERIFY(ProjectManager::loadSession("session2"));
+ ProjectManager::closeAllProjects();
+ QVERIFY(SessionManager::deleteSession("session1"));
+ QVERIFY(SessionManager::deleteSession("session2"));
+}
+
+#endif // WITH_TESTS
+
+} // namespace ProjectExplorer
diff --git a/src/plugins/projectexplorer/projectmanager.h b/src/plugins/projectexplorer/projectmanager.h
index 9d54eba572c..ad3e402992a 100644
--- a/src/plugins/projectexplorer/projectmanager.h
+++ b/src/plugins/projectexplorer/projectmanager.h
@@ -6,20 +6,35 @@
#include "projectexplorer_export.h"
#include <QString>
+#include <QObject>
+
+namespace Core { class IEditor; }
#include <functional>
namespace Utils {
class FilePath;
+using FilePaths = QList<FilePath>;
class MimeType;
} // Utils
namespace ProjectExplorer {
+class BuildSystem;
class Project;
+class RunConfiguration;
+class Target;
-class PROJECTEXPLORER_EXPORT ProjectManager
+class PROJECTEXPLORER_EXPORT ProjectManager : public QObject
{
+ Q_OBJECT
+
+public:
+ ProjectManager();
+ ~ProjectManager() override;
+
+ static ProjectManager *instance();
+
public:
static bool canOpenProjectForMimeType(const Utils::MimeType &mt);
static Project *openProject(const Utils::MimeType &mt, const Utils::FilePath &fileName);
@@ -32,7 +47,64 @@ public:
});
}
+ static bool save();
+ static void closeAllProjects();
+
+ static void addProject(Project *project);
+ static void removeProject(Project *project);
+ static void removeProjects(const QList<Project *> &remove);
+
+ static void setStartupProject(Project *startupProject);
+
+ static QList<Project *> dependencies(const Project *project);
+ static bool hasDependency(const Project *project, const Project *depProject);
+ static bool canAddDependency(const Project *project, const Project *depProject);
+ static bool addDependency(Project *project, Project *depProject);
+ static void removeDependency(Project *project, Project *depProject);
+
+ static bool isProjectConfigurationCascading();
+ static void setProjectConfigurationCascading(bool b);
+
+ static Project *startupProject();
+ static Target *startupTarget();
+ static BuildSystem *startupBuildSystem();
+ static RunConfiguration *startupRunConfiguration();
+
+ static const QList<Project *> projects();
+ static bool hasProjects();
+ static bool hasProject(Project *p);
+
+ // NBS rewrite projectOrder (dependency management)
+ static QList<Project *> projectOrder(const Project *project = nullptr);
+
+ static Project *projectForFile(const Utils::FilePath &fileName);
+ static Project *projectWithProjectFilePath(const Utils::FilePath &filePath);
+
+ static Utils::FilePaths projectsForSessionName(const QString &session);
+
+ static void reportProjectLoadingProgress();
+
+ static bool loadSession(const QString &session, bool initial = false);
+
+signals:
+ void targetAdded(ProjectExplorer::Target *target);
+ void targetRemoved(ProjectExplorer::Target *target);
+ void projectAdded(ProjectExplorer::Project *project);
+ void aboutToRemoveProject(ProjectExplorer::Project *project);
+ void projectDisplayNameChanged(ProjectExplorer::Project *project);
+ void projectRemoved(ProjectExplorer::Project *project);
+
+ void startupProjectChanged(ProjectExplorer::Project *project);
+
+ void dependencyChanged(ProjectExplorer::Project *a, ProjectExplorer::Project *b);
+
+ // for tests only
+ void projectFinishedParsing(ProjectExplorer::Project *project);
+
private:
+ static void configureEditor(Core::IEditor *editor, const QString &fileName);
+ static void configureEditors(Project *project);
+
static void registerProjectCreator(const QString &mimeType,
const std::function<Project *(const Utils::FilePath &)> &);
};
diff --git a/src/plugins/projectexplorer/projectmodels.cpp b/src/plugins/projectexplorer/projectmodels.cpp
index 0a761f91e62..d318de8cc34 100644
--- a/src/plugins/projectexplorer/projectmodels.cpp
+++ b/src/plugins/projectexplorer/projectmodels.cpp
@@ -8,6 +8,7 @@
#include "projectnodes.h"
#include "projectexplorer.h"
#include "projectexplorertr.h"
+#include "projectmanager.h"
#include "projecttree.h"
#include "session.h"
#include "target.h"
@@ -174,14 +175,15 @@ FlatModel::FlatModel(QObject *parent)
ProjectTree *tree = ProjectTree::instance();
connect(tree, &ProjectTree::subtreeChanged, this, &FlatModel::updateSubtree);
- SessionManager *sm = SessionManager::instance();
- connect(sm, &SessionManager::projectRemoved, this, &FlatModel::handleProjectRemoved);
- connect(sm, &SessionManager::aboutToLoadSession, this, &FlatModel::loadExpandData);
- connect(sm, &SessionManager::aboutToSaveSession, this, &FlatModel::saveExpandData);
- connect(sm, &SessionManager::projectAdded, this, &FlatModel::handleProjectAdded);
- connect(sm, &SessionManager::startupProjectChanged, this, [this] { emit layoutChanged(); });
+ ProjectManager *sm = ProjectManager::instance();
+ SessionManager *sb = SessionManager::instance();
+ connect(sm, &ProjectManager::projectRemoved, this, &FlatModel::handleProjectRemoved);
+ connect(sb, &SessionManager::aboutToLoadSession, this, &FlatModel::loadExpandData);
+ connect(sb, &SessionManager::aboutToSaveSession, this, &FlatModel::saveExpandData);
+ connect(sm, &ProjectManager::projectAdded, this, &FlatModel::handleProjectAdded);
+ connect(sm, &ProjectManager::startupProjectChanged, this, [this] { emit layoutChanged(); });
- for (Project *project : SessionManager::projects())
+ for (Project *project : ProjectManager::projects())
handleProjectAdded(project);
}
@@ -234,7 +236,7 @@ QVariant FlatModel::data(const QModelIndex &index, int role) const
}
case Qt::FontRole: {
QFont font;
- if (project == SessionManager::startupProject())
+ if (project == ProjectManager::startupProject())
font.setBold(true);
return font;
}
@@ -407,7 +409,7 @@ void FlatModel::updateSubtree(FolderNode *node)
void FlatModel::rebuildModel()
{
- const QList<Project *> projects = SessionManager::projects();
+ const QList<Project *> projects = ProjectManager::projects();
for (Project *project : projects)
addOrRebuildProjectModel(project);
}
diff --git a/src/plugins/projectexplorer/projectnodes.h b/src/plugins/projectexplorer/projectnodes.h
index 35927c02c8e..58fbf9cb925 100644
--- a/src/plugins/projectexplorer/projectnodes.h
+++ b/src/plugins/projectexplorer/projectnodes.h
@@ -32,6 +32,8 @@ enum class FileType : quint16 {
Resource,
QML,
Project,
+ App,
+ Lib,
FileTypeSize
};
diff --git a/src/plugins/projectexplorer/projectnodeshelper.h b/src/plugins/projectexplorer/projectnodeshelper.h
index 727497c6bf5..bcb5454fd89 100644
--- a/src/plugins/projectexplorer/projectnodeshelper.h
+++ b/src/plugins/projectexplorer/projectnodeshelper.h
@@ -11,18 +11,19 @@
#include <utils/algorithm.h>
#include <utils/filepath.h>
+#include <QPromise>
+
namespace ProjectExplorer {
template<typename Result>
-QList<FileNode *> scanForFiles(QFutureInterface<Result> &future,
- const Utils::FilePath &directory,
+QList<FileNode *> scanForFiles(QPromise<Result> &promise, const Utils::FilePath &directory,
const std::function<FileNode *(const Utils::FilePath &)> factory);
namespace Internal {
template<typename Result>
QList<FileNode *> scanForFilesRecursively(
- QFutureInterface<Result> &future,
+ QPromise<Result> &promise,
double progressStart,
double progressRange,
const Utils::FilePath &directory,
@@ -46,7 +47,7 @@ QList<FileNode *> scanForFilesRecursively(
const double progressIncrement = progressRange / static_cast<double>(entries.count());
int lastIntProgress = 0;
for (const QFileInfo &entry : entries) {
- if (future.isCanceled())
+ if (promise.isCanceled())
return result;
const Utils::FilePath entryName = Utils::FilePath::fromString(entry.absoluteFilePath());
@@ -54,7 +55,7 @@ QList<FileNode *> scanForFilesRecursively(
return vc->isVcsFileOrDirectory(entryName);
})) {
if (entry.isDir())
- result.append(scanForFilesRecursively(future,
+ result.append(scanForFilesRecursively(promise,
progress,
progressIncrement,
entryName,
@@ -66,26 +67,25 @@ QList<FileNode *> scanForFilesRecursively(
}
progress += progressIncrement;
const int intProgress = std::min(static_cast<int>(progressStart + progress),
- future.progressMaximum());
+ promise.future().progressMaximum());
if (lastIntProgress < intProgress) {
- future.setProgressValue(intProgress);
+ promise.setProgressValue(intProgress);
lastIntProgress = intProgress;
}
}
- future.setProgressValue(
- std::min(static_cast<int>(progressStart + progressRange), future.progressMaximum()));
+ promise.setProgressValue(std::min(static_cast<int>(progressStart + progressRange),
+ promise.future().progressMaximum()));
return result;
}
} // namespace Internal
template<typename Result>
-QList<FileNode *> scanForFiles(QFutureInterface<Result> &future,
- const Utils::FilePath &directory,
+QList<FileNode *> scanForFiles(QPromise<Result> &promise, const Utils::FilePath &directory,
const std::function<FileNode *(const Utils::FilePath &)> factory)
{
QSet<QString> visited;
- future.setProgressRange(0, 1000000);
- return Internal::scanForFilesRecursively(future,
+ promise.setProgressRange(0, 1000000);
+ return Internal::scanForFilesRecursively(promise,
0.0,
1000000.0,
directory,
diff --git a/src/plugins/projectexplorer/projecttree.cpp b/src/plugins/projectexplorer/projecttree.cpp
index 18b03d3ae0e..5f81f473fe4 100644
--- a/src/plugins/projectexplorer/projecttree.cpp
+++ b/src/plugins/projectexplorer/projecttree.cpp
@@ -6,9 +6,9 @@
#include "project.h"
#include "projectexplorerconstants.h"
#include "projectexplorertr.h"
+#include "projectmanager.h"
#include "projectnodes.h"
#include "projecttreewidget.h"
-#include "session.h"
#include "target.h"
#include <coreplugin/actionmanager/actioncontainer.h>
@@ -53,11 +53,11 @@ ProjectTree::ProjectTree(QObject *parent) : QObject(parent)
connect(qApp, &QApplication::focusChanged,
this, &ProjectTree::update);
- connect(SessionManager::instance(), &SessionManager::projectAdded,
+ connect(ProjectManager::instance(), &ProjectManager::projectAdded,
this, &ProjectTree::sessionAndTreeChanged);
- connect(SessionManager::instance(), &SessionManager::projectRemoved,
+ connect(ProjectManager::instance(), &ProjectManager::projectRemoved,
this, &ProjectTree::sessionAndTreeChanged);
- connect(SessionManager::instance(), &SessionManager::startupProjectChanged,
+ connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged,
this, &ProjectTree::sessionChanged);
connect(this, &ProjectTree::subtreeChanged, this, &ProjectTree::treeChanged);
}
@@ -170,7 +170,7 @@ void ProjectTree::updateFromNode(Node *node)
if (node)
project = projectForNode(node);
else
- project = SessionManager::startupProject();
+ project = ProjectManager::startupProject();
setCurrent(node, project);
for (ProjectTreeWidget *widget : std::as_const(m_projectTreeWidgets))
@@ -224,7 +224,7 @@ void ProjectTree::sessionChanged()
{
if (m_currentProject) {
Core::DocumentManager::setDefaultLocationForNewFiles(m_currentProject->projectDirectory());
- } else if (Project *project = SessionManager::startupProject()) {
+ } else if (Project *project = ProjectManager::startupProject()) {
Core::DocumentManager::setDefaultLocationForNewFiles(project->projectDirectory());
updateFromNode(nullptr); // Make startup project current if there is no other current
} else {
@@ -300,7 +300,7 @@ void ProjectTree::updateFileWarning(Core::IDocument *document, const QString &te
if (!infoBar->canInfoBeAdded(infoId))
return;
const FilePath filePath = document->filePath();
- const QList<Project *> projects = SessionManager::projects();
+ const QList<Project *> projects = ProjectManager::projects();
if (projects.isEmpty())
return;
for (Project *project : projects) {
@@ -394,7 +394,7 @@ void ProjectTree::applyTreeManager(FolderNode *folder, ConstructionPhase phase)
bool ProjectTree::hasNode(const Node *node)
{
- return Utils::contains(SessionManager::projects(), [node](const Project *p) {
+ return Utils::contains(ProjectManager::projects(), [node](const Project *p) {
if (!p)
return false;
if (p->containerNode() == node)
@@ -409,7 +409,7 @@ bool ProjectTree::hasNode(const Node *node)
void ProjectTree::forEachNode(const std::function<void(Node *)> &task)
{
- const QList<Project *> projects = SessionManager::projects();
+ const QList<Project *> projects = ProjectManager::projects();
for (Project *project : projects) {
if (ProjectNode *projectNode = project->rootProjectNode()) {
task(projectNode);
@@ -430,7 +430,7 @@ Project *ProjectTree::projectForNode(const Node *node)
while (folder && folder->parentFolderNode())
folder = folder->parentFolderNode();
- return Utils::findOrDefault(SessionManager::projects(), [folder](const Project *pro) {
+ return Utils::findOrDefault(ProjectManager::projects(), [folder](const Project *pro) {
return pro->containerNode() == folder;
});
}
@@ -438,7 +438,7 @@ Project *ProjectTree::projectForNode(const Node *node)
Node *ProjectTree::nodeForFile(const FilePath &fileName)
{
Node *node = nullptr;
- for (const Project *project : SessionManager::projects()) {
+ for (const Project *project : ProjectManager::projects()) {
project->nodeForFilePath(fileName, [&](const Node *n) {
if (!node || (!node->asFileNode() && n->asFileNode()))
node = const_cast<Node *>(n);
diff --git a/src/plugins/projectexplorer/projecttreewidget.cpp b/src/plugins/projectexplorer/projecttreewidget.cpp
index 65f38626fef..28c06e9254d 100644
--- a/src/plugins/projectexplorer/projecttreewidget.cpp
+++ b/src/plugins/projectexplorer/projecttreewidget.cpp
@@ -6,10 +6,10 @@
#include "project.h"
#include "projectexplorerconstants.h"
#include "projectexplorertr.h"
+#include "projectmanager.h"
#include "projectmodels.h"
#include "projectnodes.h"
#include "projecttree.h"
-#include "session.h"
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/command.h>
@@ -343,7 +343,7 @@ Node *ProjectTreeWidget::nodeForFile(const FilePath &fileName)
int bestNodeExpandCount = INT_MAX;
// FIXME: Looks like this could be done with less cycles.
- for (Project *project : SessionManager::projects()) {
+ for (Project *project : ProjectManager::projects()) {
if (ProjectNode *projectNode = project->rootProjectNode()) {
projectNode->forEachGenericNode([&](Node *node) {
if (node->filePath() == fileName) {
diff --git a/src/plugins/projectexplorer/projectwelcomepage.cpp b/src/plugins/projectexplorer/projectwelcomepage.cpp
index 156212a5041..5b392ea7a15 100644
--- a/src/plugins/projectexplorer/projectwelcomepage.cpp
+++ b/src/plugins/projectexplorer/projectwelcomepage.cpp
@@ -7,6 +7,7 @@
#include "sessionmodel.h"
#include "projectexplorer.h"
#include "projectexplorertr.h"
+#include "projectmanager.h"
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/actionmanager/command.h>
@@ -324,7 +325,7 @@ public:
if (expanded) {
painter->setPen(textColor);
painter->setFont(sizedFont(12, option.widget));
- const FilePaths projects = SessionManager::projectsForSessionName(sessionName);
+ const FilePaths projects = ProjectManager::projectsForSessionName(sessionName);
int yy = firstBase + SESSION_LINE_HEIGHT - 3;
QFontMetrics fm(option.widget->font());
for (const FilePath &projectPath : projects) {
@@ -378,7 +379,7 @@ public:
int h = SESSION_LINE_HEIGHT;
QString sessionName = idx.data(Qt::DisplayRole).toString();
if (m_expandedSessions.contains(sessionName)) {
- const FilePaths projects = SessionManager::projectsForSessionName(sessionName);
+ const FilePaths projects = ProjectManager::projectsForSessionName(sessionName);
h += projects.size() * 40 + LINK_HEIGHT - 6;
}
return QSize(380, h + ItemGap);
diff --git a/src/plugins/projectexplorer/projectwindow.cpp b/src/plugins/projectexplorer/projectwindow.cpp
index afe2b45e7e7..e91dbf94feb 100644
--- a/src/plugins/projectexplorer/projectwindow.cpp
+++ b/src/plugins/projectexplorer/projectwindow.cpp
@@ -12,9 +12,9 @@
#include "projectexplorerconstants.h"
#include "projectexplorertr.h"
#include "projectimporter.h"
+#include "projectmanager.h"
#include "projectpanelfactory.h"
#include "projectsettingswidget.h"
-#include "session.h"
#include "target.h"
#include "targetsettingspanel.h"
@@ -351,7 +351,7 @@ public:
case Qt::FontRole: {
QFont font;
- font.setBold(m_project == SessionManager::startupProject());
+ font.setBold(m_project == ProjectManager::startupProject());
return font;
}
@@ -391,7 +391,7 @@ public:
if (role == ItemActivatedDirectlyRole) {
// Someone selected the project using the combobox or similar.
- SessionManager::setStartupProject(m_project);
+ ProjectManager::setStartupProject(m_project);
m_currentChildIndex = 0; // Use some Target page by defaults
m_targetsItem->setData(column, dat, ItemActivatedFromAboveRole); // And propagate downwards.
announceChange();
@@ -545,18 +545,18 @@ public:
m_projectSelection->showPopup();
});
- SessionManager *sessionManager = SessionManager::instance();
- connect(sessionManager, &SessionManager::projectAdded,
+ ProjectManager *sessionManager = ProjectManager::instance();
+ connect(sessionManager, &ProjectManager::projectAdded,
this, &ProjectWindowPrivate::registerProject);
- connect(sessionManager, &SessionManager::aboutToRemoveProject,
+ connect(sessionManager, &ProjectManager::aboutToRemoveProject,
this, &ProjectWindowPrivate::deregisterProject);
- connect(sessionManager, &SessionManager::startupProjectChanged,
+ connect(sessionManager, &ProjectManager::startupProjectChanged,
this, &ProjectWindowPrivate::startupProjectChanged);
m_importBuild = new QPushButton(Tr::tr("Import Existing Build..."));
connect(m_importBuild, &QPushButton::clicked,
this, &ProjectWindowPrivate::handleImportBuild);
- connect(sessionManager, &SessionManager::startupProjectChanged, this, [this](Project *project) {
+ connect(sessionManager, &ProjectManager::startupProjectChanged, this, [this](Project *project) {
m_importBuild->setEnabled(project && project->projectImporter());
});
@@ -671,7 +671,7 @@ public:
void projectSelected(int index)
{
Project *project = m_comboBoxModel.rootItem()->childAt(index)->m_projectItem->project();
- SessionManager::setStartupProject(project);
+ ProjectManager::setStartupProject(project);
}
ComboBoxItem *itemForProject(Project *project) const
@@ -774,8 +774,8 @@ public:
}
}
if (lastTarget && lastBc) {
- SessionManager::setActiveBuildConfiguration(lastTarget, lastBc, SetActive::Cascade);
- SessionManager::setActiveTarget(project, lastTarget, SetActive::Cascade);
+ lastTarget->setActiveBuildConfiguration(lastBc, SetActive::Cascade);
+ project->setActiveTarget(lastTarget, SetActive::Cascade);
}
}
diff --git a/src/plugins/projectexplorer/projectwizardpage.cpp b/src/plugins/projectexplorer/projectwizardpage.cpp
index 34dc0b9aaa5..a648eb0981f 100644
--- a/src/plugins/projectexplorer/projectwizardpage.cpp
+++ b/src/plugins/projectexplorer/projectwizardpage.cpp
@@ -5,8 +5,8 @@
#include "project.h"
#include "projectexplorertr.h"
+#include "projectmanager.h"
#include "projectmodels.h"
-#include "session.h"
#include <coreplugin/icore.h>
#include <coreplugin/iversioncontrol.h>
@@ -463,7 +463,7 @@ void ProjectWizardPage::initializeProjectTree(Node *context, const FilePaths &pa
TreeItem *root = m_model.rootItem();
root->removeChildren();
- for (Project *project : SessionManager::projects()) {
+ for (Project *project : ProjectManager::projects()) {
if (ProjectNode *pn = project->rootProjectNode()) {
if (kind == IWizardFactory::ProjectWizard) {
if (AddNewTree *child = buildAddProjectTree(pn, paths.first(), context, &selector))
diff --git a/src/plugins/projectexplorer/rawprojectpart.cpp b/src/plugins/projectexplorer/rawprojectpart.cpp
index 6abbedde93b..31147c3803b 100644
--- a/src/plugins/projectexplorer/rawprojectpart.cpp
+++ b/src/plugins/projectexplorer/rawprojectpart.cpp
@@ -11,13 +11,14 @@
#include "target.h"
#include <ios/iosconstants.h>
+
#include <utils/algorithm.h>
namespace ProjectExplorer {
RawProjectPartFlags::RawProjectPartFlags(const ToolChain *toolChain,
const QStringList &commandLineFlags,
- const QString &includeFileBaseDir)
+ const Utils::FilePath &includeFileBaseDir)
{
// Keep the following cheap/non-blocking for the ui thread. Expensive
// operations are encapsulated in ToolChainInfo as "runners".
@@ -25,7 +26,8 @@ RawProjectPartFlags::RawProjectPartFlags(const ToolChain *toolChain,
if (toolChain) {
warningFlags = toolChain->warningFlags(commandLineFlags);
languageExtensions = toolChain->languageExtensions(commandLineFlags);
- includedFiles = toolChain->includedFiles(commandLineFlags, includeFileBaseDir);
+ includedFiles = Utils::transform(toolChain->includedFiles(commandLineFlags, includeFileBaseDir),
+ &Utils::FilePath::toFSPathString);
}
}
diff --git a/src/plugins/projectexplorer/rawprojectpart.h b/src/plugins/projectexplorer/rawprojectpart.h
index 580c83d439d..ca210ed43e2 100644
--- a/src/plugins/projectexplorer/rawprojectpart.h
+++ b/src/plugins/projectexplorer/rawprojectpart.h
@@ -37,7 +37,7 @@ class PROJECTEXPLORER_EXPORT RawProjectPartFlags
public:
RawProjectPartFlags() = default;
RawProjectPartFlags(const ToolChain *toolChain, const QStringList &commandLineFlags,
- const QString &includeFileBaseDir);
+ const Utils::FilePath &includeFileBaseDir);
public:
QStringList commandLineFlags;
diff --git a/src/plugins/projectexplorer/runconfiguration.cpp b/src/plugins/projectexplorer/runconfiguration.cpp
index b5f3f8e703e..cdb6d708c4d 100644
--- a/src/plugins/projectexplorer/runconfiguration.cpp
+++ b/src/plugins/projectexplorer/runconfiguration.cpp
@@ -12,10 +12,10 @@
#include "projectexplorer.h"
#include "projectexplorerconstants.h"
#include "projectexplorertr.h"
+#include "projectmanager.h"
#include "projectnodes.h"
#include "runconfigurationaspects.h"
#include "runcontrol.h"
-#include "session.h"
#include "target.h"
#include <coreplugin/icontext.h>
@@ -33,7 +33,6 @@
#include <utils/utilsicons.h>
#include <utils/variablechooser.h>
-#include <QDir>
#include <QHash>
#include <QPushButton>
#include <QTimer>
@@ -313,7 +312,7 @@ void RunConfiguration::update()
const bool isActive = target()->isActive() && target()->activeRunConfiguration() == this;
- if (isActive && project() == SessionManager::startupProject())
+ if (isActive && project() == ProjectManager::startupProject())
ProjectExplorerPlugin::updateRunActions();
}
diff --git a/src/plugins/projectexplorer/runconfigurationaspects.cpp b/src/plugins/projectexplorer/runconfigurationaspects.cpp
index 9ba24c04672..7e278d353e9 100644
--- a/src/plugins/projectexplorer/runconfigurationaspects.cpp
+++ b/src/plugins/projectexplorer/runconfigurationaspects.cpp
@@ -570,19 +570,12 @@ void ExecutableAspect::setExpectedKind(const PathChooser::Kind expectedKind)
Sets the environment in which paths will be searched when the expected kind
of paths is chosen as PathChooser::Command or PathChooser::ExistingCommand
to \a env.
-
- \sa Utils::StringAspect::setEnvironmentChange()
*/
-void ExecutableAspect::setEnvironmentChange(const EnvironmentChange &change)
-{
- m_executable.setEnvironmentChange(change);
- if (m_alternativeExecutable)
- m_alternativeExecutable->setEnvironmentChange(change);
-}
-
void ExecutableAspect::setEnvironment(const Environment &env)
{
- setEnvironmentChange(EnvironmentChange::fromDictionary(env.toDictionary()));
+ m_executable.setEnvironment(env);
+ if (m_alternativeExecutable)
+ m_alternativeExecutable->setEnvironment(env);
}
/*!
diff --git a/src/plugins/projectexplorer/runconfigurationaspects.h b/src/plugins/projectexplorer/runconfigurationaspects.h
index 9e948bc6ecf..9656c2f5bdb 100644
--- a/src/plugins/projectexplorer/runconfigurationaspects.h
+++ b/src/plugins/projectexplorer/runconfigurationaspects.h
@@ -168,7 +168,6 @@ public:
void setPlaceHolderText(const QString &placeHolderText);
void setHistoryCompleter(const QString &historyCompleterKey);
void setExpectedKind(const Utils::PathChooser::Kind expectedKind);
- void setEnvironmentChange(const Utils::EnvironmentChange &change);
void setEnvironment(const Utils::Environment &env);
void setDisplayStyle(Utils::StringAspect::DisplayStyle style);
diff --git a/src/plugins/projectexplorer/runcontrol.cpp b/src/plugins/projectexplorer/runcontrol.cpp
index 72b2e76e502..96bbc302ac5 100644
--- a/src/plugins/projectexplorer/runcontrol.cpp
+++ b/src/plugins/projectexplorer/runcontrol.cpp
@@ -1069,11 +1069,9 @@ bool RunControl::showPromptToStopDialog(const QString &title,
void RunControl::provideAskPassEntry(Environment &env)
{
- if (env.value("SUDO_ASKPASS").isEmpty()) {
- const FilePath askpass = SshSettings::askpassFilePath();
- if (askpass.exists())
- env.set("SUDO_ASKPASS", askpass.toUserOutput());
- }
+ const FilePath askpass = SshSettings::askpassFilePath();
+ if (askpass.exists())
+ env.setFallback("SUDO_ASKPASS", askpass.toUserOutput());
}
bool RunControlPrivate::isAllowedTransition(RunControlState from, RunControlState to)
diff --git a/src/plugins/projectexplorer/runsettingspropertiespage.cpp b/src/plugins/projectexplorer/runsettingspropertiespage.cpp
index bd8b30f2b7a..c1a3a75e3c2 100644
--- a/src/plugins/projectexplorer/runsettingspropertiespage.cpp
+++ b/src/plugins/projectexplorer/runsettingspropertiespage.cpp
@@ -291,11 +291,10 @@ void RunSettingsWidget::currentDeployConfigurationChanged(int index)
if (m_ignoreChanges.isLocked())
return;
if (index == -1)
- SessionManager::setActiveDeployConfiguration(m_target, nullptr, SetActive::Cascade);
+ m_target->setActiveDeployConfiguration(nullptr, SetActive::Cascade);
else
- SessionManager::setActiveDeployConfiguration(m_target,
- qobject_cast<DeployConfiguration *>(m_target->deployConfigurationModel()->projectConfigurationAt(index)),
- SetActive::Cascade);
+ m_target->setActiveDeployConfiguration(qobject_cast<DeployConfiguration *>(m_target->deployConfigurationModel()->projectConfigurationAt(index)),
+ SetActive::Cascade);
}
void RunSettingsWidget::aboutToShowDeployMenu()
@@ -309,7 +308,7 @@ void RunSettingsWidget::aboutToShowDeployMenu()
if (!newDc)
return;
m_target->addDeployConfiguration(newDc);
- SessionManager::setActiveDeployConfiguration(m_target, newDc, SetActive::Cascade);
+ m_target->setActiveDeployConfiguration(newDc, SetActive::Cascade);
m_removeDeployToolButton->setEnabled(m_target->deployConfigurations().size() > 1);
});
}
diff --git a/src/plugins/projectexplorer/selectablefilesmodel.cpp b/src/plugins/projectexplorer/selectablefilesmodel.cpp
index 365c319992d..28d601f89b7 100644
--- a/src/plugins/projectexplorer/selectablefilesmodel.cpp
+++ b/src/plugins/projectexplorer/selectablefilesmodel.cpp
@@ -9,10 +9,10 @@
#include <coreplugin/icore.h>
#include <utils/algorithm.h>
+#include <utils/asynctask.h>
#include <utils/fancylineedit.h>
#include <utils/fsengine/fileiconprovider.h>
#include <utils/pathchooser.h>
-#include <utils/runextensions.h>
#include <utils/stringutils.h>
#include <QDialogButtonBox>
@@ -51,13 +51,13 @@ void SelectableFilesFromDirModel::startParsing(const Utils::FilePath &baseDir)
m_rootForFuture->fullPath = baseDir;
m_rootForFuture->isDir = true;
- m_watcher.setFuture(Utils::runAsync(&SelectableFilesFromDirModel::run, this));
+ m_watcher.setFuture(Utils::asyncRun(&SelectableFilesFromDirModel::run, this));
}
-void SelectableFilesFromDirModel::run(QFutureInterface<void> &fi)
+void SelectableFilesFromDirModel::run(QPromise<void> &promise)
{
m_futureCount = 0;
- buildTree(m_baseDir, m_rootForFuture, fi, 5);
+ buildTree(m_baseDir, m_rootForFuture, promise, 5);
}
void SelectableFilesFromDirModel::buildTreeFinished()
@@ -97,7 +97,7 @@ SelectableFilesModel::FilterState SelectableFilesModel::filter(Tree *t)
}
void SelectableFilesFromDirModel::buildTree(const Utils::FilePath &baseDir, Tree *tree,
- QFutureInterface<void> &fi, int symlinkDepth)
+ QPromise<void> &promise, int symlinkDepth)
{
if (symlinkDepth == 0)
return;
@@ -111,7 +111,7 @@ void SelectableFilesFromDirModel::buildTree(const Utils::FilePath &baseDir, Tree
Utils::FilePath fn = Utils::FilePath::fromFileInfo(fileInfo);
if (m_futureCount % 100) {
emit parsingProgress(fn);
- if (fi.isCanceled())
+ if (promise.isCanceled())
return;
}
++m_futureCount;
@@ -121,7 +121,7 @@ void SelectableFilesFromDirModel::buildTree(const Utils::FilePath &baseDir, Tree
t->name = fileInfo.fileName();
t->fullPath = fn;
t->isDir = true;
- buildTree(fn, t, fi, symlinkDepth - fileInfo.isSymLink());
+ buildTree(fn, t, promise, symlinkDepth - fileInfo.isSymLink());
allChecked &= t->checked == Qt::Checked;
allUnchecked &= t->checked == Qt::Unchecked;
tree->childDirectories.append(t);
diff --git a/src/plugins/projectexplorer/selectablefilesmodel.h b/src/plugins/projectexplorer/selectablefilesmodel.h
index 6340e2d1a2d..6f9d7b67bed 100644
--- a/src/plugins/projectexplorer/selectablefilesmodel.h
+++ b/src/plugins/projectexplorer/selectablefilesmodel.h
@@ -147,11 +147,9 @@ signals:
void parsingProgress(const Utils::FilePath &fileName);
private:
- void buildTree(const Utils::FilePath &baseDir,
- Tree *tree,
- QFutureInterface<void> &fi,
+ void buildTree(const Utils::FilePath &baseDir, Tree *tree, QPromise<void> &promise,
int symlinkDepth);
- void run(QFutureInterface<void> &fi);
+ void run(QPromise<void> &promise);
void buildTreeFinished();
// Used in the future thread need to all not used after calling startParsing
diff --git a/src/plugins/projectexplorer/session.cpp b/src/plugins/projectexplorer/session.cpp
index 6dcfd9a4619..427cb8e4ca0 100644
--- a/src/plugins/projectexplorer/session.cpp
+++ b/src/plugins/projectexplorer/session.cpp
@@ -3,16 +3,11 @@
#include "session.h"
-#include "buildconfiguration.h"
-#include "deployconfiguration.h"
-#include "editorconfiguration.h"
-#include "kit.h"
-#include "project.h"
-#include "projectexplorer.h"
+#include "session_p.h"
+
#include "projectexplorerconstants.h"
#include "projectexplorertr.h"
-#include "projectnodes.h"
-#include "target.h"
+#include "projectmanager.h"
#include <coreplugin/coreconstants.h>
#include <coreplugin/editormanager/editormanager.h>
@@ -35,15 +30,8 @@
#include <QMessageBox>
#include <QPushButton>
-#ifdef WITH_TESTS
-#include <QTemporaryFile>
-#include <QTest>
-#include <vector>
-#endif
-
using namespace Core;
using namespace Utils;
-using namespace ProjectExplorer::Internal;
namespace ProjectExplorer {
@@ -61,98 +49,36 @@ const char LAST_ACTIVE_TIMES_KEY[] = "LastActiveTimes";
This could be improved.
*/
-class SessionManagerPrivate
-{
-public:
- void restoreValues(const PersistentSettingsReader &reader);
- void restoreDependencies(const PersistentSettingsReader &reader);
- void restoreStartupProject(const PersistentSettingsReader &reader);
- void restoreEditors(const PersistentSettingsReader &reader);
- void restoreProjects(const FilePaths &fileList);
- void askUserAboutFailedProjects();
- void sessionLoadingProgress();
-
- bool recursiveDependencyCheck(const FilePath &newDep, const FilePath &checkDep) const;
- FilePaths dependencies(const FilePath &proName) const;
- FilePaths dependenciesOrder() const;
- void dependencies(const FilePath &proName, FilePaths &result) const;
-
- static QString windowTitleAddition(const FilePath &filePath);
- static QString sessionTitle(const FilePath &filePath);
-
- bool hasProjects() const { return !m_projects.isEmpty(); }
-
- QString m_sessionName = QLatin1String(DEFAULT_SESSION);
- bool m_virginSession = true;
- bool m_loadingSession = false;
- bool m_casadeSetActive = false;
-
- mutable QStringList m_sessions;
- mutable QHash<QString, QDateTime> m_sessionDateTimes;
- QHash<QString, QDateTime> m_lastActiveTimes;
-
- Project *m_startupProject = nullptr;
- QList<Project *> m_projects;
- FilePaths m_failedProjects;
- QMap<FilePath, FilePaths> m_depMap;
- QMap<QString, QVariant> m_values;
- QFutureInterface<void> m_future;
- PersistentSettingsWriter *m_writer = nullptr;
-
-private:
- static QString locationInProject(const FilePath &filePath);
-};
-
static SessionManager *m_instance = nullptr;
-static SessionManagerPrivate *d = nullptr;
-
-static QString projectFolderId(Project *pro)
-{
- return pro->projectFilePath().toString();
-}
-
-const int PROJECT_SORT_VALUE = 100;
+SessionManagerPrivate *sb_d = nullptr;
-SessionManager::SessionManager(QObject *parent) : QObject(parent)
+SessionManager::SessionManager()
{
m_instance = this;
- d = new SessionManagerPrivate;
+ sb_d = new SessionManagerPrivate;
connect(ModeManager::instance(), &ModeManager::currentModeChanged,
this, &SessionManager::saveActiveMode);
connect(ICore::instance(), &ICore::saveSettingsRequested, this, [] {
QVariantMap times;
- for (auto it = d->m_lastActiveTimes.cbegin(); it != d->m_lastActiveTimes.cend(); ++it)
+ for (auto it = sb_d->m_lastActiveTimes.cbegin(); it != sb_d->m_lastActiveTimes.cend(); ++it)
times.insert(it.key(), it.value());
ICore::settings()->setValue(LAST_ACTIVE_TIMES_KEY, times);
});
- connect(EditorManager::instance(), &EditorManager::editorCreated,
- this, &SessionManager::configureEditor);
- connect(this, &SessionManager::projectAdded,
- EditorManager::instance(), &EditorManager::updateWindowTitles);
- connect(this, &SessionManager::projectRemoved,
- EditorManager::instance(), &EditorManager::updateWindowTitles);
- connect(this, &SessionManager::projectDisplayNameChanged,
- EditorManager::instance(), &EditorManager::updateWindowTitles);
connect(EditorManager::instance(), &EditorManager::editorOpened,
this, &SessionManager::markSessionFileDirty);
connect(EditorManager::instance(), &EditorManager::editorsClosed,
this, &SessionManager::markSessionFileDirty);
-
- EditorManager::setWindowTitleAdditionHandler(&SessionManagerPrivate::windowTitleAddition);
- EditorManager::setSessionTitleHandler(&SessionManagerPrivate::sessionTitle);
}
SessionManager::~SessionManager()
{
- EditorManager::setWindowTitleAdditionHandler({});
- EditorManager::setSessionTitleHandler({});
- emit m_instance->aboutToUnloadSession(d->m_sessionName);
- delete d->m_writer;
- delete d;
- d = nullptr;
+ emit m_instance->aboutToUnloadSession(sb_d->m_sessionName);
+ delete sb_d->m_writer;
+ delete sb_d;
+ sb_d = nullptr;
}
SessionManager *SessionManager::instance()
@@ -162,7 +88,7 @@ SessionManager *SessionManager::instance()
bool SessionManager::isDefaultVirgin()
{
- return isDefaultSession(d->m_sessionName) && d->m_virginSession;
+ return isDefaultSession(sb_d->m_sessionName) && sb_d->m_virginSession;
}
bool SessionManager::isDefaultSession(const QString &session)
@@ -176,597 +102,9 @@ void SessionManager::saveActiveMode(Id mode)
setValue(QLatin1String("ActiveMode"), mode.toString());
}
-bool SessionManagerPrivate::recursiveDependencyCheck(const FilePath &newDep,
- const FilePath &checkDep) const
-{
- if (newDep == checkDep)
- return false;
-
- const FilePaths depList = m_depMap.value(checkDep);
- for (const FilePath &dependency : depList) {
- if (!recursiveDependencyCheck(newDep, dependency))
- return false;
- }
-
- return true;
-}
-
-/*
- * The dependency management exposes an interface based on projects, but
- * is internally purely string based. This is suboptimal. Probably it would be
- * nicer to map the filenames to projects on load and only map it back to
- * filenames when saving.
- */
-
-QList<Project *> SessionManager::dependencies(const Project *project)
-{
- const FilePath proName = project->projectFilePath();
- const FilePaths proDeps = d->m_depMap.value(proName);
-
- QList<Project *> projects;
- for (const FilePath &dep : proDeps) {
- Project *pro = Utils::findOrDefault(d->m_projects, [&dep](Project *p) {
- return p->projectFilePath() == dep;
- });
- if (pro)
- projects += pro;
- }
-
- return projects;
-}
-
-bool SessionManager::hasDependency(const Project *project, const Project *depProject)
-{
- const FilePath proName = project->projectFilePath();
- const FilePath depName = depProject->projectFilePath();
-
- const FilePaths proDeps = d->m_depMap.value(proName);
- return proDeps.contains(depName);
-}
-
-bool SessionManager::canAddDependency(const Project *project, const Project *depProject)
-{
- const FilePath newDep = project->projectFilePath();
- const FilePath checkDep = depProject->projectFilePath();
-
- return d->recursiveDependencyCheck(newDep, checkDep);
-}
-
-bool SessionManager::addDependency(Project *project, Project *depProject)
-{
- const FilePath proName = project->projectFilePath();
- const FilePath depName = depProject->projectFilePath();
-
- // check if this dependency is valid
- if (!d->recursiveDependencyCheck(proName, depName))
- return false;
-
- FilePaths proDeps = d->m_depMap.value(proName);
- if (!proDeps.contains(depName)) {
- proDeps.append(depName);
- d->m_depMap[proName] = proDeps;
- }
- emit m_instance->dependencyChanged(project, depProject);
-
- return true;
-}
-
-void SessionManager::removeDependency(Project *project, Project *depProject)
-{
- const FilePath proName = project->projectFilePath();
- const FilePath depName = depProject->projectFilePath();
-
- FilePaths proDeps = d->m_depMap.value(proName);
- proDeps.removeAll(depName);
- if (proDeps.isEmpty())
- d->m_depMap.remove(proName);
- else
- d->m_depMap[proName] = proDeps;
- emit m_instance->dependencyChanged(project, depProject);
-}
-
-bool SessionManager::isProjectConfigurationCascading()
-{
- return d->m_casadeSetActive;
-}
-
-void SessionManager::setProjectConfigurationCascading(bool b)
-{
- d->m_casadeSetActive = b;
- markSessionFileDirty();
-}
-
-void SessionManager::setActiveTarget(Project *project, Target *target, SetActive cascade)
-{
- QTC_ASSERT(project, return);
-
- if (project->isShuttingDown())
- return;
-
- project->setActiveTarget(target);
-
- if (!target) // never cascade setting no target
- return;
-
- if (cascade != SetActive::Cascade || !d->m_casadeSetActive)
- return;
-
- Utils::Id kitId = target->kit()->id();
- for (Project *otherProject : SessionManager::projects()) {
- if (otherProject == project)
- continue;
- if (Target *otherTarget = Utils::findOrDefault(otherProject->targets(),
- [kitId](Target *t) { return t->kit()->id() == kitId; }))
- otherProject->setActiveTarget(otherTarget);
- }
-}
-
-void SessionManager::setActiveBuildConfiguration(Target *target, BuildConfiguration *bc, SetActive cascade)
-{
- QTC_ASSERT(target, return);
- QTC_ASSERT(target->project(), return);
-
- if (target->project()->isShuttingDown() || target->isShuttingDown())
- return;
-
- target->setActiveBuildConfiguration(bc);
-
- if (!bc)
- return;
- if (cascade != SetActive::Cascade || !d->m_casadeSetActive)
- return;
-
- Utils::Id kitId = target->kit()->id();
- QString name = bc->displayName(); // We match on displayname
- for (Project *otherProject : SessionManager::projects()) {
- if (otherProject == target->project())
- continue;
- Target *otherTarget = otherProject->activeTarget();
- if (!otherTarget || otherTarget->kit()->id() != kitId)
- continue;
-
- for (BuildConfiguration *otherBc : otherTarget->buildConfigurations()) {
- if (otherBc->displayName() == name) {
- otherTarget->setActiveBuildConfiguration(otherBc);
- break;
- }
- }
- }
-}
-
-void SessionManager::setActiveDeployConfiguration(Target *target, DeployConfiguration *dc, SetActive cascade)
-{
- QTC_ASSERT(target, return);
- QTC_ASSERT(target->project(), return);
-
- if (target->project()->isShuttingDown() || target->isShuttingDown())
- return;
-
- target->setActiveDeployConfiguration(dc);
-
- if (!dc)
- return;
- if (cascade != SetActive::Cascade || !d->m_casadeSetActive)
- return;
-
- Utils::Id kitId = target->kit()->id();
- QString name = dc->displayName(); // We match on displayname
- for (Project *otherProject : SessionManager::projects()) {
- if (otherProject == target->project())
- continue;
- Target *otherTarget = otherProject->activeTarget();
- if (!otherTarget || otherTarget->kit()->id() != kitId)
- continue;
-
- for (DeployConfiguration *otherDc : otherTarget->deployConfigurations()) {
- if (otherDc->displayName() == name) {
- otherTarget->setActiveDeployConfiguration(otherDc);
- break;
- }
- }
- }
-}
-
-void SessionManager::setStartupProject(Project *startupProject)
-{
- QTC_ASSERT((!startupProject && d->m_projects.isEmpty())
- || (startupProject && d->m_projects.contains(startupProject)), return);
-
- if (d->m_startupProject == startupProject)
- return;
-
- d->m_startupProject = startupProject;
- if (d->m_startupProject && d->m_startupProject->needsConfiguration()) {
- ModeManager::activateMode(Constants::MODE_SESSION);
- ModeManager::setFocusToCurrentMode();
- }
- FolderNavigationWidgetFactory::setFallbackSyncFilePath(
- startupProject ? startupProject->projectFilePath().parentDir() : FilePath());
- emit m_instance->startupProjectChanged(startupProject);
-}
-
-Project *SessionManager::startupProject()
-{
- return d->m_startupProject;
-}
-
-Target *SessionManager::startupTarget()
-{
- return d->m_startupProject ? d->m_startupProject->activeTarget() : nullptr;
-}
-
-BuildSystem *SessionManager::startupBuildSystem()
-{
- Target *t = startupTarget();
- return t ? t->buildSystem() : nullptr;
-}
-
-/*!
- * Returns the RunConfiguration of the currently active target
- * of the startup project, if such exists, or \c nullptr otherwise.
- */
-
-
-RunConfiguration *SessionManager::startupRunConfiguration()
-{
- Target *t = startupTarget();
- return t ? t->activeRunConfiguration() : nullptr;
-}
-
-void SessionManager::addProject(Project *pro)
-{
- QTC_ASSERT(pro, return);
- QTC_CHECK(!pro->displayName().isEmpty());
- QTC_CHECK(pro->id().isValid());
-
- d->m_virginSession = false;
- QTC_ASSERT(!d->m_projects.contains(pro), return);
-
- d->m_projects.append(pro);
-
- connect(pro, &Project::displayNameChanged,
- m_instance, [pro]() { emit m_instance->projectDisplayNameChanged(pro); });
-
- emit m_instance->projectAdded(pro);
- const auto updateFolderNavigation = [pro] {
- // destructing projects might trigger changes, so check if the project is actually there
- if (QTC_GUARD(d->m_projects.contains(pro))) {
- const QIcon icon = pro->rootProjectNode() ? pro->rootProjectNode()->icon() : QIcon();
- FolderNavigationWidgetFactory::insertRootDirectory({projectFolderId(pro),
- PROJECT_SORT_VALUE,
- pro->displayName(),
- pro->projectFilePath().parentDir(),
- icon});
- }
- };
- updateFolderNavigation();
- configureEditors(pro);
- connect(pro, &Project::fileListChanged, m_instance, [pro, updateFolderNavigation]() {
- configureEditors(pro);
- updateFolderNavigation(); // update icon
- });
- connect(pro, &Project::displayNameChanged, m_instance, updateFolderNavigation);
-
- if (!startupProject())
- setStartupProject(pro);
-}
-
-void SessionManager::removeProject(Project *project)
-{
- d->m_virginSession = false;
- QTC_ASSERT(project, return);
- removeProjects({project});
-}
-
bool SessionManager::loadingSession()
{
- return d->m_loadingSession;
-}
-
-bool SessionManager::save()
-{
- emit m_instance->aboutToSaveSession();
-
- const FilePath filePath = sessionNameToFileName(d->m_sessionName);
- QVariantMap data;
-
- // See the explanation at loadSession() for how we handle the implicit default session.
- if (isDefaultVirgin()) {
- if (filePath.exists()) {
- PersistentSettingsReader reader;
- if (!reader.load(filePath)) {
- QMessageBox::warning(ICore::dialogParent(), Tr::tr("Error while saving session"),
- Tr::tr("Could not save session %1").arg(filePath.toUserOutput()));
- return false;
- }
- data = reader.restoreValues();
- }
- } else {
- // save the startup project
- if (d->m_startupProject)
- data.insert("StartupProject", d->m_startupProject->projectFilePath().toSettings());
-
- const QColor c = StyleHelper::requestedBaseColor();
- if (c.isValid()) {
- QString tmp = QString::fromLatin1("#%1%2%3")
- .arg(c.red(), 2, 16, QLatin1Char('0'))
- .arg(c.green(), 2, 16, QLatin1Char('0'))
- .arg(c.blue(), 2, 16, QLatin1Char('0'));
- data.insert(QLatin1String("Color"), tmp);
- }
-
- FilePaths projectFiles = Utils::transform(projects(), &Project::projectFilePath);
- // Restore information on projects that failed to load:
- // don't read projects to the list, which the user loaded
- for (const FilePath &failed : std::as_const(d->m_failedProjects)) {
- if (!projectFiles.contains(failed))
- projectFiles << failed;
- }
-
- data.insert("ProjectList", Utils::transform<QStringList>(projectFiles,
- &FilePath::toString));
- data.insert("CascadeSetActive", d->m_casadeSetActive);
-
- QVariantMap depMap;
- auto i = d->m_depMap.constBegin();
- while (i != d->m_depMap.constEnd()) {
- QString key = i.key().toString();
- QStringList values;
- const FilePaths valueList = i.value();
- for (const FilePath &value : valueList)
- values << value.toString();
- depMap.insert(key, values);
- ++i;
- }
- data.insert(QLatin1String("ProjectDependencies"), QVariant(depMap));
- data.insert(QLatin1String("EditorSettings"), EditorManager::saveState().toBase64());
- }
-
- const auto end = d->m_values.constEnd();
- QStringList keys;
- for (auto it = d->m_values.constBegin(); it != end; ++it) {
- data.insert(QLatin1String("value-") + it.key(), it.value());
- keys << it.key();
- }
- data.insert(QLatin1String("valueKeys"), keys);
-
- if (!d->m_writer || d->m_writer->fileName() != filePath) {
- delete d->m_writer;
- d->m_writer = new PersistentSettingsWriter(filePath, "QtCreatorSession");
- }
- const bool result = d->m_writer->save(data, ICore::dialogParent());
- if (result) {
- if (!isDefaultVirgin())
- d->m_sessionDateTimes.insert(activeSession(), QDateTime::currentDateTime());
- } else {
- QMessageBox::warning(ICore::dialogParent(), Tr::tr("Error while saving session"),
- Tr::tr("Could not save session to file %1").arg(d->m_writer->fileName().toUserOutput()));
- }
-
- return result;
-}
-
-/*!
- Closes all projects
- */
-void SessionManager::closeAllProjects()
-{
- removeProjects(projects());
-}
-
-const QList<Project *> SessionManager::projects()
-{
- return d->m_projects;
-}
-
-bool SessionManager::hasProjects()
-{
- return d->hasProjects();
-}
-
-bool SessionManager::hasProject(Project *p)
-{
- return d->m_projects.contains(p);
-}
-
-FilePaths SessionManagerPrivate::dependencies(const FilePath &proName) const
-{
- FilePaths result;
- dependencies(proName, result);
- return result;
-}
-
-void SessionManagerPrivate::dependencies(const FilePath &proName, FilePaths &result) const
-{
- const FilePaths depends = m_depMap.value(proName);
-
- for (const FilePath &dep : depends)
- dependencies(dep, result);
-
- if (!result.contains(proName))
- result.append(proName);
-}
-
-QString SessionManagerPrivate::sessionTitle(const FilePath &filePath)
-{
- if (SessionManager::isDefaultSession(d->m_sessionName)) {
- if (filePath.isEmpty()) {
- // use single project's name if there is only one loaded.
- const QList<Project *> projects = SessionManager::projects();
- if (projects.size() == 1)
- return projects.first()->displayName();
- }
- } else {
- QString sessionName = d->m_sessionName;
- if (sessionName.isEmpty())
- sessionName = Tr::tr("Untitled");
- return sessionName;
- }
- return QString();
-}
-
-QString SessionManagerPrivate::locationInProject(const FilePath &filePath) {
- const Project *project = SessionManager::projectForFile(filePath);
- if (!project)
- return QString();
-
- const FilePath parentDir = filePath.parentDir();
- if (parentDir == project->projectDirectory())
- return "@ " + project->displayName();
-
- if (filePath.isChildOf(project->projectDirectory())) {
- const FilePath dirInProject = parentDir.relativeChildPath(project->projectDirectory());
- return "(" + dirInProject.toUserOutput() + " @ " + project->displayName() + ")";
- }
-
- // For a file that is "outside" the project it belongs to, we display its
- // dir's full path because it is easier to read than a series of "../../.".
- // Example: /home/hugo/GenericProject/App.files lists /home/hugo/lib/Bar.cpp
- return "(" + parentDir.toUserOutput() + " @ " + project->displayName() + ")";
-}
-
-QString SessionManagerPrivate::windowTitleAddition(const FilePath &filePath)
-{
- return filePath.isEmpty() ? QString() : locationInProject(filePath);
-}
-
-FilePaths SessionManagerPrivate::dependenciesOrder() const
-{
- QList<QPair<FilePath, FilePaths>> unordered;
- FilePaths ordered;
-
- // copy the map to a temporary list
- for (const Project *pro : m_projects) {
- const FilePath proName = pro->projectFilePath();
- const FilePaths depList = filtered(m_depMap.value(proName),
- [this](const FilePath &proPath) {
- return contains(m_projects, [proPath](const Project *p) {
- return p->projectFilePath() == proPath;
- });
- });
- unordered.push_back({proName, depList});
- }
-
- while (!unordered.isEmpty()) {
- for (int i = (unordered.count() - 1); i >= 0; --i) {
- if (unordered.at(i).second.isEmpty()) {
- ordered << unordered.at(i).first;
- unordered.removeAt(i);
- }
- }
-
- // remove the handled projects from the dependency lists
- // of the remaining unordered projects
- for (int i = 0; i < unordered.count(); ++i) {
- for (const FilePath &pro : std::as_const(ordered)) {
- FilePaths depList = unordered.at(i).second;
- depList.removeAll(pro);
- unordered[i].second = depList;
- }
- }
- }
-
- return ordered;
-}
-
-QList<Project *> SessionManager::projectOrder(const Project *project)
-{
- QList<Project *> result;
-
- FilePaths pros;
- if (project)
- pros = d->dependencies(project->projectFilePath());
- else
- pros = d->dependenciesOrder();
-
- for (const FilePath &proFile : std::as_const(pros)) {
- for (Project *pro : projects()) {
- if (pro->projectFilePath() == proFile) {
- result << pro;
- break;
- }
- }
- }
-
- return result;
-}
-
-Project *SessionManager::projectForFile(const FilePath &fileName)
-{
- if (Project * const project = Utils::findOrDefault(SessionManager::projects(),
- [&fileName](const Project *p) { return p->isKnownFile(fileName); })) {
- return project;
- }
- return Utils::findOrDefault(SessionManager::projects(),
- [&fileName](const Project *p) {
- for (const Target * const target : p->targets()) {
- for (const BuildConfiguration * const bc : target->buildConfigurations()) {
- if (fileName.isChildOf(bc->buildDirectory()))
- return false;
- }
- }
- return fileName.isChildOf(p->projectDirectory());
- });
-}
-
-Project *SessionManager::projectWithProjectFilePath(const FilePath &filePath)
-{
- return Utils::findOrDefault(SessionManager::projects(),
- [&filePath](const Project *p) { return p->projectFilePath() == filePath; });
-}
-
-void SessionManager::configureEditor(IEditor *editor, const QString &fileName)
-{
- if (auto textEditor = qobject_cast<TextEditor::BaseTextEditor*>(editor)) {
- Project *project = projectForFile(Utils::FilePath::fromString(fileName));
- // Global settings are the default.
- if (project)
- project->editorConfiguration()->configureEditor(textEditor);
- }
-}
-
-void SessionManager::configureEditors(Project *project)
-{
- const QList<IDocument *> documents = DocumentModel::openedDocuments();
- for (IDocument *document : documents) {
- if (project->isKnownFile(document->filePath())) {
- const QList<IEditor *> editors = DocumentModel::editorsForDocument(document);
- for (IEditor *editor : editors) {
- if (auto textEditor = qobject_cast<TextEditor::BaseTextEditor*>(editor)) {
- project->editorConfiguration()->configureEditor(textEditor);
- }
- }
- }
- }
-}
-
-void SessionManager::removeProjects(const QList<Project *> &remove)
-{
- for (Project *pro : remove)
- emit m_instance->aboutToRemoveProject(pro);
-
- bool changeStartupProject = false;
-
- // Delete projects
- for (Project *pro : remove) {
- pro->saveSettings();
- pro->markAsShuttingDown();
-
- // Remove the project node:
- d->m_projects.removeOne(pro);
-
- if (pro == d->m_startupProject)
- changeStartupProject = true;
-
- FolderNavigationWidgetFactory::removeRootDirectory(projectFolderId(pro));
- disconnect(pro, nullptr, m_instance, nullptr);
- emit m_instance->projectRemoved(pro);
- }
-
- if (changeStartupProject)
- setStartupProject(hasProjects() ? projects().first() : nullptr);
-
- qDeleteAll(remove);
+ return sb_d->m_loadingSession;
}
/*!
@@ -775,52 +113,52 @@ void SessionManager::removeProjects(const QList<Project *> &remove)
void SessionManager::setValue(const QString &name, const QVariant &value)
{
- if (d->m_values.value(name) == value)
+ if (sb_d->m_values.value(name) == value)
return;
- d->m_values.insert(name, value);
+ sb_d->m_values.insert(name, value);
}
QVariant SessionManager::value(const QString &name)
{
- auto it = d->m_values.constFind(name);
- return (it == d->m_values.constEnd()) ? QVariant() : *it;
+ auto it = sb_d->m_values.constFind(name);
+ return (it == sb_d->m_values.constEnd()) ? QVariant() : *it;
}
QString SessionManager::activeSession()
{
- return d->m_sessionName;
+ return sb_d->m_sessionName;
}
QStringList SessionManager::sessions()
{
- if (d->m_sessions.isEmpty()) {
+ if (sb_d->m_sessions.isEmpty()) {
// We are not initialized yet, so do that now
const FilePaths sessionFiles =
ICore::userResourcePath().dirEntries({{"*qws"}}, QDir::Time | QDir::Reversed);
const QVariantMap lastActiveTimes = ICore::settings()->value(LAST_ACTIVE_TIMES_KEY).toMap();
for (const FilePath &file : sessionFiles) {
const QString &name = file.completeBaseName();
- d->m_sessionDateTimes.insert(name, file.lastModified());
+ sb_d->m_sessionDateTimes.insert(name, file.lastModified());
const auto lastActiveTime = lastActiveTimes.find(name);
- d->m_lastActiveTimes.insert(name, lastActiveTime != lastActiveTimes.end()
+ sb_d->m_lastActiveTimes.insert(name, lastActiveTime != lastActiveTimes.end()
? lastActiveTime->toDateTime()
: file.lastModified());
if (name != QLatin1String(DEFAULT_SESSION))
- d->m_sessions << name;
+ sb_d->m_sessions << name;
}
- d->m_sessions.prepend(QLatin1String(DEFAULT_SESSION));
+ sb_d->m_sessions.prepend(QLatin1String(DEFAULT_SESSION));
}
- return d->m_sessions;
+ return sb_d->m_sessions;
}
QDateTime SessionManager::sessionDateTime(const QString &session)
{
- return d->m_sessionDateTimes.value(session);
+ return sb_d->m_sessionDateTimes.value(session);
}
QDateTime SessionManager::lastActiveTime(const QString &session)
{
- return d->m_lastActiveTimes.value(session);
+ return sb_d->m_lastActiveTimes.value(session);
}
FilePath SessionManager::sessionNameToFileName(const QString &session)
@@ -836,9 +174,9 @@ bool SessionManager::createSession(const QString &session)
{
if (sessions().contains(session))
return false;
- Q_ASSERT(d->m_sessions.size() > 0);
- d->m_sessions.insert(1, session);
- d->m_lastActiveTimes.insert(session, QDateTime::currentDateTime());
+ Q_ASSERT(sb_d->m_sessions.size() > 0);
+ sb_d->m_sessions.insert(1, session);
+ sb_d->m_lastActiveTimes.insert(session, QDateTime::currentDateTime());
return true;
}
@@ -847,7 +185,7 @@ bool SessionManager::renameSession(const QString &original, const QString &newNa
if (!cloneSession(original, newName))
return false;
if (original == activeSession())
- loadSession(newName);
+ ProjectManager::loadSession(newName);
emit instance()->sessionRenamed(original, newName);
return deleteSession(original);
}
@@ -873,10 +211,10 @@ bool SessionManager::confirmSessionDelete(const QStringList &sessions)
*/
bool SessionManager::deleteSession(const QString &session)
{
- if (!d->m_sessions.contains(session))
+ if (!sb_d->m_sessions.contains(session))
return false;
- d->m_sessions.removeOne(session);
- d->m_lastActiveTimes.remove(session);
+ sb_d->m_sessions.removeOne(session);
+ sb_d->m_lastActiveTimes.remove(session);
emit instance()->sessionRemoved(session);
FilePath sessionFile = sessionNameToFileName(session);
if (sessionFile.exists())
@@ -892,14 +230,14 @@ void SessionManager::deleteSessions(const QStringList &sessions)
bool SessionManager::cloneSession(const QString &original, const QString &clone)
{
- if (!d->m_sessions.contains(original))
+ if (!sb_d->m_sessions.contains(original))
return false;
FilePath sessionFile = sessionNameToFileName(original);
// If the file does not exist, we can still clone
if (!sessionFile.exists() || sessionFile.copyFile(sessionNameToFileName(clone))) {
- d->m_sessions.insert(1, clone);
- d->m_sessionDateTimes.insert(clone, sessionNameToFileName(clone).lastModified());
+ sb_d->m_sessions.insert(1, clone);
+ sb_d->m_sessionDateTimes.insert(clone, sessionNameToFileName(clone).lastModified());
return true;
}
return false;
@@ -914,61 +252,6 @@ void SessionManagerPrivate::restoreValues(const PersistentSettingsReader &reader
}
}
-void SessionManagerPrivate::restoreDependencies(const PersistentSettingsReader &reader)
-{
- QMap<QString, QVariant> depMap = reader.restoreValue(QLatin1String("ProjectDependencies")).toMap();
- auto i = depMap.constBegin();
- while (i != depMap.constEnd()) {
- const QString &key = i.key();
- FilePaths values;
- const QStringList valueList = i.value().toStringList();
- for (const QString &value : valueList)
- values << FilePath::fromString(value);
- m_depMap.insert(FilePath::fromString(key), values);
- ++i;
- }
-}
-
-void SessionManagerPrivate::askUserAboutFailedProjects()
-{
- FilePaths failedProjects = m_failedProjects;
- if (!failedProjects.isEmpty()) {
- QString fileList = FilePath::formatFilePaths(failedProjects, "<br>");
- QMessageBox box(QMessageBox::Warning,
- Tr::tr("Failed to restore project files"),
- Tr::tr("Could not restore the following project files:<br><b>%1</b>").
- arg(fileList));
- auto keepButton = new QPushButton(Tr::tr("Keep projects in Session"), &box);
- auto removeButton = new QPushButton(Tr::tr("Remove projects from Session"), &box);
- box.addButton(keepButton, QMessageBox::AcceptRole);
- box.addButton(removeButton, QMessageBox::DestructiveRole);
-
- box.exec();
-
- if (box.clickedButton() == removeButton)
- m_failedProjects.clear();
- }
-}
-
-void SessionManagerPrivate::restoreStartupProject(const PersistentSettingsReader &reader)
-{
- const FilePath startupProject = FilePath::fromSettings(reader.restoreValue("StartupProject"));
- if (!startupProject.isEmpty()) {
- for (Project *pro : std::as_const(m_projects)) {
- if (pro->projectFilePath() == startupProject) {
- m_instance->setStartupProject(pro);
- break;
- }
- }
- }
- if (!m_startupProject) {
- if (!startupProject.isEmpty())
- qWarning() << "Could not find startup project" << startupProject;
- if (hasProjects())
- m_instance->setStartupProject(m_projects.first());
- }
-}
-
void SessionManagerPrivate::restoreEditors(const PersistentSettingsReader &reader)
{
const QVariant editorsettings = reader.restoreValue(QLatin1String("EditorSettings"));
@@ -979,186 +262,6 @@ void SessionManagerPrivate::restoreEditors(const PersistentSettingsReader &reade
}
/*!
- Loads a session, takes a session name (not filename).
-*/
-void SessionManagerPrivate::restoreProjects(const FilePaths &fileList)
-{
- // indirectly adds projects to session
- // Keep projects that failed to load in the session!
- m_failedProjects = fileList;
- if (!fileList.isEmpty()) {
- ProjectExplorerPlugin::OpenProjectResult result = ProjectExplorerPlugin::openProjects(fileList);
- if (!result)
- ProjectExplorerPlugin::showOpenProjectError(result);
- const QList<Project *> projects = result.projects();
- for (const Project *p : projects)
- m_failedProjects.removeAll(p->projectFilePath());
- }
-}
-
-/*
- * ========== Notes on storing and loading the default session ==========
- * The default session comes in two flavors: implicit and explicit. The implicit one,
- * also referred to as "default virgin" in the code base, is the one that is active
- * at start-up, if no session has been explicitly loaded due to command-line arguments
- * or the "restore last session" setting in the session manager.
- * The implicit default session silently turns into the explicit default session
- * by loading a project or a file or changing settings in the Dependencies panel. The explicit
- * default session can also be loaded by the user via the Welcome Screen.
- * This mechanism somewhat complicates the handling of session-specific settings such as
- * the ones in the task pane: Users expect that changes they make there become persistent, even
- * when they are in the implicit default session. However, we can't just blindly store
- * the implicit default session, because then we'd overwrite the project list of the explicit
- * default session. Therefore, we use the following logic:
- * - Upon start-up, if no session is to be explicitly loaded, we restore the parts of the
- * explicit default session that are not related to projects, editors etc; the
- * "general settings" of the session, so to speak.
- * - When storing the implicit default session, we overwrite only these "general settings"
- * of the explicit default session and keep the others as they are.
- * - When switching from the implicit to the explicit default session, we keep the
- * "general settings" and load everything else from the session file.
- * This guarantees that user changes are properly transferred and nothing gets lost from
- * either the implicit or the explicit default session.
- *
- */
-bool SessionManager::loadSession(const QString &session, bool initial)
-{
- const bool loadImplicitDefault = session.isEmpty();
- const bool switchFromImplicitToExplicitDefault = session == DEFAULT_SESSION
- && d->m_sessionName == DEFAULT_SESSION && !initial;
-
- // Do nothing if we have that session already loaded,
- // exception if the session is the default virgin session
- // we still want to be able to load the default session
- if (session == d->m_sessionName && !isDefaultVirgin())
- return true;
-
- if (!loadImplicitDefault && !sessions().contains(session))
- return false;
-
- FilePaths fileList;
- // Try loading the file
- FilePath fileName = sessionNameToFileName(loadImplicitDefault ? DEFAULT_SESSION : session);
- PersistentSettingsReader reader;
- if (fileName.exists()) {
- if (!reader.load(fileName)) {
- QMessageBox::warning(ICore::dialogParent(), Tr::tr("Error while restoring session"),
- Tr::tr("Could not restore session %1").arg(fileName.toUserOutput()));
-
- return false;
- }
-
- if (loadImplicitDefault) {
- d->restoreValues(reader);
- emit m_instance->sessionLoaded(DEFAULT_SESSION);
- return true;
- }
-
- fileList = FileUtils::toFilePathList(reader.restoreValue("ProjectList").toStringList());
- } else if (loadImplicitDefault) {
- return true;
- }
-
- d->m_loadingSession = true;
-
- // Allow everyone to set something in the session and before saving
- emit m_instance->aboutToUnloadSession(d->m_sessionName);
-
- if (!save()) {
- d->m_loadingSession = false;
- return false;
- }
-
- // Clean up
- if (!EditorManager::closeAllEditors()) {
- d->m_loadingSession = false;
- return false;
- }
-
- // find a list of projects to close later
- const QList<Project *> projectsToRemove = Utils::filtered(projects(), [&fileList](Project *p) {
- return !fileList.contains(p->projectFilePath());
- });
- const QList<Project *> openProjects = projects();
- const FilePaths projectPathsToLoad = Utils::filtered(fileList, [&openProjects](const FilePath &path) {
- return !Utils::contains(openProjects, [&path](Project *p) {
- return p->projectFilePath() == path;
- });
- });
- d->m_failedProjects.clear();
- d->m_depMap.clear();
- if (!switchFromImplicitToExplicitDefault)
- d->m_values.clear();
- d->m_casadeSetActive = false;
-
- d->m_sessionName = session;
- delete d->m_writer;
- d->m_writer = nullptr;
- EditorManager::updateWindowTitles();
-
- if (fileName.exists()) {
- d->m_virginSession = false;
-
- ProgressManager::addTask(d->m_future.future(), Tr::tr("Loading Session"),
- "ProjectExplorer.SessionFile.Load");
-
- d->m_future.setProgressRange(0, 1);
- d->m_future.setProgressValue(0);
-
- if (!switchFromImplicitToExplicitDefault)
- d->restoreValues(reader);
- emit m_instance->aboutToLoadSession(session);
-
- // retrieve all values before the following code could change them again
- Id modeId = Id::fromSetting(value(QLatin1String("ActiveMode")));
- if (!modeId.isValid())
- modeId = Id(Core::Constants::MODE_EDIT);
-
- QColor c = QColor(reader.restoreValue(QLatin1String("Color")).toString());
- if (c.isValid())
- StyleHelper::setBaseColor(c);
-
- d->m_future.setProgressRange(0, projectPathsToLoad.count() + 1/*initialization above*/ + 1/*editors*/);
- d->m_future.setProgressValue(1);
- QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
-
- d->restoreProjects(projectPathsToLoad);
- d->sessionLoadingProgress();
- d->restoreDependencies(reader);
- d->restoreStartupProject(reader);
-
- removeProjects(projectsToRemove); // only remove old projects now that the startup project is set!
-
- d->restoreEditors(reader);
-
- d->m_future.reportFinished();
- d->m_future = QFutureInterface<void>();
-
- // Fall back to Project mode if the startup project is unconfigured and
- // use the mode saved in the session otherwise
- if (d->m_startupProject && d->m_startupProject->needsConfiguration())
- modeId = Id(Constants::MODE_SESSION);
-
- ModeManager::activateMode(modeId);
- ModeManager::setFocusToCurrentMode();
- } else {
- removeProjects(projects());
- ModeManager::activateMode(Id(Core::Constants::MODE_EDIT));
- ModeManager::setFocusToCurrentMode();
- }
-
- d->m_casadeSetActive = reader.restoreValue(QLatin1String("CascadeSetActive"), false).toBool();
- d->m_lastActiveTimes.insert(session, QDateTime::currentDateTime());
-
- emit m_instance->sessionLoaded(session);
-
- // Starts a event loop, better do that at the very end
- d->askUserAboutFailedProjects();
- d->m_loadingSession = false;
- return true;
-}
-
-/*!
Returns the last session that was opened by the user.
*/
QString SessionManager::lastSession()
@@ -1174,14 +277,9 @@ QString SessionManager::startupSession()
return ICore::settings()->value(Constants::STARTUPSESSION_KEY).toString();
}
-void SessionManager::reportProjectLoadingProgress()
-{
- d->sessionLoadingProgress();
-}
-
void SessionManager::markSessionFileDirty()
{
- d->m_virginSession = false;
+ sb_d->m_virginSession = false;
}
void SessionManagerPrivate::sessionLoadingProgress()
@@ -1190,73 +288,4 @@ void SessionManagerPrivate::sessionLoadingProgress()
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
}
-FilePaths SessionManager::projectsForSessionName(const QString &session)
-{
- const FilePath fileName = sessionNameToFileName(session);
- PersistentSettingsReader reader;
- if (fileName.exists()) {
- if (!reader.load(fileName)) {
- qWarning() << "Could not restore session" << fileName.toUserOutput();
- return {};
- }
- }
- return transform(reader.restoreValue(QLatin1String("ProjectList")).toStringList(),
- &FilePath::fromUserInput);
-}
-
-#ifdef WITH_TESTS
-
-void ProjectExplorerPlugin::testSessionSwitch()
-{
- QVERIFY(SessionManager::createSession("session1"));
- QVERIFY(SessionManager::createSession("session2"));
- QTemporaryFile cppFile("main.cpp");
- QVERIFY(cppFile.open());
- cppFile.close();
- QTemporaryFile projectFile1("XXXXXX.pro");
- QTemporaryFile projectFile2("XXXXXX.pro");
- struct SessionSpec {
- SessionSpec(const QString &n, QTemporaryFile &f) : name(n), projectFile(f) {}
- const QString name;
- QTemporaryFile &projectFile;
- };
- std::vector<SessionSpec> sessionSpecs{SessionSpec("session1", projectFile1),
- SessionSpec("session2", projectFile2)};
- for (const SessionSpec &sessionSpec : sessionSpecs) {
- static const QByteArray proFileContents
- = "TEMPLATE = app\n"
- "CONFIG -= qt\n"
- "SOURCES = " + cppFile.fileName().toLocal8Bit();
- QVERIFY(sessionSpec.projectFile.open());
- sessionSpec.projectFile.write(proFileContents);
- sessionSpec.projectFile.close();
- QVERIFY(SessionManager::loadSession(sessionSpec.name));
- const OpenProjectResult openResult
- = ProjectExplorerPlugin::openProject(
- FilePath::fromString(sessionSpec.projectFile.fileName()));
- if (openResult.errorMessage().contains("text/plain"))
- QSKIP("This test requires the presence of QmakeProjectManager to be fully functional");
- QVERIFY(openResult);
- QCOMPARE(openResult.projects().count(), 1);
- QVERIFY(openResult.project());
- QCOMPARE(SessionManager::projects().count(), 1);
- }
- for (int i = 0; i < 30; ++i) {
- QVERIFY(SessionManager::loadSession("session1"));
- QCOMPARE(SessionManager::activeSession(), "session1");
- QCOMPARE(SessionManager::projects().count(), 1);
- QVERIFY(SessionManager::loadSession("session2"));
- QCOMPARE(SessionManager::activeSession(), "session2");
- QCOMPARE(SessionManager::projects().count(), 1);
- }
- QVERIFY(SessionManager::loadSession("session1"));
- SessionManager::closeAllProjects();
- QVERIFY(SessionManager::loadSession("session2"));
- SessionManager::closeAllProjects();
- QVERIFY(SessionManager::deleteSession("session1"));
- QVERIFY(SessionManager::deleteSession("session2"));
-}
-
-#endif // WITH_TESTS
-
} // namespace ProjectExplorer
diff --git a/src/plugins/projectexplorer/session.h b/src/plugins/projectexplorer/session.h
index a29e478046b..d42868f129b 100644
--- a/src/plugins/projectexplorer/session.h
+++ b/src/plugins/projectexplorer/session.h
@@ -5,6 +5,9 @@
#include "projectexplorer_export.h"
+// FIXME: Remove once dowstream is adjusted.
+#include "projectmanager.h"
+
#include <utils/id.h>
#include <utils/persistentsettings.h>
@@ -12,25 +15,14 @@
#include <QString>
#include <QStringList>
-namespace Core { class IEditor; }
-
namespace ProjectExplorer {
-class Project;
-class Target;
-class BuildConfiguration;
-class BuildSystem;
-class DeployConfiguration;
-class RunConfiguration;
-
-enum class SetActive { Cascade, NoCascade };
-
class PROJECTEXPLORER_EXPORT SessionManager : public QObject
{
Q_OBJECT
public:
- explicit SessionManager(QObject *parent = nullptr);
+ SessionManager();
~SessionManager() override;
static SessionManager *instance();
@@ -52,39 +44,7 @@ public:
static bool cloneSession(const QString &original, const QString &clone);
static bool renameSession(const QString &original, const QString &newName);
- static bool loadSession(const QString &session, bool initial = false);
-
- static bool save();
- static void closeAllProjects();
-
- static void addProject(Project *project);
- static void removeProject(Project *project);
- static void removeProjects(const QList<Project *> &remove);
-
- static void setStartupProject(Project *startupProject);
-
- static QList<Project *> dependencies(const Project *project);
- static bool hasDependency(const Project *project, const Project *depProject);
- static bool canAddDependency(const Project *project, const Project *depProject);
- static bool addDependency(Project *project, Project *depProject);
- static void removeDependency(Project *project, Project *depProject);
-
- static bool isProjectConfigurationCascading();
- static void setProjectConfigurationCascading(bool b);
-
- static void setActiveTarget(Project *p, Target *t, SetActive cascade);
- static void setActiveBuildConfiguration(Target *t, BuildConfiguration *bc, SetActive cascade);
- static void setActiveDeployConfiguration(Target *t, DeployConfiguration *dc, SetActive cascade);
-
static Utils::FilePath sessionNameToFileName(const QString &session);
- static Project *startupProject();
- static Target *startupTarget();
- static BuildSystem *startupBuildSystem();
- static RunConfiguration *startupRunConfiguration();
-
- static const QList<Project *> projects();
- static bool hasProjects();
- static bool hasProject(Project *p);
static bool isDefaultVirgin();
static bool isDefaultSession(const QString &session);
@@ -93,44 +53,20 @@ public:
static void setValue(const QString &name, const QVariant &value);
static QVariant value(const QString &name);
- // NBS rewrite projectOrder (dependency management)
- static QList<Project *> projectOrder(const Project *project = nullptr);
-
- static Project *projectForFile(const Utils::FilePath &fileName);
- static Project *projectWithProjectFilePath(const Utils::FilePath &filePath);
-
- static Utils::FilePaths projectsForSessionName(const QString &session);
-
- static void reportProjectLoadingProgress();
static bool loadingSession();
+ static void markSessionFileDirty();
signals:
- void targetAdded(ProjectExplorer::Target *target);
- void targetRemoved(ProjectExplorer::Target *target);
- void projectAdded(ProjectExplorer::Project *project);
- void aboutToRemoveProject(ProjectExplorer::Project *project);
- void projectDisplayNameChanged(ProjectExplorer::Project *project);
- void projectRemoved(ProjectExplorer::Project *project);
-
- void startupProjectChanged(ProjectExplorer::Project *project);
-
void aboutToUnloadSession(QString sessionName);
void aboutToLoadSession(QString sessionName);
void sessionLoaded(QString sessionName);
void aboutToSaveSession();
- void dependencyChanged(ProjectExplorer::Project *a, ProjectExplorer::Project *b);
void sessionRenamed(const QString &oldName, const QString &newName);
void sessionRemoved(const QString &name);
- // for tests only
- void projectFinishedParsing(ProjectExplorer::Project *project);
-
private:
static void saveActiveMode(Utils::Id mode);
- static void configureEditor(Core::IEditor *editor, const QString &fileName);
- static void markSessionFileDirty();
- static void configureEditors(Project *project);
};
} // namespace ProjectExplorer
diff --git a/src/plugins/projectexplorer/session_p.h b/src/plugins/projectexplorer/session_p.h
new file mode 100644
index 00000000000..078fad5c364
--- /dev/null
+++ b/src/plugins/projectexplorer/session_p.h
@@ -0,0 +1,38 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "session.h"
+
+#include <QFutureInterface>
+
+using namespace Core;
+using namespace Utils;
+
+namespace ProjectExplorer {
+
+class SessionManagerPrivate
+{
+public:
+ void restoreValues(const PersistentSettingsReader &reader);
+ void restoreEditors(const PersistentSettingsReader &reader);
+ void sessionLoadingProgress();
+
+ static QString windowTitleAddition(const FilePath &filePath);
+ static QString sessionTitle(const FilePath &filePath);
+
+ QString m_sessionName = "default";
+ bool m_virginSession = true;
+ bool m_loadingSession = false;
+
+ mutable QStringList m_sessions;
+ mutable QHash<QString, QDateTime> m_sessionDateTimes;
+ QHash<QString, QDateTime> m_lastActiveTimes;
+
+ QMap<QString, QVariant> m_values;
+ QFutureInterface<void> m_future;
+ PersistentSettingsWriter *m_writer = nullptr;
+};
+
+extern SessionManagerPrivate *sb_d;
+
+} // namespace ProjectExplorer
diff --git a/src/plugins/projectexplorer/sessionmodel.cpp b/src/plugins/projectexplorer/sessionmodel.cpp
index d8c9aa935e2..6e22c3b091d 100644
--- a/src/plugins/projectexplorer/sessionmodel.cpp
+++ b/src/plugins/projectexplorer/sessionmodel.cpp
@@ -4,6 +4,7 @@
#include "sessionmodel.h"
#include "projectexplorertr.h"
+#include "projectmanager.h"
#include "session.h"
#include "sessiondialog.h"
@@ -122,10 +123,10 @@ QVariant SessionModel::data(const QModelIndex &index, int role) const
result = SessionManager::activeSession() == sessionName;
break;
case ProjectsPathRole:
- result = pathsWithTildeHomePath(SessionManager::projectsForSessionName(sessionName));
+ result = pathsWithTildeHomePath(ProjectManager::projectsForSessionName(sessionName));
break;
case ProjectsDisplayRole:
- result = pathsToBaseNames(SessionManager::projectsForSessionName(sessionName));
+ result = pathsToBaseNames(ProjectManager::projectsForSessionName(sessionName));
break;
case ShortcutRole: {
const Id sessionBase = SESSION_BASE_ID;
@@ -240,7 +241,7 @@ void SessionModel::renameSession(QWidget *parent, const QString &session)
void SessionModel::switchToSession(const QString &session)
{
- SessionManager::loadSession(session);
+ ProjectManager::loadSession(session);
emit sessionSwitched();
}
diff --git a/src/plugins/projectexplorer/target.cpp b/src/plugins/projectexplorer/target.cpp
index 8f227512eec..d2e59aab063 100644
--- a/src/plugins/projectexplorer/target.cpp
+++ b/src/plugins/projectexplorer/target.cpp
@@ -21,8 +21,8 @@
#include "projectexplorericons.h"
#include "projectexplorersettings.h"
#include "projectexplorertr.h"
+#include "projectmanager.h"
#include "runconfiguration.h"
-#include "session.h"
#include <coreplugin/coreconstants.h>
@@ -120,10 +120,10 @@ Target::Target(Project *project, Kit *k, _constructor_tag) :
});
connect(this, &Target::parsingFinished, this, [this, project](bool success) {
- if (success && this == SessionManager::startupTarget())
+ if (success && this == ProjectManager::startupTarget())
updateDefaultRunConfigurations();
// For testing.
- emit SessionManager::instance()->projectFinishedParsing(project);
+ emit ProjectManager::instance()->projectFinishedParsing(project);
emit project->anyParsingFinished(this, success);
}, Qt::QueuedConnection); // Must wait for run configs to change their enabled state.
@@ -242,6 +242,70 @@ QString Target::activeBuildKey() const
return d->m_activeRunConfiguration->buildKey();
}
+void Target::setActiveBuildConfiguration(BuildConfiguration *bc, SetActive cascade)
+{
+ QTC_ASSERT(project(), return);
+
+ if (project()->isShuttingDown() || isShuttingDown())
+ return;
+
+ setActiveBuildConfiguration(bc);
+
+ if (!bc)
+ return;
+ if (cascade != SetActive::Cascade || !ProjectManager::isProjectConfigurationCascading())
+ return;
+
+ Id kitId = kit()->id();
+ QString name = bc->displayName(); // We match on displayname
+ for (Project *otherProject : ProjectManager::projects()) {
+ if (otherProject == project())
+ continue;
+ Target *otherTarget = otherProject->activeTarget();
+ if (!otherTarget || otherTarget->kit()->id() != kitId)
+ continue;
+
+ for (BuildConfiguration *otherBc : otherTarget->buildConfigurations()) {
+ if (otherBc->displayName() == name) {
+ otherTarget->setActiveBuildConfiguration(otherBc);
+ break;
+ }
+ }
+ }
+}
+
+void Target::setActiveDeployConfiguration(DeployConfiguration *dc, SetActive cascade)
+{
+ QTC_ASSERT(project(), return);
+
+ if (project()->isShuttingDown() || isShuttingDown())
+ return;
+
+ setActiveDeployConfiguration(dc);
+
+ if (!dc)
+ return;
+ if (cascade != SetActive::Cascade || !ProjectManager::isProjectConfigurationCascading())
+ return;
+
+ Id kitId = kit()->id();
+ QString name = dc->displayName(); // We match on displayname
+ for (Project *otherProject : ProjectManager::projects()) {
+ if (otherProject == project())
+ continue;
+ Target *otherTarget = otherProject->activeTarget();
+ if (!otherTarget || otherTarget->kit()->id() != kitId)
+ continue;
+
+ for (DeployConfiguration *otherDc : otherTarget->deployConfigurations()) {
+ if (otherDc->displayName() == name) {
+ otherTarget->setActiveDeployConfiguration(otherDc);
+ break;
+ }
+ }
+ }
+}
+
Utils::Id Target::id() const
{
return d->m_kit->id();
@@ -307,9 +371,9 @@ bool Target::removeBuildConfiguration(BuildConfiguration *bc)
if (activeBuildConfiguration() == bc) {
if (d->m_buildConfigurations.isEmpty())
- SessionManager::setActiveBuildConfiguration(this, nullptr, SetActive::Cascade);
+ setActiveBuildConfiguration(nullptr, SetActive::Cascade);
else
- SessionManager::setActiveBuildConfiguration(this, d->m_buildConfigurations.at(0), SetActive::Cascade);
+ setActiveBuildConfiguration(d->m_buildConfigurations.at(0), SetActive::Cascade);
}
emit removedBuildConfiguration(bc);
@@ -377,10 +441,9 @@ bool Target::removeDeployConfiguration(DeployConfiguration *dc)
if (activeDeployConfiguration() == dc) {
if (d->m_deployConfigurations.isEmpty())
- SessionManager::setActiveDeployConfiguration(this, nullptr, SetActive::Cascade);
+ setActiveDeployConfiguration(nullptr, SetActive::Cascade);
else
- SessionManager::setActiveDeployConfiguration(this, d->m_deployConfigurations.at(0),
- SetActive::Cascade);
+ setActiveDeployConfiguration(d->m_deployConfigurations.at(0), SetActive::Cascade);
}
ProjectExplorerPlugin::targetSelector()->removedDeployConfiguration(dc);
diff --git a/src/plugins/projectexplorer/target.h b/src/plugins/projectexplorer/target.h
index 78f0b5f3b5b..aeca2fd9e0b 100644
--- a/src/plugins/projectexplorer/target.h
+++ b/src/plugins/projectexplorer/target.h
@@ -26,11 +26,13 @@ class Project;
class ProjectConfigurationModel;
class RunConfiguration;
+enum class SetActive : int { Cascade, NoCascade };
+
class TargetPrivate;
class PROJECTEXPLORER_EXPORT Target : public QObject
{
- friend class SessionManager; // for setActiveBuild and setActiveDeployConfiguration
+ friend class ProjectManager; // for setActiveBuild and setActiveDeployConfiguration
Q_OBJECT
public:
@@ -109,6 +111,9 @@ public:
QString activeBuildKey() const; // Build key of active run configuaration
+ void setActiveBuildConfiguration(BuildConfiguration *bc, SetActive cascade);
+ void setActiveDeployConfiguration(DeployConfiguration *dc, SetActive cascade);
+
signals:
void targetEnabled(bool);
void iconChanged();
diff --git a/src/plugins/projectexplorer/targetsettingspanel.cpp b/src/plugins/projectexplorer/targetsettingspanel.cpp
index 6d8c81e634c..d2fdc3b43a8 100644
--- a/src/plugins/projectexplorer/targetsettingspanel.cpp
+++ b/src/plugins/projectexplorer/targetsettingspanel.cpp
@@ -12,9 +12,9 @@
#include "project.h"
#include "projectexplorericons.h"
#include "projectexplorertr.h"
+#include "projectmanager.h"
#include "projectwindow.h"
#include "runsettingspropertiespage.h"
-#include "session.h"
#include "target.h"
#include "targetsetuppage.h"
#include "task.h"
@@ -281,7 +281,7 @@ public:
QFont font = parent()->data(column, role).value<QFont>();
if (TargetItem *targetItem = parent()->currentTargetItem()) {
Target *t = targetItem->target();
- if (t && t->id() == m_kitId && m_project == SessionManager::startupProject())
+ if (t && t->id() == m_kitId && m_project == ProjectManager::startupProject())
font.setBold(true);
}
return font;
@@ -334,7 +334,7 @@ public:
// Go to Run page, when on Run previously etc.
TargetItem *previousItem = parent()->currentTargetItem();
m_currentChild = previousItem ? previousItem->m_currentChild : DefaultPage;
- SessionManager::setActiveTarget(m_project, target(), SetActive::Cascade);
+ m_project->setActiveTarget(target(), SetActive::Cascade);
parent()->setData(column, QVariant::fromValue(static_cast<TreeItem *>(this)),
ItemActivatedFromBelowRole);
}
@@ -346,7 +346,7 @@ public:
int child = indexOf(data.value<TreeItem *>());
QTC_ASSERT(child != -1, return false);
m_currentChild = child; // Triggered from sub-item.
- SessionManager::setActiveTarget(m_project, target(), SetActive::Cascade);
+ m_project->setActiveTarget(target(), SetActive::Cascade);
// Propagate Build/Run selection up.
parent()->setData(column, QVariant::fromValue(static_cast<TreeItem *>(this)),
ItemActivatedFromBelowRole);
@@ -355,7 +355,7 @@ public:
if (role == ItemActivatedFromAboveRole) {
// Usually programmatic activation, e.g. after opening the Project mode.
- SessionManager::setActiveTarget(m_project, target(), SetActive::Cascade);
+ m_project->setActiveTarget(target(), SetActive::Cascade);
return true;
}
return false;
@@ -377,7 +377,7 @@ public:
= menu->addAction(Tr::tr("Enable Kit for All Projects"));
enableForAllAction->setEnabled(isSelectable);
QObject::connect(enableForAllAction, &QAction::triggered, [kit] {
- for (Project * const p : SessionManager::projects()) {
+ for (Project * const p : ProjectManager::projects()) {
if (!p->target(kit))
p->addTargetForKit(kit);
}
@@ -411,7 +411,7 @@ public:
QAction *disableForAllAction = menu->addAction(Tr::tr("Disable Kit for All Projects"));
disableForAllAction->setEnabled(isSelectable);
QObject::connect(disableForAllAction, &QAction::triggered, [kit] {
- for (Project * const p : SessionManager::projects()) {
+ for (Project * const p : ProjectManager::projects()) {
Target * const t = p->target(kit);
if (!t)
continue;
diff --git a/src/plugins/projectexplorer/targetsetuppage.cpp b/src/plugins/projectexplorer/targetsetuppage.cpp
index a17aa58c946..3b11c8f1471 100644
--- a/src/plugins/projectexplorer/targetsetuppage.cpp
+++ b/src/plugins/projectexplorer/targetsetuppage.cpp
@@ -658,7 +658,7 @@ bool TargetSetupPage::setupProject(Project *project)
if (m_importer)
activeTarget = m_importer->preferredTarget(project->targets());
if (activeTarget)
- SessionManager::setActiveTarget(project, activeTarget, SetActive::NoCascade);
+ project->setActiveTarget(activeTarget, SetActive::NoCascade);
return true;
}
diff --git a/src/plugins/projectexplorer/task.cpp b/src/plugins/projectexplorer/task.cpp
index dab4c7ed17d..c2937e76ed4 100644
--- a/src/plugins/projectexplorer/task.cpp
+++ b/src/plugins/projectexplorer/task.cpp
@@ -120,6 +120,34 @@ QIcon Task::icon() const
return m_icon;
}
+QString Task::toolTip(const QString &extraHeading) const
+{
+ if (isNull())
+ return {};
+
+ QString text = description();
+ static const QString linkTagStartPlaceholder("__QTC_LINK_TAG_START__");
+ static const QString linkTagEndPlaceholder("__QTC_LINK_TAG_END__");
+ static const QString linkEndPlaceholder("__QTC_LINK_END__");
+ for (auto formatRange = formats.crbegin(); formatRange != formats.crend(); ++formatRange) {
+ if (!formatRange->format.isAnchor())
+ continue;
+ text.insert(formatRange->start + formatRange->length, linkEndPlaceholder);
+ text.insert(formatRange->start, QString::fromLatin1("%1%2%3").arg(
+ linkTagStartPlaceholder, formatRange->format.anchorHref(), linkTagEndPlaceholder));
+ }
+ text = text.toHtmlEscaped();
+ text.replace(linkEndPlaceholder, "</a>");
+ text.replace(linkTagStartPlaceholder, "<a href=\"");
+ text.replace(linkTagEndPlaceholder, "\">");
+ const QString htmlExtraHeading = extraHeading.isEmpty()
+ ? QString()
+ : QString::fromUtf8("<b>%1</b><br/>").arg(extraHeading);
+ return QString::fromUtf8("<html><body>%1<code style=\"white-space:pre;font-family:monospace\">"
+ "%2</code></body></html>")
+ .arg(htmlExtraHeading, text);
+}
+
//
// functions
//
diff --git a/src/plugins/projectexplorer/task.h b/src/plugins/projectexplorer/task.h
index f38302f90f9..27adeebf039 100644
--- a/src/plugins/projectexplorer/task.h
+++ b/src/plugins/projectexplorer/task.h
@@ -51,6 +51,7 @@ public:
void setFile(const Utils::FilePath &file);
QString description() const;
QIcon icon() const;
+ QString toolTip(const QString &extraHeading = {}) const;
friend PROJECTEXPLORER_EXPORT bool operator==(const Task &t1, const Task &t2);
friend PROJECTEXPLORER_EXPORT bool operator<(const Task &a, const Task &b);
diff --git a/src/plugins/projectexplorer/taskhub.cpp b/src/plugins/projectexplorer/taskhub.cpp
index da839e89f19..7c1cff54542 100644
--- a/src/plugins/projectexplorer/taskhub.cpp
+++ b/src/plugins/projectexplorer/taskhub.cpp
@@ -51,13 +51,8 @@ public:
: Tr::tr("Warning"));
setPriority(task.type == Task::Error ? TextEditor::TextMark::NormalPriority
: TextEditor::TextMark::LowPriority);
- if (task.category == Constants::TASK_CATEGORY_COMPILE) {
- setToolTip("<html><body><b>" + Tr::tr("Build Issue")
- + "</b><br/><code style=\"white-space:pre;font-family:monospace\">"
- + task.description().toHtmlEscaped() + "</code></body></html>");
- } else {
- setToolTip(task.description());
- }
+ setToolTip(task.toolTip(task.category == Constants::TASK_CATEGORY_COMPILE
+ ? Tr::tr("Build Issue") : QString()));
setIcon(task.icon());
setVisible(!task.icon().isNull());
}
diff --git a/src/plugins/projectexplorer/taskwindow.cpp b/src/plugins/projectexplorer/taskwindow.cpp
index 52e3899d654..db8dbf7d5ba 100644
--- a/src/plugins/projectexplorer/taskwindow.cpp
+++ b/src/plugins/projectexplorer/taskwindow.cpp
@@ -27,14 +27,17 @@
#include <utils/qtcassert.h>
#include <utils/stylehelper.h>
#include <utils/theme/theme.h>
+#include <utils/tooltip/tooltip.h>
#include <utils/utilsicons.h>
#include <QDir>
+#include <QLabel>
+#include <QMenu>
#include <QPainter>
+#include <QScrollBar>
#include <QStyledItemDelegate>
-#include <QMenu>
#include <QToolButton>
-#include <QScrollBar>
+#include <QVBoxLayout>
using namespace Utils;
@@ -92,14 +95,10 @@ public:
private:
void resizeEvent(QResizeEvent *e) override;
- void mousePressEvent(QMouseEvent *e) override;
- void mouseReleaseEvent(QMouseEvent *e) override;
- void mouseMoveEvent(QMouseEvent *e) override;
+ void keyReleaseEvent(QKeyEvent *e) override;
+ bool event(QEvent *e) override;
- Link locationForPos(const QPoint &pos);
-
- bool m_linksActive = true;
- Qt::MouseButton m_mouseButtonPressed = Qt::NoButton;
+ void showToolTip(const Task &task, const QPoint &pos);
};
class TaskDelegate : public QStyledItemDelegate
@@ -117,28 +116,16 @@ public:
// TaskView uses this method if the size of the taskview changes
void emitSizeHintChanged(const QModelIndex &index);
- void currentChanged(const QModelIndex &current, const QModelIndex &previous);
-
- QString hrefForPos(const QPointF &pos);
-
private:
void generateGradientPixmap(int width, int height, QColor color, bool selected) const;
mutable int m_cachedHeight = 0;
mutable QFont m_cachedFont;
- mutable QList<QPair<QRectF, QString>> m_hrefs;
/*
- Collapsed:
- +----------------------------------------------------------------------------------------------------+
- | TASKICONAREA TEXTAREA FILEAREA LINEAREA |
- +----------------------------------------------------------------------------------------------------+
-
- Expanded:
- +----------------------------------------------------------------------------------------------------+
- | TASKICONICON TEXTAREA FILEAREA LINEAREA |
- | more text -------------------------------------------------------------------------> |
- +----------------------------------------------------------------------------------------------------+
+ +------------------------------------------------------------------------------------------+
+ | TASKICONAREA TEXTAREA FILEAREA LINEAREA |
+ +------------------------------------------------------------------------------------------+
*/
class Positions
{
@@ -205,8 +192,8 @@ TaskView::TaskView(QWidget *parent)
{
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
- setMouseTracking(true);
setAutoScroll(false); // QTCREATORBUG-25101
+ setUniformItemSizes(true);
QFontMetrics fm(font());
int vStepSize = fm.height() + 3;
@@ -224,54 +211,49 @@ void TaskView::resizeEvent(QResizeEvent *e)
static_cast<TaskDelegate *>(itemDelegate())->emitSizeHintChanged(selectionModel()->currentIndex());
}
-void TaskView::mousePressEvent(QMouseEvent *e)
+void TaskView::keyReleaseEvent(QKeyEvent *e)
{
- m_mouseButtonPressed = e->button();
- ListView::mousePressEvent(e);
-}
-
-void TaskView::mouseReleaseEvent(QMouseEvent *e)
-{
- if (m_linksActive && m_mouseButtonPressed == Qt::LeftButton) {
- const Link loc = locationForPos(e->pos());
- if (!loc.targetFilePath.isEmpty()) {
- Core::EditorManager::openEditorAt(loc, {},
- Core::EditorManager::SwitchSplitIfAlreadyVisible);
+ ListView::keyReleaseEvent(e);
+ if (e->key() == Qt::Key_Space) {
+ const Task task = static_cast<TaskFilterModel *>(model())->task(currentIndex());
+ if (!task.isNull()) {
+ const QPoint toolTipPos = mapToGlobal(visualRect(currentIndex()).topLeft());
+ QMetaObject::invokeMethod(this, [this, task, toolTipPos] {
+ showToolTip(task, toolTipPos); }, Qt::QueuedConnection);
}
}
-
- // Mouse was released, activate links again
- m_linksActive = true;
- m_mouseButtonPressed = Qt::NoButton;
- ListView::mouseReleaseEvent(e);
}
-void TaskView::mouseMoveEvent(QMouseEvent *e)
+bool TaskView::event(QEvent *e)
{
- // Cursor was dragged, deactivate links
- if (m_mouseButtonPressed != Qt::NoButton)
- m_linksActive = false;
+ if (e->type() != QEvent::ToolTip)
+ return QListView::event(e);
- viewport()->setCursor(m_linksActive && !locationForPos(e->pos()).targetFilePath.isEmpty()
- ? Qt::PointingHandCursor : Qt::ArrowCursor);
- ListView::mouseMoveEvent(e);
+ const auto helpEvent = static_cast<QHelpEvent*>(e);
+ const Task task = static_cast<TaskFilterModel *>(model())->task(indexAt(helpEvent->pos()));
+ if (task.isNull())
+ return QListView::event(e);
+ showToolTip(task, helpEvent->globalPos());
+ e->accept();
+ return true;
}
-Link TaskView::locationForPos(const QPoint &pos)
+void TaskView::showToolTip(const Task &task, const QPoint &pos)
{
- const auto delegate = qobject_cast<TaskDelegate *>(itemDelegate(indexAt(pos)));
- if (!delegate)
- return {};
- OutputFormatter formatter;
- Link loc;
- connect(&formatter, &OutputFormatter::openInEditorRequested, this, [&loc](const Link &link) {
- loc = link;
- });
-
- const QString href = delegate->hrefForPos(pos);
- if (!href.isEmpty())
- formatter.handleLink(href);
- return loc;
+ const QString toolTip = task.toolTip();
+ if (!toolTip.isEmpty()) {
+ const auto label = new QLabel(toolTip);
+ connect(label, &QLabel::linkActivated, [](const QString &link) {
+ Core::EditorManager::openEditorAt(OutputLineParser::parseLinkTarget(link), {},
+ Core::EditorManager::SwitchSplitIfAlreadyVisible);
+ });
+ const auto layout = new QVBoxLayout;
+ layout->setContentsMargins(0, 0, 0, 0);
+ layout->addWidget(label);
+ ToolTip::show(pos, layout);
+ } else {
+ ToolTip::hideImmediately();
+ }
}
/////
@@ -340,8 +322,6 @@ TaskWindow::TaskWindow() : d(std::make_unique<TaskWindowPrivate>())
Core::ICore::addContextObject(d->m_taskWindowContext);
connect(d->m_listview->selectionModel(), &QItemSelectionModel::currentChanged,
- tld, &TaskDelegate::currentChanged);
- connect(d->m_listview->selectionModel(), &QItemSelectionModel::currentChanged,
this, [this](const QModelIndex &index) { d->m_listview->scrollTo(index); });
connect(d->m_listview, &QAbstractItemView::activated,
this, &TaskWindow::triggerDefaultHandler);
@@ -764,54 +744,19 @@ QSize TaskDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelInd
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
- auto view = qobject_cast<const QAbstractItemView *>(opt.widget);
- const bool current = view->selectionModel()->currentIndex() == index;
QSize s;
s.setWidth(option.rect.width());
- if (!current && option.font == m_cachedFont && m_cachedHeight > 0) {
+ if (option.font == m_cachedFont && m_cachedHeight > 0) {
s.setHeight(m_cachedHeight);
return s;
}
- QFontMetrics fm(option.font);
- int fontHeight = fm.height();
- int fontLeading = fm.leading();
-
- auto model = static_cast<TaskFilterModel *>(view->model())->taskModel();
- Positions positions(option, model);
-
- if (current) {
- QString description = index.data(TaskModel::Description).toString();
- // Layout the description
- int leading = fontLeading;
- int height = 0;
- description.replace(QLatin1Char('\n'), QChar::LineSeparator);
- QTextLayout tl(description);
- tl.setFormats(index.data(TaskModel::Task_t).value<Task>().formats);
- tl.beginLayout();
- while (true) {
- QTextLine line = tl.createLine();
- if (!line.isValid())
- break;
- line.setLineWidth(positions.textAreaWidth());
- height += leading;
- line.setPosition(QPoint(0, height));
- height += static_cast<int>(line.height());
- }
- tl.endLayout();
-
- s.setHeight(height + leading + fontHeight + 3);
- } else {
- s.setHeight(fontHeight + 3);
- }
+ s.setHeight(option.fontMetrics.height() + 3);
if (s.height() < Positions::minimumHeight())
s.setHeight(Positions::minimumHeight());
-
- if (!current) {
- m_cachedHeight = s.height();
- m_cachedFont = option.font;
- }
+ m_cachedHeight = s.height();
+ m_cachedFont = option.font;
return s;
}
@@ -821,22 +766,6 @@ void TaskDelegate::emitSizeHintChanged(const QModelIndex &index)
emit sizeHintChanged(index);
}
-void TaskDelegate::currentChanged(const QModelIndex &current, const QModelIndex &previous)
-{
- m_hrefs.clear();
- emit sizeHintChanged(current);
- emit sizeHintChanged(previous);
-}
-
-QString TaskDelegate::hrefForPos(const QPointF &pos)
-{
- for (const auto &link : std::as_const(m_hrefs)) {
- if (link.first.contains(pos))
- return link.second;
- }
- return {};
-}
-
void TaskDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyleOptionViewItem opt = option;
@@ -849,7 +778,6 @@ void TaskDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
auto view = qobject_cast<const QAbstractItemView *>(opt.widget);
const bool selected = view->selectionModel()->isSelected(index);
- const bool current = view->selectionModel()->currentIndex() == index;
if (selected) {
painter->setBrush(opt.palette.highlight().color());
@@ -878,84 +806,17 @@ void TaskDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
icon.pixmap(Positions::taskIconWidth(), Positions::taskIconHeight()));
// Paint TextArea:
- if (!current) {
- // in small mode we lay out differently
- QString bottom = index.data(TaskModel::Description).toString().split(QLatin1Char('\n')).first();
- painter->setClipRect(positions.textArea());
- painter->drawText(positions.textAreaLeft(), positions.top() + fm.ascent(), bottom);
- if (fm.horizontalAdvance(bottom) > positions.textAreaWidth()) {
- // draw a gradient to mask the text
- int gradientStart = positions.textAreaRight() - ELLIPSIS_GRADIENT_WIDTH + 1;
- QLinearGradient lg(gradientStart, 0, gradientStart + ELLIPSIS_GRADIENT_WIDTH, 0);
- lg.setColorAt(0, Qt::transparent);
- lg.setColorAt(1, backgroundColor);
- painter->fillRect(gradientStart, positions.top(), ELLIPSIS_GRADIENT_WIDTH, positions.firstLineHeight(), lg);
- }
- } else {
- // Description
- QString description = index.data(TaskModel::Description).toString();
- // Layout the description
- int leading = fm.leading();
- int height = 0;
- description.replace(QLatin1Char('\n'), QChar::LineSeparator);
- QTextLayout tl(description);
- QVector<QTextLayout::FormatRange> formats = index.data(TaskModel::Task_t).value<Task>().formats;
- for (QTextLayout::FormatRange &format : formats)
- format.format.setForeground(textColor);
- tl.setFormats(formats);
- tl.beginLayout();
- while (true) {
- QTextLine line = tl.createLine();
- if (!line.isValid())
- break;
- line.setLineWidth(positions.textAreaWidth());
- height += leading;
- line.setPosition(QPoint(0, height));
- height += static_cast<int>(line.height());
- }
- tl.endLayout();
- const QPoint indexPos = view->visualRect(index).topLeft();
- tl.draw(painter, QPoint(positions.textAreaLeft(), positions.top()));
- m_hrefs.clear();
- for (const auto &range : tl.formats()) {
- if (!range.format.isAnchor())
- continue;
- const QTextLine &firstLinkLine = tl.lineForTextPosition(range.start);
- const QTextLine &lastLinkLine = tl.lineForTextPosition(range.start + range.length - 1);
- for (int i = firstLinkLine.lineNumber(); i <= lastLinkLine.lineNumber(); ++i) {
- const QTextLine &linkLine = tl.lineAt(i);
- if (!linkLine.isValid())
- break;
- const QPointF linePos = linkLine.position();
- const int linkStartPos = i == firstLinkLine.lineNumber()
- ? range.start : linkLine.textStart();
- const qreal startOffset = linkLine.cursorToX(linkStartPos);
- const int linkEndPos = i == lastLinkLine.lineNumber()
- ? range.start + range.length
- : linkLine.textStart() + linkLine.textLength();
- const qreal endOffset = linkLine.cursorToX(linkEndPos);
- const QPointF linkPos(indexPos.x() + positions.textAreaLeft() + linePos.x()
- + startOffset, positions.top() + linePos.y());
- const QSize linkSize(endOffset - startOffset, linkLine.height());
- const QRectF linkRect(linkPos, linkSize);
- m_hrefs.push_back({linkRect, range.format.anchorHref()});
- }
- }
-
- const QColor mix = StyleHelper::mergedColors(textColor, backgroundColor, 70);
- const QString directory = QDir::toNativeSeparators(index.data(TaskModel::File).toString());
- int secondBaseLine = positions.top() + fm.ascent() + height + leading;
- if (index.data(TaskModel::FileNotFound).toBool() && !directory.isEmpty()) {
- const QString fileNotFound = Tr::tr("File not found: %1").arg(directory);
- const QColor errorColor = selected ? mix : creatorTheme()->color(Theme::TextColorError);
- painter->setPen(errorColor);
- painter->drawText(positions.textAreaLeft(), secondBaseLine, fileNotFound);
- } else {
- painter->setPen(mix);
- painter->drawText(positions.textAreaLeft(), secondBaseLine, directory);
- }
+ QString bottom = index.data(TaskModel::Description).toString().split(QLatin1Char('\n')).first();
+ painter->setClipRect(positions.textArea());
+ painter->drawText(positions.textAreaLeft(), positions.top() + fm.ascent(), bottom);
+ if (fm.horizontalAdvance(bottom) > positions.textAreaWidth()) {
+ // draw a gradient to mask the text
+ int gradientStart = positions.textAreaRight() - ELLIPSIS_GRADIENT_WIDTH + 1;
+ QLinearGradient lg(gradientStart, 0, gradientStart + ELLIPSIS_GRADIENT_WIDTH, 0);
+ lg.setColorAt(0, Qt::transparent);
+ lg.setColorAt(1, backgroundColor);
+ painter->fillRect(gradientStart, positions.top(), ELLIPSIS_GRADIENT_WIDTH, positions.firstLineHeight(), lg);
}
- painter->setPen(textColor);
// Paint FileArea
QString file = index.data(TaskModel::File).toString();
diff --git a/src/plugins/projectexplorer/testdata/multi-target-project/CMakeLists.txt b/src/plugins/projectexplorer/testdata/multi-target-project/CMakeLists.txt
new file mode 100644
index 00000000000..5313539cd79
--- /dev/null
+++ b/src/plugins/projectexplorer/testdata/multi-target-project/CMakeLists.txt
@@ -0,0 +1,3 @@
+project(multi-target-project)
+add_executable(multi-target-project-app multi-target-project-main.cpp multi-target-project-shared.h)
+add_library(multi-target-project-lib STATIC multi-target-project-lib.cpp multi-target-project-shared.h)
diff --git a/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-app.pro b/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-app.pro
new file mode 100644
index 00000000000..96870c05c1f
--- /dev/null
+++ b/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-app.pro
@@ -0,0 +1,4 @@
+TARGET = app
+CONFIG -= qt
+SOURCES = multi-target-project-main.cpp
+HEADERS = multi-target-project-shared.h
diff --git a/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-lib.cpp b/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-lib.cpp
new file mode 100644
index 00000000000..9b7a34861c7
--- /dev/null
+++ b/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-lib.cpp
@@ -0,0 +1,6 @@
+#include "multi-target-project-shared.h"
+
+int increaseNumber()
+{
+ return getNumber() + 1;
+}
diff --git a/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-lib.pro b/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-lib.pro
new file mode 100644
index 00000000000..42571540519
--- /dev/null
+++ b/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-lib.pro
@@ -0,0 +1,4 @@
+TEMPLATE = lib
+CONFIG += static
+SOURCES = multi-target-project-lib.cpp
+HEADERS = multi-target-project-shared.h
diff --git a/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-main.cpp b/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-main.cpp
new file mode 100644
index 00000000000..306400b3502
--- /dev/null
+++ b/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-main.cpp
@@ -0,0 +1,6 @@
+#include "multi-target-project-shared.h"
+
+int main()
+{
+ return getNumber();
+}
diff --git a/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-shared.h b/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-shared.h
new file mode 100644
index 00000000000..9f839e82d02
--- /dev/null
+++ b/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project-shared.h
@@ -0,0 +1,3 @@
+#pragma once
+
+inline int getNumber() { return 5; }
diff --git a/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project.pro b/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project.pro
new file mode 100644
index 00000000000..5e6289d7b26
--- /dev/null
+++ b/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project.pro
@@ -0,0 +1,4 @@
+TEMPLATE = subdirs
+app.file = multi-target-project-app.pro
+lib.file = multi-target-project-lib.pro
+SUBDIRS = app lib
diff --git a/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project.qbs b/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project.qbs
new file mode 100644
index 00000000000..079ac82c955
--- /dev/null
+++ b/src/plugins/projectexplorer/testdata/multi-target-project/multi-target-project.qbs
@@ -0,0 +1,10 @@
+Project {
+ CppApplication {
+ name: "app"
+ files: ["multi-target-project-main.cpp", "multi-target-project-shared.h"]
+ }
+ StaticLibrary {
+ Depends { name: "cpp" }
+ files: ["multi-target-project-lib.cpp", "multi-target-project-shared.h"]
+ }
+}
diff --git a/src/plugins/projectexplorer/toolchain.cpp b/src/plugins/projectexplorer/toolchain.cpp
index 1a3d3d43f59..dfca4040fe3 100644
--- a/src/plugins/projectexplorer/toolchain.cpp
+++ b/src/plugins/projectexplorer/toolchain.cpp
@@ -192,7 +192,7 @@ bool ToolChain::isValid() const
return d->m_isValid.value_or(false);
}
-QStringList ToolChain::includedFiles(const QStringList &flags, const QString &directory) const
+FilePaths ToolChain::includedFiles(const QStringList &flags, const FilePath &directory) const
{
Q_UNUSED(flags)
Q_UNUSED(directory)
@@ -466,12 +466,12 @@ Utils::LanguageVersion ToolChain::languageVersion(const Utils::Id &language, con
}
}
-QStringList ToolChain::includedFiles(const QString &option,
- const QStringList &flags,
- const QString &directoryPath,
- PossiblyConcatenatedFlag possiblyConcatenated)
+FilePaths ToolChain::includedFiles(const QString &option,
+ const QStringList &flags,
+ const FilePath &directoryPath,
+ PossiblyConcatenatedFlag possiblyConcatenated)
{
- QStringList result;
+ FilePaths result;
for (int i = 0; i < flags.size(); ++i) {
QString includeFile;
@@ -484,11 +484,8 @@ QStringList ToolChain::includedFiles(const QString &option,
if (includeFile.isEmpty() && flag == option && i + 1 < flags.size())
includeFile = flags[++i];
- if (!includeFile.isEmpty()) {
- if (!QFileInfo(includeFile).isAbsolute())
- includeFile = directoryPath + "/" + includeFile;
- result.append(QDir::cleanPath(includeFile));
- }
+ if (!includeFile.isEmpty())
+ result.append(directoryPath.resolvePath(includeFile));
}
return result;
diff --git a/src/plugins/projectexplorer/toolchain.h b/src/plugins/projectexplorer/toolchain.h
index 15bee2fdaa8..35cad5333ee 100644
--- a/src/plugins/projectexplorer/toolchain.h
+++ b/src/plugins/projectexplorer/toolchain.h
@@ -102,7 +102,7 @@ public:
virtual Utils::LanguageExtensions languageExtensions(const QStringList &cxxflags) const = 0;
virtual Utils::WarningFlags warningFlags(const QStringList &cflags) const = 0;
- virtual QStringList includedFiles(const QStringList &flags, const QString &directory) const;
+ virtual Utils::FilePaths includedFiles(const QStringList &flags, const Utils::FilePath &directory) const;
virtual QString sysRoot() const;
QString explicitCodeModelTargetTriple() const;
@@ -184,10 +184,10 @@ protected:
virtual bool fromMap(const QVariantMap &data);
enum class PossiblyConcatenatedFlag { No, Yes };
- static QStringList includedFiles(const QString &option,
- const QStringList &flags,
- const QString &directoryPath,
- PossiblyConcatenatedFlag possiblyConcatenated);
+ static Utils::FilePaths includedFiles(const QString &option,
+ const QStringList &flags,
+ const Utils::FilePath &directoryPath,
+ PossiblyConcatenatedFlag possiblyConcatenated);
private:
ToolChain(const ToolChain &) = delete;
diff --git a/src/plugins/projectexplorer/treescanner.cpp b/src/plugins/projectexplorer/treescanner.cpp
index abcc66c3cc3..05d3db702f6 100644
--- a/src/plugins/projectexplorer/treescanner.cpp
+++ b/src/plugins/projectexplorer/treescanner.cpp
@@ -9,9 +9,9 @@
#include <coreplugin/iversioncontrol.h>
#include <coreplugin/vcsmanager.h>
+#include <utils/asynctask.h>
#include <utils/qtcassert.h>
#include <utils/algorithm.h>
-#include <utils/runextensions.h>
#include <memory>
@@ -42,9 +42,9 @@ bool TreeScanner::asyncScanForFiles(const Utils::FilePath &directory)
if (!m_futureWatcher.isFinished())
return false;
- m_scanFuture = Utils::runAsync(
- [directory, filter = m_filter, factory = m_factory] (FutureInterface &fi) {
- TreeScanner::scanForFiles(fi, directory, filter, factory);
+ m_scanFuture = Utils::asyncRun(
+ [directory, filter = m_filter, factory = m_factory] (Promise &promise) {
+ TreeScanner::scanForFiles(promise, directory, filter, factory);
});
m_futureWatcher.setFuture(m_scanFuture);
@@ -139,10 +139,10 @@ static std::unique_ptr<FolderNode> createFolderNode(const Utils::FilePath &direc
return fileSystemNode;
}
-void TreeScanner::scanForFiles(FutureInterface &fi, const Utils::FilePath& directory,
+void TreeScanner::scanForFiles(Promise &promise, const Utils::FilePath& directory,
const FileFilter &filter, const FileTypeFactory &factory)
{
- QList<FileNode *> nodes = ProjectExplorer::scanForFiles(fi, directory,
+ QList<FileNode *> nodes = ProjectExplorer::scanForFiles(promise, directory,
[&filter, &factory](const Utils::FilePath &fn) -> FileNode * {
const Utils::MimeType mimeType = Utils::mimeTypeForFile(fn);
@@ -160,10 +160,10 @@ void TreeScanner::scanForFiles(FutureInterface &fi, const Utils::FilePath& direc
Utils::sort(nodes, ProjectExplorer::Node::sortByPath);
- fi.setProgressValue(fi.progressMaximum());
+ promise.setProgressValue(promise.future().progressMaximum());
Result result{createFolderNode(directory, nodes), nodes};
- fi.reportResult(result);
+ promise.addResult(result);
}
} // namespace ProjectExplorer
diff --git a/src/plugins/projectexplorer/treescanner.h b/src/plugins/projectexplorer/treescanner.h
index e324303c172..f8d019121b9 100644
--- a/src/plugins/projectexplorer/treescanner.h
+++ b/src/plugins/projectexplorer/treescanner.h
@@ -31,7 +31,7 @@ public:
};
using Future = QFuture<Result>;
using FutureWatcher = QFutureWatcher<Result>;
- using FutureInterface = QFutureInterface<Result>;
+ using Promise = QPromise<Result>;
using FileFilter = std::function<bool(const Utils::MimeType &, const Utils::FilePath &)>;
using FileTypeFactory = std::function<ProjectExplorer::FileType(const Utils::MimeType &, const Utils::FilePath &)>;
@@ -69,7 +69,7 @@ signals:
void finished();
private:
- static void scanForFiles(FutureInterface &fi, const Utils::FilePath &directory,
+ static void scanForFiles(Promise &fi, const Utils::FilePath &directory,
const FileFilter &filter, const FileTypeFactory &factory);
private:
diff --git a/src/plugins/python/CMakeLists.txt b/src/plugins/python/CMakeLists.txt
index a508e14ddc8..c1f4767f83b 100644
--- a/src/plugins/python/CMakeLists.txt
+++ b/src/plugins/python/CMakeLists.txt
@@ -19,4 +19,5 @@ add_qtc_plugin(Python
pythonscanner.cpp pythonscanner.h
pythonsettings.cpp pythonsettings.h
pythonutils.cpp pythonutils.h
+ pythonwizardpage.cpp pythonwizardpage.h
)
diff --git a/src/plugins/python/Python.json.in b/src/plugins/python/Python.json.in
index 3e62d146313..98fd52ec8d5 100644
--- a/src/plugins/python/Python.json.in
+++ b/src/plugins/python/Python.json.in
@@ -30,12 +30,16 @@
\" <comment>Python module interface file</comment>\",
\" <glob pattern=\'*.pyi\'/>\",
\" </mime-type>\",
- \" <mime-type type=\'text/x-python-project\'>\",
+ \" <mime-type type=\'text/x-pyqt-project\'>\",
\" <sub-class-of type=\'text/x-python\'/>\",
\" <comment>Qt Creator Python project file</comment>\",
- \" <glob pattern=\'*.pyproject\'/>\",
\" <glob pattern=\'*.pyqtc\'/>\",
\" </mime-type>\",
+ \" <mime-type type=\'text/x-python-project\'>\",
+ \" <sub-class-of type=\'application/json\'/>\",
+ \" <comment>Qt Creator Python project file</comment>\",
+ \" <glob pattern=\'*.pyproject\'/>\",
+ \" </mime-type>\",
\"</mime-info>\"
]
}
diff --git a/src/plugins/python/pipsupport.cpp b/src/plugins/python/pipsupport.cpp
index 848184b462f..96aa4f64e2f 100644
--- a/src/plugins/python/pipsupport.cpp
+++ b/src/plugins/python/pipsupport.cpp
@@ -10,13 +10,13 @@
#include <coreplugin/progressmanager/progressmanager.h>
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <utils/algorithm.h>
+#include <utils/asynctask.h>
#include <utils/mimeutils.h>
#include <utils/qtcprocess.h>
-#include <utils/runextensions.h>
using namespace Utils;
@@ -145,7 +145,7 @@ Pip *Pip::instance(const FilePath &python)
QFuture<PipPackageInfo> Pip::info(const PipPackage &package)
{
- return Utils::runAsync(&Pip::infoImpl, this, package);
+ return Utils::asyncRun(&Pip::infoImpl, this, package);
}
PipPackageInfo Pip::infoImpl(const PipPackage &package)
diff --git a/src/plugins/python/pyside.cpp b/src/plugins/python/pyside.cpp
index 592addd3085..f994108cae0 100644
--- a/src/plugins/python/pyside.cpp
+++ b/src/plugins/python/pyside.cpp
@@ -17,10 +17,10 @@
#include <texteditor/textdocument.h>
#include <utils/algorithm.h>
+#include <utils/asynctask.h>
#include <utils/infobar.h>
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
-#include <utils/runextensions.h>
#include <QRegularExpression>
#include <QTextCursor>
@@ -92,15 +92,6 @@ void PySideInstaller::installPyside(const FilePath &python,
install->run();
}
-void PySideInstaller::changeInterpreter(const QString &interpreterId,
- RunConfiguration *runConfig)
-{
- if (runConfig) {
- if (auto aspect = runConfig->aspect<InterpreterAspect>())
- aspect->setCurrentInterpreter(PythonSettings::interpreter(interpreterId));
- }
-}
-
void PySideInstaller::handlePySideMissing(const FilePath &python,
const QString &pySide,
TextEditor::TextDocument *document)
@@ -140,8 +131,7 @@ void PySideInstaller::runPySideChecker(const FilePath &python,
handlePySideMissing(python, pySide, document);
watcher->deleteLater();
});
- watcher->setFuture(
- Utils::runAsync(&missingPySideInstallation, python, pySide));
+ watcher->setFuture(Utils::asyncRun(&missingPySideInstallation, python, pySide));
}
} // Python::Internal
diff --git a/src/plugins/python/pyside.h b/src/plugins/python/pyside.h
index 299a095412a..3b4f99974a5 100644
--- a/src/plugins/python/pyside.h
+++ b/src/plugins/python/pyside.h
@@ -30,7 +30,6 @@ private:
void installPyside(const Utils::FilePath &python,
const QString &pySide, TextEditor::TextDocument *document);
- void changeInterpreter(const QString &interpreterId, ProjectExplorer::RunConfiguration *runConfig);
void handlePySideMissing(const Utils::FilePath &python,
const QString &pySide,
TextEditor::TextDocument *document);
diff --git a/src/plugins/python/pysidebuildconfiguration.cpp b/src/plugins/python/pysidebuildconfiguration.cpp
index 3480be3e87a..ce37bb67b32 100644
--- a/src/plugins/python/pysidebuildconfiguration.cpp
+++ b/src/plugins/python/pysidebuildconfiguration.cpp
@@ -42,7 +42,7 @@ PySideBuildStepFactory::PySideBuildStepFactory()
registerStep<PySideBuildStep>(pySideBuildStep);
setSupportedProjectType(PythonProjectId);
setDisplayName(Tr::tr("Run PySide6 project tool"));
- setFlags(BuildStepInfo::UniqueStep);
+ setFlags(BuildStep::UniqueStep);
}
PySideBuildStep::PySideBuildStep(BuildStepList *bsl, Id id)
diff --git a/src/plugins/python/python.qbs b/src/plugins/python/python.qbs
index 9f8288cb11a..5186dafcdcd 100644
--- a/src/plugins/python/python.qbs
+++ b/src/plugins/python/python.qbs
@@ -49,6 +49,8 @@ QtcPlugin {
"pythontr.h",
"pythonutils.cpp",
"pythonutils.h",
+ "pythonwizardpage.cpp",
+ "pythonwizardpage.h",
]
}
}
diff --git a/src/plugins/python/pythoneditor.cpp b/src/plugins/python/pythoneditor.cpp
index 6551facef50..ca6b606616a 100644
--- a/src/plugins/python/pythoneditor.cpp
+++ b/src/plugins/python/pythoneditor.cpp
@@ -19,7 +19,7 @@
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorer.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <texteditor/textdocument.h>
@@ -152,7 +152,7 @@ void PythonEditorWidget::setUserDefinedPython(const Interpreter &interpreter)
QTC_ASSERT(pythonDocument, return);
FilePath documentPath = pythonDocument->filePath();
QTC_ASSERT(!documentPath.isEmpty(), return);
- if (Project *project = SessionManager::projectForFile(documentPath)) {
+ if (Project *project = ProjectManager::projectForFile(documentPath)) {
if (Target *target = project->activeTarget()) {
if (RunConfiguration *rc = target->activeRunConfiguration()) {
if (auto interpretersAspect= rc->aspect<InterpreterAspect>()) {
@@ -163,6 +163,7 @@ void PythonEditorWidget::setUserDefinedPython(const Interpreter &interpreter)
}
}
definePythonForDocument(textDocument()->filePath(), interpreter.command);
+ updateInterpretersSelector();
pythonDocument->checkForPyls();
}
@@ -184,7 +185,7 @@ void PythonEditorWidget::updateInterpretersSelector()
disconnect(connection);
m_projectConnections.clear();
const FilePath documentPath = textDocument()->filePath();
- if (Project *project = SessionManager::projectForFile(documentPath)) {
+ if (Project *project = ProjectManager::projectForFile(documentPath)) {
m_projectConnections << connect(project,
&Project::activeTargetChanged,
this,
@@ -212,33 +213,51 @@ void PythonEditorWidget::updateInterpretersSelector()
m_interpreters->setText(text);
};
- const FilePath currentInterpreter = detectPython(textDocument()->filePath());
+ const FilePath currentInterpreterPath = detectPython(textDocument()->filePath());
const QList<Interpreter> configuredInterpreters = PythonSettings::interpreters();
- bool foundCurrentInterpreter = false;
auto interpretersGroup = new QActionGroup(menu);
interpretersGroup->setExclusive(true);
+ std::optional<Interpreter> currentInterpreter;
for (const Interpreter &interpreter : configuredInterpreters) {
QAction *action = interpretersGroup->addAction(interpreter.name);
connect(action, &QAction::triggered, this, [this, interpreter]() {
setUserDefinedPython(interpreter);
});
action->setCheckable(true);
- if (!foundCurrentInterpreter && interpreter.command == currentInterpreter) {
- foundCurrentInterpreter = true;
+ if (!currentInterpreter && interpreter.command == currentInterpreterPath) {
+ currentInterpreter = interpreter;
action->setChecked(true);
setButtonText(interpreter.name);
m_interpreters->setToolTip(interpreter.command.toUserOutput());
}
}
menu->addActions(interpretersGroup->actions());
- if (!foundCurrentInterpreter) {
- if (currentInterpreter.exists())
- setButtonText(currentInterpreter.toUserOutput());
+ if (!currentInterpreter) {
+ if (currentInterpreterPath.exists())
+ setButtonText(currentInterpreterPath.toUserOutput());
else
setButtonText(Tr::tr("No Python Selected"));
}
- if (!interpretersGroup->actions().isEmpty())
- menu->addSeparator();
+ if (!interpretersGroup->actions().isEmpty()) {
+ menu->addSeparator();
+ auto venvAction = menu->addAction(Tr::tr("Create Virtual Environment"));
+ connect(venvAction,
+ &QAction::triggered,
+ this,
+ [self = QPointer<PythonEditorWidget>(this), currentInterpreter]() {
+ if (!currentInterpreter)
+ return;
+ auto callback = [self](const std::optional<Interpreter> &venvInterpreter) {
+ if (self && venvInterpreter)
+ self->setUserDefinedPython(*venvInterpreter);
+ };
+ PythonSettings::createVirtualEnvironmentInteractive(self->textDocument()
+ ->filePath()
+ .parentDir(),
+ *currentInterpreter,
+ callback);
+ });
+ }
auto settingsAction = menu->addAction(Tr::tr("Manage Python Interpreters"));
connect(settingsAction, &QAction::triggered, this, []() {
Core::ICore::showOptionsDialog(Constants::C_PYTHONOPTIONS_PAGE_ID);
diff --git a/src/plugins/python/pythonlanguageclient.cpp b/src/plugins/python/pythonlanguageclient.cpp
index dde324dbd95..088cd516440 100644
--- a/src/plugins/python/pythonlanguageclient.cpp
+++ b/src/plugins/python/pythonlanguageclient.cpp
@@ -23,15 +23,15 @@
#include <languageserverprotocol/workspace.h>
#include <projectexplorer/extracompiler.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <texteditor/textdocument.h>
#include <texteditor/texteditor.h>
+#include <utils/asynctask.h>
#include <utils/infobar.h>
#include <utils/qtcprocess.h>
-#include <utils/runextensions.h>
#include <utils/variablechooser.h>
#include <QCheckBox>
@@ -341,7 +341,7 @@ void PyLSConfigureAssistant::openDocumentWithPython(const FilePath &python,
instance()->handlePyLSState(python, watcher->result(), document);
watcher->deleteLater();
});
- watcher->setFuture(Utils::runAsync(&checkPythonLanguageServer, python));
+ watcher->setFuture(Utils::asyncRun(&checkPythonLanguageServer, python));
}
void PyLSConfigureAssistant::handlePyLSState(const FilePath &python,
diff --git a/src/plugins/python/pythonplugin.cpp b/src/plugins/python/pythonplugin.cpp
index dff3c0cb220..7eefbc5d4bd 100644
--- a/src/plugins/python/pythonplugin.cpp
+++ b/src/plugins/python/pythonplugin.cpp
@@ -6,10 +6,12 @@
#include "pysidebuildconfiguration.h"
#include "pythoneditor.h"
#include "pythonproject.h"
-#include "pythonsettings.h"
#include "pythonrunconfiguration.h"
+#include "pythonsettings.h"
+#include "pythonwizardpage.h"
#include <projectexplorer/buildtargetinfo.h>
+#include <projectexplorer/jsonwizard/jsonwizardfactory.h>
#include <projectexplorer/localenvironmentaspect.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/projectmanager.h>
@@ -57,6 +59,8 @@ void PythonPlugin::initialize()
d = new PythonPluginPrivate;
ProjectManager::registerProjectType<PythonProject>(PythonMimeType);
+ ProjectManager::registerProjectType<PythonProject>(PythonMimeTypeLegacy);
+ JsonWizardFactory::registerPageFactory(new PythonWizardPageFactory);
}
void PythonPlugin::extensionsInitialized()
diff --git a/src/plugins/python/pythonproject.h b/src/plugins/python/pythonproject.h
index 0217a77b615..676e010a015 100644
--- a/src/plugins/python/pythonproject.h
+++ b/src/plugins/python/pythonproject.h
@@ -7,7 +7,8 @@
namespace Python::Internal {
-const char PythonMimeType[] = "text/x-python-project"; // ### FIXME
+const char PythonMimeType[] = "text/x-python-project";
+const char PythonMimeTypeLegacy[] = "text/x-pyqt-project";
const char PythonProjectId[] = "PythonProject";
const char PythonErrorTaskCategory[] = "Task.Category.Python";
diff --git a/src/plugins/python/pythonsettings.cpp b/src/plugins/python/pythonsettings.cpp
index 9306fe3b6f4..ff3a18ef149 100644
--- a/src/plugins/python/pythonsettings.cpp
+++ b/src/plugins/python/pythonsettings.cpp
@@ -6,6 +6,7 @@
#include "pythonconstants.h"
#include "pythonplugin.h"
#include "pythontr.h"
+#include "pythonutils.h"
#include <coreplugin/dialogs/ioptionspage.h>
#include <coreplugin/icore.h>
@@ -30,19 +31,22 @@
#include <utils/treemodel.h>
#include <utils/utilsicons.h>
+#include <QCheckBox>
+#include <QComboBox>
+#include <QDialogButtonBox>
#include <QDir>
+#include <QFormLayout>
+#include <QGroupBox>
+#include <QJsonDocument>
+#include <QJsonObject>
#include <QLabel>
-#include <QPushButton>
#include <QPointer>
+#include <QPushButton>
#include <QSettings>
#include <QStackedWidget>
#include <QTreeView>
-#include <QWidget>
#include <QVBoxLayout>
-#include <QGroupBox>
-#include <QCheckBox>
-#include <QJsonDocument>
-#include <QJsonObject>
+#include <QWidget>
using namespace ProjectExplorer;
using namespace Utils;
@@ -69,7 +73,7 @@ static Interpreter createInterpreter(const FilePath &python,
result.name = defaultName;
QDir pythonDir(python.parentDir().toString());
if (pythonDir.exists() && pythonDir.exists("activate") && pythonDir.cdUp())
- result.name += QString(" (%1 Virtual Environment)").arg(pythonDir.dirName());
+ result.name += QString(" (%1)").arg(pythonDir.dirName());
if (!suffix.isEmpty())
result.name += ' ' + suffix;
@@ -769,12 +773,86 @@ void PythonSettings::addInterpreter(const Interpreter &interpreter, bool isDefau
saveSettings();
}
+Interpreter PythonSettings::addInterpreter(const FilePath &interpreterPath,
+ bool isDefault,
+ const QString &nameSuffix)
+{
+ const Interpreter interpreter = createInterpreter(interpreterPath, {}, nameSuffix);
+ addInterpreter(interpreter, isDefault);
+ return interpreter;
+}
+
PythonSettings *PythonSettings::instance()
{
QTC_CHECK(settingsInstance);
return settingsInstance;
}
+void PythonSettings::createVirtualEnvironmentInteractive(
+ const FilePath &startDirectory,
+ const Interpreter &defaultInterpreter,
+ const std::function<void(std::optional<Interpreter>)> &callback)
+{
+ QDialog dialog;
+ dialog.setModal(true);
+ auto layout = new QFormLayout(&dialog);
+ auto interpreters = new QComboBox;
+ const QString preselectedId = defaultInterpreter.id.isEmpty()
+ ? PythonSettings::defaultInterpreter().id
+ : defaultInterpreter.id;
+ for (const Interpreter &interpreter : PythonSettings::interpreters()) {
+ interpreters->addItem(interpreter.name, interpreter.id);
+ if (!preselectedId.isEmpty() && interpreter.id == preselectedId)
+ interpreters->setCurrentIndex(interpreters->count() - 1);
+ }
+ layout->addRow(Tr::tr("Python Interpreter"), interpreters);
+ auto pathChooser = new PathChooser();
+ pathChooser->setInitialBrowsePathBackup(startDirectory);
+ pathChooser->setExpectedKind(PathChooser::Directory);
+ pathChooser->setPromptDialogTitle(Tr::tr("New Python Virtual Environment Directory"));
+ layout->addRow(Tr::tr("Virtual Environment Directory"), pathChooser);
+ auto buttons = new QDialogButtonBox(QDialogButtonBox::Cancel);
+ auto createButton = buttons->addButton(Tr::tr("Create"), QDialogButtonBox::AcceptRole);
+ createButton->setEnabled(false);
+ connect(pathChooser,
+ &PathChooser::validChanged,
+ createButton,
+ [createButton](bool valid) { createButton->setEnabled(valid); });
+ connect(buttons, &QDialogButtonBox::accepted, &dialog, &QDialog::accept);
+ connect(buttons, &QDialogButtonBox::rejected, &dialog, &QDialog::reject);
+ layout->addRow(buttons);
+ dialog.setLayout(layout);
+ if (dialog.exec() == QDialog::Rejected) {
+ callback({});
+ return;
+ }
+
+ const Interpreter interpreter = PythonSettings::interpreter(
+ interpreters->currentData().toString());
+
+ auto venvDir = pathChooser->filePath();
+ createVirtualEnvironment(venvDir, interpreter, callback);
+}
+
+void PythonSettings::createVirtualEnvironment(
+ const FilePath &directory,
+ const Interpreter &interpreter,
+ const std::function<void(std::optional<Interpreter>)> &callback,
+ const QString &nameSuffix)
+{
+ createVenv(interpreter.command, directory, [directory, callback, nameSuffix](bool success) {
+ std::optional<Interpreter> result;
+ if (success) {
+ FilePath venvPython = directory.osType() == Utils::OsTypeWindows ? directory / "Scripts"
+ : directory / "bin";
+ venvPython = venvPython.pathAppended("python").withExecutableSuffix();
+ if (venvPython.exists())
+ result = PythonSettings::addInterpreter(venvPython, false, nameSuffix);
+ }
+ callback(result);
+ });
+}
+
QList<Interpreter> PythonSettings::detectPythonVenvs(const FilePath &path)
{
QList<Interpreter> result;
diff --git a/src/plugins/python/pythonsettings.h b/src/plugins/python/pythonsettings.h
index 693c7322085..35939c1ecd1 100644
--- a/src/plugins/python/pythonsettings.h
+++ b/src/plugins/python/pythonsettings.h
@@ -24,12 +24,23 @@ public:
static Interpreter interpreter(const QString &interpreterId);
static void setInterpreter(const QList<Interpreter> &interpreters, const QString &defaultId);
static void addInterpreter(const Interpreter &interpreter, bool isDefault = false);
+ static Interpreter addInterpreter(const Utils::FilePath &interpreterPath,
+ bool isDefault = false,
+ const QString &nameSuffix = {});
static void setPyLSConfiguration(const QString &configuration);
static bool pylsEnabled();
static void setPylsEnabled(const bool &enabled);
static QString pylsConfiguration();
static PythonSettings *instance();
-
+ static void createVirtualEnvironmentInteractive(
+ const Utils::FilePath &startDirectory,
+ const Interpreter &defaultInterpreter,
+ const std::function<void(std::optional<Interpreter>)> &callback);
+ static void createVirtualEnvironment(
+ const Utils::FilePath &directory,
+ const Interpreter &interpreter,
+ const std::function<void(std::optional<Interpreter>)> &callback,
+ const QString &nameSuffix = {});
static QList<Interpreter> detectPythonVenvs(const Utils::FilePath &path);
signals:
diff --git a/src/plugins/python/pythonutils.cpp b/src/plugins/python/pythonutils.cpp
index 346c42d183d..1f89eef5b5f 100644
--- a/src/plugins/python/pythonutils.cpp
+++ b/src/plugins/python/pythonutils.cpp
@@ -8,9 +8,10 @@
#include "pythontr.h"
#include <coreplugin/messagemanager.h>
+#include <coreplugin/progressmanager/processprogress.h>
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <utils/algorithm.h>
@@ -31,9 +32,9 @@ static QHash<FilePath, FilePath> &userDefinedPythonsForDocument()
FilePath detectPython(const FilePath &documentPath)
{
Project *project = documentPath.isEmpty() ? nullptr
- : SessionManager::projectForFile(documentPath);
+ : ProjectManager::projectForFile(documentPath);
if (!project)
- project = SessionManager::startupProject();
+ project = ProjectManager::startupProject();
Environment env = Environment::systemEnvironment();
@@ -107,7 +108,7 @@ void openPythonRepl(QObject *parent, const FilePath &file, ReplType type)
{
static const auto workingDir = [](const FilePath &file) {
if (file.isEmpty()) {
- if (Project *project = SessionManager::startupProject())
+ if (Project *project = ProjectManager::startupProject())
return project->projectDirectory();
return FilePath::currentWorkingPath();
}
@@ -155,7 +156,7 @@ QString pythonName(const FilePath &pythonPath)
PythonProject *pythonProjectForFile(const FilePath &pythonFile)
{
- for (Project *project : SessionManager::projects()) {
+ for (Project *project : ProjectManager::projects()) {
if (auto pythonProject = qobject_cast<PythonProject *>(project)) {
if (pythonProject->isKnownFile(pythonFile))
return pythonProject;
@@ -164,4 +165,24 @@ PythonProject *pythonProjectForFile(const FilePath &pythonFile)
return nullptr;
}
+void createVenv(const Utils::FilePath &python,
+ const Utils::FilePath &venvPath,
+ const std::function<void(bool)> &callback)
+{
+ QTC_ASSERT(python.isExecutableFile(), callback(false); return);
+ QTC_ASSERT(!venvPath.exists() || venvPath.isDir(), callback(false); return);
+
+ const CommandLine command(python, QStringList{"-m", "venv", venvPath.toUserOutput()});
+
+ auto process = new QtcProcess;
+ auto progress = new Core::ProcessProgress(process);
+ progress->setDisplayName(Tr::tr("Create Python venv"));
+ QObject::connect(process, &QtcProcess::done, [process, callback](){
+ callback(process->result() == ProcessResult::FinishedWithSuccess);
+ process->deleteLater();
+ });
+ process->setCommand(command);
+ process->start();
+}
+
} // Python::Internal
diff --git a/src/plugins/python/pythonutils.h b/src/plugins/python/pythonutils.h
index 8d5b06974ef..f3e685b4ae0 100644
--- a/src/plugins/python/pythonutils.h
+++ b/src/plugins/python/pythonutils.h
@@ -16,4 +16,8 @@ QString pythonName(const Utils::FilePath &pythonPath);
class PythonProject;
PythonProject *pythonProjectForFile(const Utils::FilePath &pythonFile);
+void createVenv(const Utils::FilePath &python,
+ const Utils::FilePath &venvPath,
+ const std::function<void(bool)> &callback);
+
} // Python::Internal
diff --git a/src/plugins/python/pythonwizardpage.cpp b/src/plugins/python/pythonwizardpage.cpp
new file mode 100644
index 00000000000..b13cf3c346d
--- /dev/null
+++ b/src/plugins/python/pythonwizardpage.cpp
@@ -0,0 +1,219 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "pythonwizardpage.h"
+
+#include "pythonconstants.h"
+#include "pythonsettings.h"
+#include "pythontr.h"
+
+#include <coreplugin/generatedfile.h>
+
+#include <utils/algorithm.h>
+#include <utils/layoutbuilder.h>
+#include <utils/mimeutils.h>
+#include <utils/qtcassert.h>
+
+#include <projectexplorer/project.h>
+#include <projectexplorer/projectmanager.h>
+#include <projectexplorer/target.h>
+
+using namespace ProjectExplorer;
+using namespace Utils;
+
+namespace Python::Internal {
+
+PythonWizardPageFactory::PythonWizardPageFactory()
+{
+ setTypeIdsSuffix("PythonConfiguration");
+}
+
+WizardPage *PythonWizardPageFactory::create(JsonWizard *wizard, Id typeId, const QVariant &data)
+{
+ Q_UNUSED(wizard)
+
+ QTC_ASSERT(canCreate(typeId), return nullptr);
+
+ QList<QPair<QString, QVariant>> pySideAndData;
+ for (const QVariant &item : data.toMap().value("items").toList()) {
+ const QMap<QString, QVariant> map = item.toMap();
+ const QVariant name = map.value("trKey");
+ if (name.isValid())
+ pySideAndData.emplaceBack(QPair<QString, QVariant>{name.toString(), map.value("value")});
+ }
+ bool validIndex = false;
+ int defaultPySide = data.toMap().value("index").toInt(&validIndex);
+ if (!validIndex)
+ defaultPySide = -1;
+ return new PythonWizardPage(pySideAndData, defaultPySide);
+}
+
+static bool validItem(const QVariant &item)
+{
+ QMap<QString, QVariant> map = item.toMap();
+ if (!map.value("trKey").canConvert<QString>())
+ return false;
+ map = map.value("value").toMap();
+ return map.value("PySideVersion").canConvert<QString>();
+}
+
+bool PythonWizardPageFactory::validateData(Id typeId, const QVariant &data, QString *errorMessage)
+{
+ QTC_ASSERT(canCreate(typeId), return false);
+ const QList<QVariant> items = data.toMap().value("items").toList();
+
+ if (items.isEmpty()) {
+ if (errorMessage) {
+ *errorMessage = Tr::tr("'data' of a Python wizard page expects a map with 'items' "
+ "containing a list of objects");
+ }
+ return false;
+ }
+
+ if (!Utils::allOf(items, &validItem)) {
+ if (errorMessage) {
+ *errorMessage = Tr::tr(
+ "An item of Python wizard page data expects a 'trKey' field containing the ui "
+ "visible string for that python version and an field 'value' containing an object "
+ "with a 'PySideVersion' field used for import statements in the python files.");
+ }
+ return false;
+ }
+ return true;
+}
+
+PythonWizardPage::PythonWizardPage(const QList<QPair<QString, QVariant>> &pySideAndData,
+ const int defaultPyside)
+{
+ using namespace Utils::Layouting;
+ m_interpreter.setSettingsDialogId(Constants::C_PYTHONOPTIONS_PAGE_ID);
+ connect(PythonSettings::instance(),
+ &PythonSettings::interpretersChanged,
+ this,
+ &PythonWizardPage::updateInterpreters);
+
+ m_pySideVersion.setLabelText(Tr::tr("PySide version"));
+ m_pySideVersion.setDisplayStyle(SelectionAspect::DisplayStyle::ComboBox);
+ for (auto [name, data] : pySideAndData)
+ m_pySideVersion.addOption(SelectionAspect::Option(name, {}, data));
+ if (defaultPyside >= 0)
+ m_pySideVersion.setDefaultValue(defaultPyside);
+
+ m_createVenv.setLabelText(Tr::tr("Create new Virtual Environment"));
+
+ m_venvPath.setLabelText(Tr::tr("Path to virtual environment"));
+ m_venvPath.setDisplayStyle(StringAspect::PathChooserDisplay);
+ m_venvPath.setEnabler(&m_createVenv);
+ m_venvPath.setExpectedKind(PathChooser::Directory);
+
+ m_stateLabel = new InfoLabel();
+ m_stateLabel->setWordWrap(true);
+ m_stateLabel->setFilled(true);
+ m_stateLabel->setType(InfoLabel::Error);
+ connect(&m_venvPath, &StringAspect::valueChanged, this, &PythonWizardPage::updateStateLabel);
+ connect(&m_createVenv, &BoolAspect::valueChanged, this, &PythonWizardPage::updateStateLabel);
+
+ Grid {
+ m_pySideVersion, br,
+ m_interpreter, br,
+ m_createVenv, br,
+ m_venvPath, br,
+ m_stateLabel, br
+ }.attachTo(this, WithoutMargins);
+}
+
+void PythonWizardPage::initializePage()
+{
+ auto wiz = qobject_cast<JsonWizard *>(wizard());
+ QTC_ASSERT(wiz, return);
+ connect(wiz, &JsonWizard::filesPolished,
+ this, &PythonWizardPage::setupProject,
+ Qt::UniqueConnection);
+
+ const FilePath projectDir = FilePath::fromString(wiz->property("ProjectDirectory").toString());
+ m_createVenv.setValue(!projectDir.isEmpty());
+ if (m_venvPath.filePath().isEmpty())
+ m_venvPath.setFilePath(projectDir.isEmpty() ? FilePath{} : projectDir / "venv");
+
+ updateInterpreters();
+ updateStateLabel();
+}
+
+bool PythonWizardPage::validatePage()
+{
+ if (m_createVenv.value() && !m_venvPath.pathChooser()->isValid())
+ return false;
+ auto wiz = qobject_cast<JsonWizard *>(wizard());
+ const QMap<QString, QVariant> data = m_pySideVersion.itemValue().toMap();
+ for (auto it = data.begin(), end = data.end(); it != end; ++it)
+ wiz->setValue(it.key(), it.value());
+ return true;
+}
+
+void PythonWizardPage::setupProject(const JsonWizard::GeneratorFiles &files)
+{
+ for (const JsonWizard::GeneratorFile &f : files) {
+ if (f.file.attributes() & Core::GeneratedFile::OpenProjectAttribute) {
+ Interpreter interpreter = m_interpreter.currentInterpreter();
+ Project *project = ProjectManager::openProject(Utils::mimeTypeForFile(f.file.filePath()),
+ f.file.filePath().absoluteFilePath());
+ if (m_createVenv.value()) {
+ auto openProjectWithInterpreter = [f](const std::optional<Interpreter> &interpreter) {
+ if (!interpreter)
+ return;
+ Project *project = ProjectManager::projectWithProjectFilePath(f.file.filePath());
+ if (!project)
+ return;
+ if (Target *target = project->activeTarget()) {
+ if (RunConfiguration *rc = target->activeRunConfiguration()) {
+ if (auto interpreters = rc->aspect<InterpreterAspect>())
+ interpreters->setCurrentInterpreter(*interpreter);
+ }
+ }
+ };
+ PythonSettings::createVirtualEnvironment(m_venvPath.filePath(),
+ interpreter,
+ openProjectWithInterpreter,
+ project ? project->displayName()
+ : QString{});
+ }
+
+ if (project) {
+ project->addTargetForDefaultKit();
+ if (Target *target = project->activeTarget()) {
+ if (RunConfiguration *rc = target->activeRunConfiguration()) {
+ if (auto interpreters = rc->aspect<InterpreterAspect>()) {
+ interpreters->setCurrentInterpreter(interpreter);
+ project->saveSettings();
+ }
+ }
+ }
+ delete project;
+ }
+ }
+ }
+}
+
+void PythonWizardPage::updateInterpreters()
+{
+ m_interpreter.setDefaultInterpreter(PythonSettings::defaultInterpreter());
+ m_interpreter.updateInterpreters(PythonSettings::interpreters());
+}
+
+void PythonWizardPage::updateStateLabel()
+{
+ QTC_ASSERT(m_stateLabel, return);
+ if (m_createVenv.value()) {
+ if (PathChooser *pathChooser = m_venvPath.pathChooser()) {
+ if (!pathChooser->isValid()) {
+ m_stateLabel->show();
+ m_stateLabel->setText(pathChooser->errorMessage());
+ return;
+ }
+ }
+ }
+ m_stateLabel->hide();
+}
+
+} // namespace Python::Internal
+
diff --git a/src/plugins/python/pythonwizardpage.h b/src/plugins/python/pythonwizardpage.h
new file mode 100644
index 00000000000..1d605b24176
--- /dev/null
+++ b/src/plugins/python/pythonwizardpage.h
@@ -0,0 +1,43 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include <projectexplorer/jsonwizard/jsonwizard.h>
+#include <projectexplorer/jsonwizard/jsonwizardpagefactory.h>
+#include <projectexplorer/runconfigurationaspects.h>
+
+#include <utils/aspects.h>
+#include <utils/wizardpage.h>
+
+namespace Python::Internal {
+
+class PythonWizardPageFactory : public ProjectExplorer::JsonWizardPageFactory
+{
+public:
+ PythonWizardPageFactory();
+
+ Utils::WizardPage *create(ProjectExplorer::JsonWizard *wizard,
+ Utils::Id typeId,
+ const QVariant &data) override;
+ bool validateData(Utils::Id typeId, const QVariant &data, QString *errorMessage) override;
+};
+
+class PythonWizardPage : public Utils::WizardPage
+{
+public:
+ PythonWizardPage(const QList<QPair<QString, QVariant>> &pySideAndData, const int defaultPyside);
+ void initializePage() override;
+ bool validatePage() override;
+
+private:
+ void setupProject(const ProjectExplorer::JsonWizard::GeneratorFiles &files);
+ void updateInterpreters();
+ void updateStateLabel();
+
+ ProjectExplorer::InterpreterAspect m_interpreter;
+ Utils::SelectionAspect m_pySideVersion;
+ Utils::BoolAspect m_createVenv;
+ Utils::StringAspect m_venvPath;
+ Utils::InfoLabel *m_stateLabel = nullptr;
+};
+
+} // namespace Python::Internal
diff --git a/src/plugins/qbsprojectmanager/qbsinstallstep.cpp b/src/plugins/qbsprojectmanager/qbsinstallstep.cpp
index 27c875e8688..1aeee5be65b 100644
--- a/src/plugins/qbsprojectmanager/qbsinstallstep.cpp
+++ b/src/plugins/qbsprojectmanager/qbsinstallstep.cpp
@@ -81,7 +81,7 @@ void QbsInstallStep::doRun()
QJsonObject request;
request.insert("type", "install-project");
- request.insert("install-root", installRoot());
+ request.insert("install-root", installRoot().path());
request.insert("clean-install-root", m_cleanInstallRoot->value());
request.insert("keep-going", m_keepGoing->value());
request.insert("dry-run", m_dryRun->value());
@@ -102,10 +102,10 @@ void QbsInstallStep::doCancel()
m_session->cancelCurrentJob();
}
-QString QbsInstallStep::installRoot() const
+FilePath QbsInstallStep::installRoot() const
{
const QbsBuildStep * const bs = buildConfig()->qbsStep();
- return bs ? bs->installRoot().toString() : QString();
+ return bs ? bs->installRoot() : FilePath();
}
const QbsBuildConfiguration *QbsInstallStep::buildConfig() const
@@ -162,7 +162,7 @@ QWidget *QbsInstallStep::createConfigWidget()
{
auto widget = new QWidget;
- auto installRootValueLabel = new QLabel(installRoot());
+ auto installRootValueLabel = new QLabel(installRoot().toUserOutput());
auto commandLineKeyLabel = new QLabel(Tr::tr("Equivalent command line:"));
commandLineKeyLabel->setAlignment(Qt::AlignTop);
@@ -183,7 +183,7 @@ QWidget *QbsInstallStep::createConfigWidget()
builder.attachTo(widget);
const auto updateState = [this, commandLineTextEdit, installRootValueLabel] {
- installRootValueLabel->setText(installRoot());
+ installRootValueLabel->setText(installRoot().toUserOutput());
commandLineTextEdit->setPlainText(buildConfig()->equivalentCommandLine(stepData()));
};
diff --git a/src/plugins/qbsprojectmanager/qbsinstallstep.h b/src/plugins/qbsprojectmanager/qbsinstallstep.h
index 7eef7aa9e14..e0063da8189 100644
--- a/src/plugins/qbsprojectmanager/qbsinstallstep.h
+++ b/src/plugins/qbsprojectmanager/qbsinstallstep.h
@@ -25,7 +25,7 @@ public:
QbsInstallStep(ProjectExplorer::BuildStepList *bsl, Utils::Id id);
~QbsInstallStep() override;
- QString installRoot() const;
+ Utils::FilePath installRoot() const;
QbsBuildStepData stepData() const;
private:
diff --git a/src/plugins/qbsprojectmanager/qbsnodetreebuilder.cpp b/src/plugins/qbsprojectmanager/qbsnodetreebuilder.cpp
index 55ae7d8ac03..82e896e77a8 100644
--- a/src/plugins/qbsprojectmanager/qbsnodetreebuilder.cpp
+++ b/src/plugins/qbsprojectmanager/qbsnodetreebuilder.cpp
@@ -42,6 +42,10 @@ static FileType fileType(const QJsonObject &artifact)
return FileType::StateChart;
if (fileTags.contains("qt.qml.qml"))
return FileType::QML;
+ if (fileTags.contains("application"))
+ return FileType::App;
+ if (fileTags.contains("staticlibrary") || fileTags.contains("dynamiclibrary"))
+ return FileType::Lib;
return FileType::Unknown;
}
diff --git a/src/plugins/qbsprojectmanager/qbsproject.cpp b/src/plugins/qbsprojectmanager/qbsproject.cpp
index dc72660e7a3..7bf1cfd425c 100644
--- a/src/plugins/qbsprojectmanager/qbsproject.cpp
+++ b/src/plugins/qbsprojectmanager/qbsproject.cpp
@@ -41,10 +41,10 @@
#include <projectexplorer/taskhub.h>
#include <projectexplorer/toolchain.h>
#include <utils/algorithm.h>
+#include <utils/asynctask.h>
#include <utils/environment.h>
#include <utils/hostosinfo.h>
#include <utils/qtcassert.h>
-#include <utils/runextensions.h>
#include <qmljs/qmljsmodelmanagerinterface.h>
#include <qmljstools/qmljsmodelmanager.h>
#include <qtsupport/qtcppkitinfo.h>
@@ -483,7 +483,7 @@ void QbsBuildSystem::updateProjectNodes(const std::function<void ()> &continuati
if (continuation)
continuation();
});
- m_treeCreationWatcher->setFuture(runAsync(ProjectExplorerPlugin::sharedThreadPool(),
+ m_treeCreationWatcher->setFuture(Utils::asyncRun(ProjectExplorerPlugin::sharedThreadPool(),
QThread::LowPriority, &QbsNodeTreeBuilder::buildTree,
project()->displayName(), project()->projectFilePath(), project()->projectDirectory(),
projectData()));
@@ -498,7 +498,7 @@ FilePath QbsBuildSystem::installRoot()
if (!step->enabled())
continue;
if (const auto qbsInstallStep = qobject_cast<const QbsInstallStep *>(step))
- return FilePath::fromUserInput(qbsInstallStep->installRoot());
+ return qbsInstallStep->installRoot();
}
}
const QbsBuildStep * const buildStep = m_buildConfiguration->qbsStep();
diff --git a/src/plugins/qbsprojectmanager/qbsprojectmanagerplugin.cpp b/src/plugins/qbsprojectmanager/qbsprojectmanagerplugin.cpp
index 01a58753a20..3245b609bef 100644
--- a/src/plugins/qbsprojectmanager/qbsprojectmanagerplugin.cpp
+++ b/src/plugins/qbsprojectmanager/qbsprojectmanagerplugin.cpp
@@ -33,7 +33,7 @@
#include <projectexplorer/projectexplorericons.h>
#include <projectexplorer/projectmanager.h>
#include <projectexplorer/projecttree.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <qtsupport/qtsupportconstants.h>
@@ -60,7 +60,7 @@ static Node *currentEditorNode()
static QbsProject *currentEditorProject()
{
Core::IDocument *doc = Core::EditorManager::currentDocument();
- return doc ? qobject_cast<QbsProject *>(SessionManager::projectForFile(doc->filePath())) : nullptr;
+ return doc ? qobject_cast<QbsProject *>(ProjectManager::projectForFile(doc->filePath())) : nullptr;
}
class QbsProjectManagerPluginPrivate
@@ -226,13 +226,13 @@ void QbsProjectManagerPlugin::initialize()
connect(Core::EditorManager::instance(), &Core::EditorManager::currentEditorChanged,
this, &QbsProjectManagerPlugin::updateBuildActions);
- connect(SessionManager::instance(), &SessionManager::targetAdded,
+ connect(ProjectManager::instance(), &ProjectManager::targetAdded,
this, &QbsProjectManagerPlugin::targetWasAdded);
- connect(SessionManager::instance(), &SessionManager::targetRemoved,
+ connect(ProjectManager::instance(), &ProjectManager::targetRemoved,
this, &QbsProjectManagerPlugin::updateBuildActions);
- connect(SessionManager::instance(), &SessionManager::startupProjectChanged,
+ connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged,
this, &QbsProjectManagerPlugin::updateReparseQbsAction);
- connect(SessionManager::instance(), &SessionManager::projectAdded,
+ connect(ProjectManager::instance(), &ProjectManager::projectAdded,
this, [this](Project *project) {
auto qbsProject = qobject_cast<QbsProject *>(project);
connect(project, &Project::anyParsingStarted,
@@ -283,7 +283,7 @@ void QbsProjectManagerPlugin::updateContextActions(Node *node)
void QbsProjectManagerPlugin::updateReparseQbsAction()
{
- auto project = qobject_cast<QbsProject *>(SessionManager::startupProject());
+ auto project = qobject_cast<QbsProject *>(ProjectManager::startupProject());
m_reparseQbs->setEnabled(project
&& !BuildManager::isBuilding(project)
&& project && project->activeTarget()
@@ -342,7 +342,7 @@ void QbsProjectManagerPlugin::projectChanged(QbsProject *project)
{
auto qbsProject = qobject_cast<QbsProject *>(project);
- if (!qbsProject || qbsProject == SessionManager::startupProject())
+ if (!qbsProject || qbsProject == ProjectManager::startupProject())
updateReparseQbsAction();
if (!qbsProject || qbsProject == ProjectTree::currentProject())
@@ -537,7 +537,7 @@ void QbsProjectManagerPlugin::reparseSelectedProject()
void QbsProjectManagerPlugin::reparseCurrentProject()
{
- reparseProject(dynamic_cast<QbsProject *>(SessionManager::startupProject()));
+ reparseProject(dynamic_cast<QbsProject *>(ProjectManager::startupProject()));
}
void QbsProjectManagerPlugin::reparseProject(QbsProject *project)
diff --git a/src/plugins/qbsprojectmanager/qbsprojectparser.cpp b/src/plugins/qbsprojectmanager/qbsprojectparser.cpp
index 1620b747ac3..ee1aafdb6b0 100644
--- a/src/plugins/qbsprojectmanager/qbsprojectparser.cpp
+++ b/src/plugins/qbsprojectmanager/qbsprojectparser.cpp
@@ -67,10 +67,10 @@ void QbsProjectParser::parse(const QVariantMap &config, const Environment &env,
request.insert("override-build-graph-data", true);
static const auto envToJson = [](const Environment &env) {
QJsonObject envObj;
- for (auto it = env.constBegin(); it != env.constEnd(); ++it) {
- if (env.isEnabled(it))
- envObj.insert(env.key(it), env.value(it));
- }
+ env.forEachEntry([&](const QString &key, const QString &value, bool enabled) {
+ if (enabled)
+ envObj.insert(key, value);
+ });
return envObj;
};
request.insert("environment", envToJson(env));
diff --git a/src/plugins/qmakeprojectmanager/customwidgetwizard/plugingenerator.cpp b/src/plugins/qmakeprojectmanager/customwidgetwizard/plugingenerator.cpp
index ab3f59d8518..28e67ec3ece 100644
--- a/src/plugins/qmakeprojectmanager/customwidgetwizard/plugingenerator.cpp
+++ b/src/plugins/qmakeprojectmanager/customwidgetwizard/plugingenerator.cpp
@@ -49,17 +49,10 @@ static Core::GeneratedFile generateIconFile(const FilePath &source,
return rc;
}
-static QString qt4PluginExport(const QString &pluginName, const QString &pluginClassName)
-{
- return QLatin1String("#if QT_VERSION < 0x050000\nQ_EXPORT_PLUGIN2(")
- + pluginName + QLatin1String(", ") + pluginClassName
- + QLatin1String(")\n#endif // QT_VERSION < 0x050000");
-}
-
static QString qt5PluginMetaData(const QString &interfaceName)
{
- return QLatin1String("#if QT_VERSION >= 0x050000\n Q_PLUGIN_METADATA(IID \"org.qt-project.Qt.")
- + interfaceName + QLatin1String("\")\n#endif // QT_VERSION >= 0x050000");
+ return QLatin1String(" Q_PLUGIN_METADATA(IID \"org.qt-project.Qt.")
+ + interfaceName + QLatin1String("\")");
}
QList<Core::GeneratedFile> PluginGenerator::generatePlugin(const GenerationParameters& p, const PluginOptions &options,
@@ -121,10 +114,8 @@ QList<Core::GeneratedFile> PluginGenerator::generatePlugin(const GenerationPara
sm.insert(QLatin1String("WIDGET_TOOLTIP"), cStringQuote(wo.toolTip));
sm.insert(QLatin1String("WIDGET_WHATSTHIS"), cStringQuote(wo.whatsThis));
sm.insert(QLatin1String("WIDGET_ISCONTAINER"), wo.isContainer ? QLatin1String("true") : QLatin1String("false"));
- sm.insert(QLatin1String("WIDGET_DOMXML"), cStringQuote(wo.domXml));
- sm.insert(QLatin1String("SINGLE_PLUGIN_EXPORT"),
- options.widgetOptions.count() == 1 ?
- qt4PluginExport(options.pluginName, wo.pluginClassName) : QString());
+ sm.insert(QLatin1String("WIDGET_DOMXML"), QLatin1String("R\"(")
+ + wo.domXml.trimmed() + QLatin1String(")\""));
const QString pluginSourceContents = processTemplate(p.templatePath + QLatin1String("/tpl_single.cpp"), sm, errorMessage);
if (pluginSourceContents.isEmpty())
@@ -239,7 +230,6 @@ QList<Core::GeneratedFile> PluginGenerator::generatePlugin(const GenerationPara
options.collectionHeaderFile +
QLatin1String("\""));
sm.insert(QLatin1String("PLUGIN_ADDITIONS"), pluginAdditions);
- sm.insert(QLatin1String("COLLECTION_PLUGIN_EXPORT"), qt4PluginExport(options.pluginName, options.collectionClassName));
const QString collectionSourceFileContents = processTemplate(p.templatePath + QLatin1String("/tpl_collection.cpp"), sm, errorMessage);
if (collectionSourceFileContents.isEmpty())
return QList<Core::GeneratedFile>();
diff --git a/src/plugins/qmakeprojectmanager/librarydetailscontroller.cpp b/src/plugins/qmakeprojectmanager/librarydetailscontroller.cpp
index b1427c84bae..8c094c557a8 100644
--- a/src/plugins/qmakeprojectmanager/librarydetailscontroller.cpp
+++ b/src/plugins/qmakeprojectmanager/librarydetailscontroller.cpp
@@ -3,13 +3,13 @@
#include "librarydetailscontroller.h"
-#include "qmakebuildconfiguration.h"
#include "qmakeparsernodes.h"
#include "qmakeproject.h"
#include "qmakeprojectmanagertr.h"
+#include <projectexplorer/buildconfiguration.h>
#include <projectexplorer/projectexplorer.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <utils/hostosinfo.h>
@@ -21,6 +21,7 @@
#include <QDir>
#include <QFileInfo>
#include <QGroupBox>
+#include <QLabel>
#include <QRadioButton>
#include <QTextStream>
@@ -868,7 +869,7 @@ QString PackageLibraryDetailsController::snippet() const
bool PackageLibraryDetailsController::isLinkPackageGenerated() const
{
- const Project *project = SessionManager::projectForFile(proFile());
+ const Project *project = ProjectManager::projectForFile(proFile());
if (!project)
return false;
@@ -1016,7 +1017,7 @@ void InternalLibraryDetailsController::updateProFile()
libraryDetailsWidget()->libraryComboBox->clear();
const QmakeProject *project
- = dynamic_cast<QmakeProject *>(SessionManager::projectForFile(proFile()));
+ = dynamic_cast<QmakeProject *>(ProjectManager::projectForFile(proFile()));
if (!project)
return;
@@ -1104,7 +1105,7 @@ QString InternalLibraryDetailsController::snippet() const
// the build directory of the active build configuration
QDir rootBuildDir = rootDir; // If the project is unconfigured use the project dir
- if (const Project *project = SessionManager::projectForFile(proFile())) {
+ if (const Project *project = ProjectManager::projectForFile(proFile())) {
if (ProjectExplorer::Target *t = project->activeTarget())
if (ProjectExplorer::BuildConfiguration *bc = t->activeBuildConfiguration())
rootBuildDir.setPath(bc->buildDirectory().toString());
diff --git a/src/plugins/qmakeprojectmanager/makefileparse.cpp b/src/plugins/qmakeprojectmanager/makefileparse.cpp
index e9d8c2878e7..5fbab2084dd 100644
--- a/src/plugins/qmakeprojectmanager/makefileparse.cpp
+++ b/src/plugins/qmakeprojectmanager/makefileparse.cpp
@@ -359,11 +359,12 @@ void MakeFileParse::parseCommandLine(const QString &command, const QString &proj
// Unit tests:
#ifdef WITH_TESTS
-# include <QTest>
-# include "qmakeprojectmanagerplugin.h"
+#include "qmakeprojectmanagerplugin.h"
-# include "projectexplorer/outputparser_test.h"
+#include <projectexplorer/outputparser_test.h>
+
+#include <QTest>
using namespace QmakeProjectManager::Internal;
using namespace ProjectExplorer;
@@ -479,8 +480,8 @@ void QmakeProjectManagerPlugin::testMakefileParser()
MakeFileParse parser("/tmp/something", MakeFileParse::Mode::FilterKnownConfigValues);
parser.parseCommandLine(command, project);
- QCOMPARE(ProcessArgs::splitArgs(parser.unparsedArguments()),
- ProcessArgs::splitArgs(unparsedArguments));
+ QCOMPARE(ProcessArgs::splitArgs(parser.unparsedArguments(), HostOsInfo::hostOs()),
+ ProcessArgs::splitArgs(unparsedArguments, HostOsInfo::hostOs()));
QCOMPARE(parser.effectiveBuildConfig({}), effectiveBuildConfig);
const QMakeStepConfig qmsc = parser.config();
diff --git a/src/plugins/qmakeprojectmanager/profileeditor.cpp b/src/plugins/qmakeprojectmanager/profileeditor.cpp
index 810d5d4f2e0..b492112db3d 100644
--- a/src/plugins/qmakeprojectmanager/profileeditor.cpp
+++ b/src/plugins/qmakeprojectmanager/profileeditor.cpp
@@ -7,18 +7,22 @@
#include "profilehighlighter.h"
#include "profilehoverhandler.h"
#include "qmakenodes.h"
-#include "qmakeproject.h"
#include "qmakeprojectmanagerconstants.h"
#include <coreplugin/coreplugintr.h>
+
#include <extensionsystem/pluginmanager.h>
+
#include <projectexplorer/buildconfiguration.h>
#include <projectexplorer/projectexplorerconstants.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
+
#include <qtsupport/qtsupportconstants.h>
+
#include <texteditor/textdocument.h>
#include <texteditor/texteditoractionhandler.h>
+
#include <utils/fsengine/fileiconprovider.h>
#include <utils/qtcassert.h>
#include <utils/theme/theme.h>
@@ -65,7 +69,7 @@ QString ProFileEditorWidget::checkForPrfFile(const QString &baseName) const
const QmakePriFileNode *projectNode = nullptr;
// FIXME: Remove this check once project nodes are fully "static".
- for (const Project * const project : SessionManager::projects()) {
+ for (const Project * const project : ProjectManager::projects()) {
static const auto isParsing = [](const Project *project) {
for (const Target * const t : project->targets()) {
for (const BuildConfiguration * const bc : t->buildConfigurations()) {
diff --git a/src/plugins/qmakeprojectmanager/qmakenodes.h b/src/plugins/qmakeprojectmanager/qmakenodes.h
index 778f84b64fd..7dc1133fd0c 100644
--- a/src/plugins/qmakeprojectmanager/qmakenodes.h
+++ b/src/plugins/qmakeprojectmanager/qmakenodes.h
@@ -12,7 +12,6 @@
namespace QmakeProjectManager {
class QmakeProFileNode;
-class QmakeProject;
// Implements ProjectNode for qmake .pri files
class QMAKEPROJECTMANAGER_EXPORT QmakePriFileNode : public ProjectExplorer::ProjectNode
diff --git a/src/plugins/qmakeprojectmanager/qmakenodetreebuilder.cpp b/src/plugins/qmakeprojectmanager/qmakenodetreebuilder.cpp
index f261594e59e..752cb7c3192 100644
--- a/src/plugins/qmakeprojectmanager/qmakenodetreebuilder.cpp
+++ b/src/plugins/qmakeprojectmanager/qmakenodetreebuilder.cpp
@@ -202,7 +202,23 @@ static void createTree(QmakeBuildSystem *buildSystem,
}
}
- if (!generatedFiles.empty()) {
+ FileType targetFileType = FileType::Unknown;
+ FilePath targetBinary;
+ if (proFile && proFile->targetInformation().valid) {
+ if (proFile->projectType() == ProjectType::ApplicationTemplate) {
+ targetFileType = FileType::App;
+ targetBinary = buildSystem->executableFor(proFile);
+ } else if (proFile->projectType() == ProjectType::SharedLibraryTemplate
+ || proFile->projectType() == ProjectType::StaticLibraryTemplate) {
+ targetFileType = FileType::Lib;
+ const FilePaths libs = Utils::sorted(buildSystem->allLibraryTargetFiles(proFile),
+ [](const FilePath &fp1, const FilePath &fp2) {
+ return fp1.fileName().length() < fp2.fileName().length(); });
+ if (!libs.isEmpty())
+ targetBinary = libs.last(); // Longest file name is the one that's not a symlink.
+ }
+ }
+ if (!generatedFiles.empty() || !targetBinary.isEmpty()) {
QTC_CHECK(proFile);
const FilePath baseDir = generatedFiles.size() == 1 ? generatedFiles.first().parentDir()
: buildSystem->buildDir(proFile->filePath());
@@ -214,6 +230,11 @@ static void createTree(QmakeBuildSystem *buildSystem,
fileNode->setIsGenerated(true);
genFolder->addNestedNode(std::move(fileNode));
}
+ if (!targetBinary.isEmpty()) {
+ auto targetFileNode = std::make_unique<FileNode>(targetBinary, targetFileType);
+ targetFileNode->setIsGenerated(true);
+ genFolder->addNestedNode(std::move(targetFileNode));
+ }
node->addNode(std::move(genFolder));
}
diff --git a/src/plugins/qmakeprojectmanager/qmakeparser.cpp b/src/plugins/qmakeprojectmanager/qmakeparser.cpp
index e95b403bff4..2cb9d941c14 100644
--- a/src/plugins/qmakeprojectmanager/qmakeparser.cpp
+++ b/src/plugins/qmakeprojectmanager/qmakeparser.cpp
@@ -71,11 +71,12 @@ OutputLineParser::Result QMakeParser::handleLine(const QString &line, OutputForm
// Unit tests:
#ifdef WITH_TESTS
-# include <QTest>
-# include "qmakeprojectmanagerplugin.h"
+#include "qmakeprojectmanagerplugin.h"
-# include "projectexplorer/outputparser_test.h"
+#include <projectexplorer/outputparser_test.h>
+
+#include <QTest>
using namespace QmakeProjectManager::Internal;
diff --git a/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp b/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp
index e15fadb46db..cad20e189f1 100644
--- a/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp
+++ b/src/plugins/qmakeprojectmanager/qmakeparsernodes.cpp
@@ -30,6 +30,7 @@
#include <utils/QtConcurrentTools>
#include <utils/algorithm.h>
+#include <utils/asynctask.h>
#include <utils/filesystemwatcher.h>
#include <utils/mimeutils.h>
#include <utils/qtcassert.h>
@@ -1284,9 +1285,9 @@ void QmakeProFile::asyncUpdate()
if (!includedInExactParse())
m_readerExact->setExact(false);
QmakeEvalInput input = evalInput();
- QFuture<QmakeEvalResultPtr> future = runAsync(ProjectExplorerPlugin::sharedThreadPool(),
- QThread::LowestPriority,
- &QmakeProFile::asyncEvaluate, this, input);
+ QFuture<QmakeEvalResultPtr> future = Utils::asyncRun(ProjectExplorerPlugin::sharedThreadPool(),
+ QThread::LowestPriority,
+ &QmakeProFile::asyncEvaluate, this, input);
m_parseFutureWatcher->setFuture(future);
}
@@ -1630,9 +1631,9 @@ QmakeEvalResultPtr QmakeProFile::evaluate(const QmakeEvalInput &input)
return result;
}
-void QmakeProFile::asyncEvaluate(QFutureInterface<QmakeEvalResultPtr> &fi, QmakeEvalInput input)
+void QmakeProFile::asyncEvaluate(QPromise<QmakeEvalResultPtr> &promise, QmakeEvalInput input)
{
- fi.reportResult(evaluate(input));
+ promise.addResult(evaluate(input));
}
bool sortByParserNodes(Node *a, Node *b)
diff --git a/src/plugins/qmakeprojectmanager/qmakeparsernodes.h b/src/plugins/qmakeprojectmanager/qmakeparsernodes.h
index 34c4958564b..9296addfa80 100644
--- a/src/plugins/qmakeprojectmanager/qmakeparsernodes.h
+++ b/src/plugins/qmakeprojectmanager/qmakeparsernodes.h
@@ -336,7 +336,7 @@ private:
static Internal::QmakeEvalResultPtr evaluate(const Internal::QmakeEvalInput &input);
void applyEvaluate(const Internal::QmakeEvalResultPtr &parseResult);
- void asyncEvaluate(QFutureInterface<Internal::QmakeEvalResultPtr> &fi,
+ void asyncEvaluate(QPromise<Internal::QmakeEvalResultPtr> &promise,
Internal::QmakeEvalInput input);
void cleanupProFileReaders();
diff --git a/src/plugins/qmakeprojectmanager/qmakeproject.cpp b/src/plugins/qmakeprojectmanager/qmakeproject.cpp
index 6d840187251..c12827b6b43 100644
--- a/src/plugins/qmakeprojectmanager/qmakeproject.cpp
+++ b/src/plugins/qmakeprojectmanager/qmakeproject.cpp
@@ -40,15 +40,17 @@
#include <proparser/qmakevfs.h>
#include <proparser/qmakeglobals.h>
+#include <qmljs/qmljsmodelmanagerinterface.h>
+
#include <qtsupport/profilereader.h>
#include <qtsupport/qtcppkitinfo.h>
#include <qtsupport/qtkitinformation.h>
#include <qtsupport/qtversionmanager.h>
#include <utils/algorithm.h>
+#include <utils/asynctask.h>
#include <utils/qtcprocess.h>
#include <utils/runextensions.h>
-#include <qmljs/qmljsmodelmanagerinterface.h>
#include <QDebug>
#include <QDir>
@@ -335,7 +337,7 @@ void QmakeBuildSystem::updateCppCodeModel()
rpp.setBuildTargetType(BuildTargetType::Unknown);
break;
}
- const QString includeFileBaseDir = pro->sourceDir().toString();
+ const FilePath includeFileBaseDir = pro->sourceDir();
QStringList cxxArgs = pro->variableValue(Variable::CppFlags);
QStringList cArgs = pro->variableValue(Variable::CFlags);
@@ -872,9 +874,9 @@ QtSupport::ProFileReader *QmakeBuildSystem::createProFileReader(const QmakeProFi
rootProFileName,
deviceRoot());
- Environment::const_iterator eit = env.constBegin(), eend = env.constEnd();
- for (; eit != eend; ++eit)
- m_qmakeGlobals->environment.insert(env.key(eit), env.expandedValueForKey(env.key(eit)));
+ env.forEachEntry([&](const QString &key, const QString &value, bool) {
+ m_qmakeGlobals->environment.insert(key, env.expandVariables(value));
+ });
m_qmakeGlobals->setCommandLineArguments(rootProFileName, qmakeArgs);
m_qmakeGlobals->runSystemFunction = bc->runSystemFunction();
@@ -923,9 +925,9 @@ const FilePath &QmakeBuildSystem::qmakeSysroot() const
void QmakeBuildSystem::destroyProFileReader(QtSupport::ProFileReader *reader)
{
// The ProFileReader destructor is super expensive (but thread-safe).
- const auto deleteFuture = runAsync(ProjectExplorerPlugin::sharedThreadPool(), QThread::LowestPriority,
- [reader] { delete reader; });
- onFinished(deleteFuture, this, [this](const QFuture<void> &) {
+ const auto deleteFuture = Utils::asyncRun(ProjectExplorerPlugin::sharedThreadPool(),
+ [reader] { delete reader; });
+ Utils::onFinished(deleteFuture, this, [this](const QFuture<void> &) {
if (!--m_qmakeGlobalsRefCnt) {
deregisterFromCacheManager();
m_qmakeGlobals.reset();
@@ -1309,15 +1311,13 @@ static FilePath destDirFor(const TargetInformation &ti)
return ti.destDir;
}
-void QmakeBuildSystem::collectLibraryData(const QmakeProFile *file, DeploymentData &deploymentData)
+FilePaths QmakeBuildSystem::allLibraryTargetFiles(const QmakeProFile *file) const
{
- const QString targetPath = file->installsList().targetPath;
- if (targetPath.isEmpty())
- return;
const ToolChain *const toolchain = ToolChainKitAspect::cxxToolChain(kit());
if (!toolchain)
- return;
+ return {};
+ FilePaths libs;
TargetInformation ti = file->targetInformation();
QString targetFileName = ti.target;
const QStringList config = file->variableValue(Variable::Config);
@@ -1337,7 +1337,7 @@ void QmakeBuildSystem::collectLibraryData(const QmakeProFile *file, DeploymentDa
}
targetFileName += targetVersionExt + QLatin1Char('.');
targetFileName += QLatin1String(isStatic ? "lib" : "dll");
- deploymentData.addFile(destDirFor(ti) / targetFileName, targetPath);
+ libs << FilePath::fromString(targetFileName);
break;
}
case Abi::DarwinOS: {
@@ -1357,10 +1357,10 @@ void QmakeBuildSystem::collectLibraryData(const QmakeProFile *file, DeploymentDa
targetFileName += majorVersion;
}
targetFileName += QLatin1Char('.');
- targetFileName += file->singleVariableValue(isStatic
- ? Variable::StaticLibExtension : Variable::ShLibExtension);
+ targetFileName += file->singleVariableValue(isStatic ? Variable::StaticLibExtension
+ : Variable::ShLibExtension);
}
- deploymentData.addFile(destDir / targetFileName, targetPath);
+ libs << destDir / targetFileName;
break;
}
case Abi::LinuxOS:
@@ -1372,10 +1372,10 @@ void QmakeBuildSystem::collectLibraryData(const QmakeProFile *file, DeploymentDa
targetFileName += QLatin1Char('.');
if (isStatic) {
- targetFileName += QLatin1Char('a');
+ libs << destDirFor(ti) / (targetFileName + QLatin1Char('a'));
} else {
targetFileName += QLatin1String("so");
- deploymentData.addFile(destDirFor(ti) / targetFileName, targetPath);
+ libs << destDirFor(ti) / targetFileName;
if (nameIsVersioned) {
QString version = file->singleVariableValue(Variable::Version);
if (version.isEmpty())
@@ -1386,9 +1386,7 @@ void QmakeBuildSystem::collectLibraryData(const QmakeProFile *file, DeploymentDa
targetFileName += QLatin1Char('.');
while (!versionComponents.isEmpty()) {
const QString versionString = versionComponents.join(QLatin1Char('.'));
- deploymentData.addFile(destDirFor(ti).pathAppended(targetFileName
- + versionString),
- targetPath);
+ libs << destDirFor(ti).pathAppended(targetFileName + versionString);
versionComponents.removeLast();
}
}
@@ -1397,6 +1395,18 @@ void QmakeBuildSystem::collectLibraryData(const QmakeProFile *file, DeploymentDa
default:
break;
}
+
+ return libs;
+}
+
+void QmakeBuildSystem::collectLibraryData(const QmakeProFile *file, DeploymentData &deploymentData)
+{
+ const QString targetPath = file->installsList().targetPath;
+ if (!targetPath.isEmpty()) {
+ const FilePaths libs = allLibraryTargetFiles(file);
+ for (const FilePath &lib : libs)
+ deploymentData.addFile(lib, targetPath);
+ }
}
static FilePath getFullPathOf(const QmakeProFile *pro, Variable variable,
diff --git a/src/plugins/qmakeprojectmanager/qmakeproject.h b/src/plugins/qmakeprojectmanager/qmakeproject.h
index 6615baaaca5..3097ae7b579 100644
--- a/src/plugins/qmakeprojectmanager/qmakeproject.h
+++ b/src/plugins/qmakeprojectmanager/qmakeproject.h
@@ -106,6 +106,7 @@ public:
void collectData(const QmakeProFile *file, ProjectExplorer::DeploymentData &deploymentData);
void collectApplicationData(const QmakeProFile *file,
ProjectExplorer::DeploymentData &deploymentData);
+ Utils::FilePaths allLibraryTargetFiles(const QmakeProFile *file) const;
void collectLibraryData(const QmakeProFile *file,
ProjectExplorer::DeploymentData &deploymentData);
void startAsyncTimer(QmakeProFile::AsyncUpdateDelay delay);
diff --git a/src/plugins/qmakeprojectmanager/qmakeprojectmanagerplugin.cpp b/src/plugins/qmakeprojectmanager/qmakeprojectmanagerplugin.cpp
index e557615507d..67d450b56f4 100644
--- a/src/plugins/qmakeprojectmanager/qmakeprojectmanagerplugin.cpp
+++ b/src/plugins/qmakeprojectmanager/qmakeprojectmanagerplugin.cpp
@@ -29,7 +29,7 @@
#include <projectexplorer/projectnodes.h>
#include <projectexplorer/projectmanager.h>
#include <projectexplorer/projecttree.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectexplorerconstants.h>
@@ -244,7 +244,7 @@ void QmakeProjectManagerPlugin::initialize()
connect(BuildManager::instance(), &BuildManager::buildStateChanged,
d, &QmakeProjectManagerPluginPrivate::buildStateChanged);
- connect(SessionManager::instance(), &SessionManager::startupProjectChanged,
+ connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged,
d, &QmakeProjectManagerPluginPrivate::projectChanged);
connect(ProjectTree::instance(), &ProjectTree::currentProjectChanged,
d, &QmakeProjectManagerPluginPrivate::projectChanged);
@@ -294,7 +294,7 @@ void QmakeProjectManagerPluginPrivate::projectChanged()
if (ProjectTree::currentProject())
m_previousStartupProject = qobject_cast<QmakeProject *>(ProjectTree::currentProject());
else
- m_previousStartupProject = qobject_cast<QmakeProject *>(SessionManager::startupProject());
+ m_previousStartupProject = qobject_cast<QmakeProject *>(ProjectManager::startupProject());
if (m_previousStartupProject) {
connect(m_previousStartupProject, &Project::activeTargetChanged,
@@ -366,7 +366,7 @@ void QmakeProjectManagerPluginPrivate::addLibraryImpl(const FilePath &filePath,
void QmakeProjectManagerPluginPrivate::runQMake()
{
- runQMakeImpl(SessionManager::startupProject(), nullptr);
+ runQMakeImpl(ProjectManager::startupProject(), nullptr);
}
void QmakeProjectManagerPluginPrivate::runQMakeContextMenu()
@@ -411,7 +411,7 @@ void QmakeProjectManagerPluginPrivate::buildFile()
FileNode *node = n ? n->asFileNode() : nullptr;
if (!node)
return;
- Project *project = SessionManager::projectForFile(file);
+ Project *project = ProjectManager::projectForFile(file);
if (!project)
return;
Target *target = project->activeTarget();
@@ -563,7 +563,7 @@ void QmakeProjectManagerPluginPrivate::enableBuildFileMenus(const FilePath &file
bool enabled = false;
if (Node *node = ProjectTree::nodeForFile(file)) {
- if (Project *project = SessionManager::projectForFile(file)) {
+ if (Project *project = ProjectManager::projectForFile(file)) {
if (const FileNode *fileNode = node->asFileNode()) {
const FileType type = fileNode->fileType();
visible = qobject_cast<QmakeProject *>(project)
diff --git a/src/plugins/qmakeprojectmanager/qmakestep.cpp b/src/plugins/qmakeprojectmanager/qmakestep.cpp
index c726156e942..344acf25ba0 100644
--- a/src/plugins/qmakeprojectmanager/qmakestep.cpp
+++ b/src/plugins/qmakeprojectmanager/qmakestep.cpp
@@ -744,7 +744,7 @@ QMakeStepFactory::QMakeStepFactory()
setSupportedStepList(ProjectExplorer::Constants::BUILDSTEPS_BUILD);
//: QMakeStep default display name
setDisplayName(::QmakeProjectManager::Tr::tr("qmake")); // Fully qualifying for lupdate
- setFlags(BuildStepInfo::UniqueStep);
+ setFlags(BuildStep::UniqueStep);
}
QMakeStepConfig::TargetArchConfig QMakeStepConfig::targetArchFor(const Abi &, const QtVersion *)
diff --git a/src/plugins/qmakeprojectmanager/wizards/qtprojectparameters.cpp b/src/plugins/qmakeprojectmanager/wizards/qtprojectparameters.cpp
index e2db56e8246..1612e36753b 100644
--- a/src/plugins/qmakeprojectmanager/wizards/qtprojectparameters.cpp
+++ b/src/plugins/qmakeprojectmanager/wizards/qtprojectparameters.cpp
@@ -65,8 +65,10 @@ void QtProjectParameters::writeProFile(QTextStream &str) const
switch (type) {
case ConsoleApp:
// Mac: Command line apps should not be bundles
- str << "CONFIG += console\nCONFIG -= app_bundle\n\n";
- // fallthrough
+ str << "CONFIG += console\n"
+ "CONFIG -= app_bundle\n\n"
+ "TEMPLATE = app\n";
+ break;
case GuiApp:
str << "TEMPLATE = app\n";
break;
diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt
index 03db5b27ca5..d8c4ff8a714 100644
--- a/src/plugins/qmldesigner/CMakeLists.txt
+++ b/src/plugins/qmldesigner/CMakeLists.txt
@@ -83,7 +83,6 @@ extend_qtc_library(QmlDesignerCore
set(UI_FILES
${CMAKE_CURRENT_LIST_DIR}/designercore/instances/puppetbuildprogressdialog.ui
- ${CMAKE_CURRENT_LIST_DIR}/designercore/instances/puppetdialog.ui
)
qt_wrap_ui(UI_SOURCES ${UI_FILES})
@@ -284,8 +283,6 @@ extend_qtc_library(QmlDesignerCore
puppetstartdata.h
puppetstarter.cpp
puppetstarter.h
- puppetdialog.cpp
- puppetdialog.h
qprocessuniqueptr.h
)
diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp b/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp
index f3460cbca81..e9b5ab7082b 100644
--- a/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp
+++ b/src/plugins/qmldesigner/assetexporterplugin/assetexportdialog.cpp
@@ -11,7 +11,7 @@
#include <projectexplorer/task.h>
#include <projectexplorer/taskhub.h>
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <utils/fileutils.h>
#include <utils/outputformatter.h>
@@ -63,7 +63,7 @@ AssetExportDialog::AssetExportDialog(const Utils::FilePath &exportPath,
m_ui->exportPath->setExpectedKind(Utils::PathChooser::Kind::SaveFile);
m_ui->exportPath->setFilePath(
exportPath.pathAppended(
- ProjectExplorer::SessionManager::startupProject()->displayName() + ".metadata"
+ ProjectExplorer::ProjectManager::startupProject()->displayName() + ".metadata"
));
m_ui->exportPath->setPromptDialogTitle(tr("Choose Export File"));
m_ui->exportPath->setPromptDialogFilter(tr("Metadata file (*.metadata)"));
diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp b/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp
index d9167049a56..5cfb1414ed0 100644
--- a/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp
+++ b/src/plugins/qmldesigner/assetexporterplugin/assetexporter.cpp
@@ -10,11 +10,12 @@
#include "rewriterview.h"
#include "qmlitemnode.h"
#include "qmlobjectnode.h"
-#include "coreplugin/editormanager/editormanager.h"
-#include "utils/qtcassert.h"
-#include "utils/runextensions.h"
-#include "projectexplorer/session.h"
-#include "projectexplorer/project.h"
+
+#include <coreplugin/editormanager/editormanager.h>
+#include <projectexplorer/project.h>
+#include <projectexplorer/projectmanager.h>
+#include <utils/asynctask.h>
+#include <utils/qtcassert.h>
#include <auxiliarydataproperties.h>
@@ -69,7 +70,7 @@ public:
private:
void addAsset(const QPixmap &p, const Utils::FilePath &path);
- void doDumping(QFutureInterface<void> &fi);
+ void doDumping(QPromise<void> &promise);
void savePixmap(const QPixmap &p, Utils::FilePath &path) const;
QFuture<void> m_dumpFuture;
@@ -406,7 +407,7 @@ void AssetExporter::writeMetadata() const
m_currentState.change(ParsingState::WritingJson);
- auto const startupProject = ProjectExplorer::SessionManager::startupProject();
+ auto const startupProject = ProjectExplorer::ProjectManager::startupProject();
QTC_ASSERT(startupProject, return);
const QString projectName = startupProject->displayName();
@@ -452,7 +453,7 @@ QDebug operator<<(QDebug os, const AssetExporter::ParsingState &s)
AssetDumper::AssetDumper():
m_quitDumper(false)
{
- m_dumpFuture = Utils::runAsync(&AssetDumper::doDumping, this);
+ m_dumpFuture = Utils::asyncRun(&AssetDumper::doDumping, this);
}
AssetDumper::~AssetDumper()
@@ -489,7 +490,7 @@ void AssetDumper::addAsset(const QPixmap &p, const Utils::FilePath &path)
m_assets.push({p, path});
}
-void AssetDumper::doDumping(QFutureInterface<void> &fi)
+void AssetDumper::doDumping(QPromise<void> &promise)
{
auto haveAsset = [this] (std::pair<QPixmap, Utils::FilePath> *asset) {
QMutexLocker locker(&m_queueMutex);
@@ -503,7 +504,7 @@ void AssetDumper::doDumping(QFutureInterface<void> &fi)
forever {
std::pair<QPixmap, Utils::FilePath> asset;
if (haveAsset(&asset)) {
- if (fi.isCanceled())
+ if (promise.isCanceled())
break;
savePixmap(asset.first, asset.second);
} else {
@@ -513,10 +514,9 @@ void AssetDumper::doDumping(QFutureInterface<void> &fi)
m_queueCondition.wait(&m_queueMutex);
}
- if (fi.isCanceled())
+ if (promise.isCanceled())
break;
}
- fi.reportFinished();
}
void AssetDumper::savePixmap(const QPixmap &p, Utils::FilePath &path) const
diff --git a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.cpp b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.cpp
index 2d6f6c7d80f..3ab1d1a6869 100644
--- a/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.cpp
+++ b/src/plugins/qmldesigner/assetexporterplugin/assetexporterplugin.cpp
@@ -19,9 +19,9 @@
#include "coreplugin/documentmanager.h"
#include "qmldesigner/qmldesignerplugin.h"
#include "projectexplorer/projectexplorerconstants.h"
-#include "projectexplorer/session.h"
+#include "projectexplorer/projectmanager.h"
#include "projectexplorer/project.h"
-#include "projectexplorer/session.h"
+#include "projectexplorer/projectmanager.h"
#include "projectexplorer/taskhub.h"
#include "extensionsystem/pluginmanager.h"
@@ -54,8 +54,8 @@ AssetExporterPlugin::AssetExporterPlugin()
// Instantiate actions created by the plugin.
addActions();
- connect(ProjectExplorer::SessionManager::instance(),
- &ProjectExplorer::SessionManager::startupProjectChanged,
+ connect(ProjectExplorer::ProjectManager::instance(),
+ &ProjectExplorer::ProjectManager::startupProjectChanged,
this, &AssetExporterPlugin::updateActions);
updateActions();
@@ -68,7 +68,7 @@ QString AssetExporterPlugin::pluginName() const
void AssetExporterPlugin::onExport()
{
- auto startupProject = ProjectExplorer::SessionManager::startupProject();
+ auto startupProject = ProjectExplorer::ProjectManager::startupProject();
if (!startupProject)
return;
@@ -97,7 +97,7 @@ void AssetExporterPlugin::addActions()
void AssetExporterPlugin::updateActions()
{
- auto project = ProjectExplorer::SessionManager::startupProject();
+ auto project = ProjectExplorer::ProjectManager::startupProject();
QAction* const exportAction = Core::ActionManager::command(Constants::EXPORT_QML)->action();
exportAction->setEnabled(project && !project->needsConfiguration());
}
diff --git a/src/plugins/qmldesigner/assetexporterplugin/filepathmodel.cpp b/src/plugins/qmldesigner/assetexporterplugin/filepathmodel.cpp
index b50bc3cffba..dd92973eba9 100644
--- a/src/plugins/qmldesigner/assetexporterplugin/filepathmodel.cpp
+++ b/src/plugins/qmldesigner/assetexporterplugin/filepathmodel.cpp
@@ -4,9 +4,10 @@
#include "exportnotification.h"
-#include "projectexplorer/project.h"
-#include "projectexplorer/projectnodes.h"
-#include "utils/runextensions.h"
+#include <projectexplorer/project.h>
+#include <projectexplorer/projectnodes.h>
+
+#include <utils/asynctask.h>
#include <QLoggingCategory>
#include <QTimer>
@@ -17,19 +18,19 @@ namespace {
Q_LOGGING_CATEGORY(loggerError, "qtc.designer.assetExportPlugin.filePathModel", QtCriticalMsg)
Q_LOGGING_CATEGORY(loggerInfo, "qtc.designer.assetExportPlugin.filePathModel", QtInfoMsg)
-void findQmlFiles(QFutureInterface<Utils::FilePath> &f, const Project *project)
+void findQmlFiles(QPromise<Utils::FilePath> &promise, const Project *project)
{
- if (!project || f.isCanceled())
+ if (!project || promise.isCanceled())
return;
int index = 0;
- project->files([&f, &index](const Node* node) ->bool {
- if (f.isCanceled())
+ project->files([&promise, &index](const Node* node) ->bool {
+ if (promise.isCanceled())
return false;
Utils::FilePath path = node->filePath();
bool isComponent = !path.fileName().isEmpty() && path.fileName().front().isUpper();
if (isComponent && node->filePath().endsWith(".ui.qml"))
- f.reportResult(path, index++);
+ promise.addResult(path, index++);
return true;
});
}
@@ -132,7 +133,7 @@ void FilePathModel::processProject()
connect(m_preprocessWatcher.get(), &QFutureWatcher<Utils::FilePath>::finished,
this, &FilePathModel::endResetModel);
- QFuture<Utils::FilePath> f = Utils::runAsync(&findQmlFiles, m_project);
+ QFuture<Utils::FilePath> f = Utils::asyncRun(&findQmlFiles, m_project);
m_preprocessWatcher->setFuture(f);
}
diff --git a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.cpp b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.cpp
index 9792a6615a3..f9c44229899 100644
--- a/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.cpp
+++ b/src/plugins/qmldesigner/components/assetslibrary/assetslibraryview.cpp
@@ -19,7 +19,7 @@
#include <nodelistproperty.h>
#include <projectexplorer/kit.h>
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <rewriterview.h>
#include <sqlitedatabase.h>
diff --git a/src/plugins/qmldesigner/components/componentcore/changestyleaction.cpp b/src/plugins/qmldesigner/components/componentcore/changestyleaction.cpp
index 56ab654912e..1c68fdf0733 100644
--- a/src/plugins/qmldesigner/components/componentcore/changestyleaction.cpp
+++ b/src/plugins/qmldesigner/components/componentcore/changestyleaction.cpp
@@ -5,7 +5,7 @@
#include "designermcumanager.h"
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <QComboBox>
#include <QSettings>
@@ -14,7 +14,7 @@ namespace QmlDesigner {
static QString styleConfigFileName(const QString &qmlFileName)
{
- ProjectExplorer::Project *currentProject = ProjectExplorer::SessionManager::projectForFile(Utils::FilePath::fromString(qmlFileName));
+ ProjectExplorer::Project *currentProject = ProjectExplorer::ProjectManager::projectForFile(Utils::FilePath::fromString(qmlFileName));
if (currentProject) {
const QList<Utils::FilePath> fileNames = currentProject->files(
diff --git a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp
index f0969bfabb0..ca1e288815e 100644
--- a/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp
+++ b/src/plugins/qmldesigner/components/contentlibrary/contentlibraryview.cpp
@@ -20,7 +20,7 @@
#ifndef QMLDESIGNER_TEST
#include <projectexplorer/kit.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <qtsupport/baseqtversion.h>
#include <qtsupport/qtkitinformation.h>
@@ -367,7 +367,7 @@ void ContentLibraryView::updateBundleMaterialsQuick3DVersion()
#ifndef QMLDESIGNER_TEST
if (hasImport && major == -1) {
// Import without specifying version, so we take the kit version
- auto target = ProjectExplorer::SessionManager::startupTarget();
+ auto target = ProjectExplorer::ProjectManager::startupTarget();
if (target) {
QtSupport::QtVersion *qtVersion = QtSupport::QtKitAspect::qtVersion(target->kit());
if (qtVersion) {
diff --git a/src/plugins/qmldesigner/components/eventlist/eventlist.cpp b/src/plugins/qmldesigner/components/eventlist/eventlist.cpp
index 23e60dbda28..a0d7cc89815 100644
--- a/src/plugins/qmldesigner/components/eventlist/eventlist.cpp
+++ b/src/plugins/qmldesigner/components/eventlist/eventlist.cpp
@@ -8,7 +8,7 @@
#include "bindingproperty.h"
#include "metainfo.h"
#include "projectexplorer/project.h"
-#include "projectexplorer/session.h"
+#include "projectexplorer/projectmanager.h"
#include "qmldesignerplugin.h"
#include "signalhandlerproperty.h"
#include "utils/fileutils.h"
@@ -23,7 +23,7 @@ namespace QmlDesigner {
Utils::FilePath projectFilePath()
{
if (auto *doc = QmlDesignerPlugin::instance()->documentManager().currentDesignDocument()) {
- if (auto *proj = ProjectExplorer::SessionManager::projectForFile(doc->fileName()))
+ if (auto *proj = ProjectExplorer::ProjectManager::projectForFile(doc->fileName()))
return proj->projectDirectory();
}
return Utils::FilePath();
diff --git a/src/plugins/qmldesigner/components/integration/designdocument.cpp b/src/plugins/qmldesigner/components/integration/designdocument.cpp
index efeea8aa36f..9c73789c91c 100644
--- a/src/plugins/qmldesigner/components/integration/designdocument.cpp
+++ b/src/plugins/qmldesigner/components/integration/designdocument.cpp
@@ -20,7 +20,7 @@
#include <projectexplorer/projecttree.h>
#include <projectexplorer/project.h>
#include <projectexplorer/target.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/kit.h>
#include <qtsupport/qtkitinformation.h>
#include <qtsupport/qtsupportconstants.h>
@@ -403,7 +403,7 @@ bool DesignDocument::isQtForMCUsProject() const
Utils::FilePath DesignDocument::projectFolder() const
{
- ProjectExplorer::Project *currentProject = ProjectExplorer::SessionManager::projectForFile(fileName());
+ ProjectExplorer::Project *currentProject = ProjectExplorer::ProjectManager::projectForFile(fileName());
if (currentProject)
return currentProject->projectDirectory();
@@ -434,7 +434,7 @@ void DesignDocument::changeToInFileComponentModel(ComponentTextModifier *textMod
void DesignDocument::updateQrcFiles()
{
- ProjectExplorer::Project *currentProject = ProjectExplorer::SessionManager::projectForFile(fileName());
+ ProjectExplorer::Project *currentProject = ProjectExplorer::ProjectManager::projectForFile(fileName());
if (currentProject) {
const auto srcFiles = currentProject->files(ProjectExplorer::Project::SourceFiles);
@@ -716,7 +716,7 @@ void DesignDocument::redo()
static Target *getActiveTarget(DesignDocument *designDocument)
{
- Project *currentProject = SessionManager::projectForFile(designDocument->fileName());
+ Project *currentProject = ProjectManager::projectForFile(designDocument->fileName());
if (!currentProject)
currentProject = ProjectTree::currentProject();
diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp
index 648ffdb56cc..df9f2737972 100644
--- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp
+++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimportdialog.cpp
@@ -1,5 +1,6 @@
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
#include "itemlibraryassetimportdialog.h"
#include "ui_itemlibraryassetimportdialog.h"
@@ -13,7 +14,8 @@
#include "theme.h"
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
+
#include <coreplugin/icore.h>
#include <QFileInfo>
@@ -322,7 +324,7 @@ void ItemLibraryAssetImportDialog::updateImport(const ModelNode &updateNode,
// Unable to find original scene source, launch file dialog to locate it
QString initialPath;
ProjectExplorer::Project *currentProject
- = ProjectExplorer::SessionManager::projectForFile(
+ = ProjectExplorer::ProjectManager::projectForFile(
Utils::FilePath::fromString(compFileName));
if (currentProject)
initialPath = currentProject->projectDirectory().toString();
diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp
index d9b82419433..cca5ed47b31 100644
--- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp
+++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp
@@ -17,7 +17,7 @@
#include <qmljs/qmljsmodelmanagerinterface.h>
#include <utils/algorithm.h>
-#include <utils/runextensions.h>
+#include <utils/asynctask.h>
#include <utils/qtcassert.h>
#include <QApplication>
@@ -684,7 +684,7 @@ void ItemLibraryAssetImporter::finalizeQuick3DImport()
if (modelManager) {
QmlJS::PathsAndLanguages pathToScan;
pathToScan.maybeInsert(Utils::FilePath::fromString(m_importPath));
- result = Utils::runAsync(&QmlJS::ModelManagerInterface::importScan,
+ result = Utils::asyncRun(&QmlJS::ModelManagerInterface::importScan,
modelManager->workingCopy(), pathToScan,
modelManager, true, true, true);
}
diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp
index 556d0c978fe..96b95f7949a 100644
--- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp
+++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrarymodel.cpp
@@ -13,7 +13,7 @@
#include <nodehints.h>
#include <nodemetainfo.h>
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include "qmldesignerconstants.h"
#include "qmldesignerplugin.h"
#include <utils/algorithm.h>
@@ -336,7 +336,7 @@ void ItemLibraryModel::update(ItemLibraryInfo *itemLibraryInfo, Model *model)
DesignDocument *document = QmlDesignerPlugin::instance()->currentDesignDocument();
Utils::FilePath qmlFileName = document->fileName();
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::projectForFile(qmlFileName);
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::projectForFile(qmlFileName);
QString projectName = project ? project->displayName() : "";
QString materialBundlePrefix = QLatin1String(Constants::COMPONENT_BUNDLES_FOLDER).mid(1);
diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp
index 95b6476638d..aacbd9b1b53 100644
--- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp
+++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryview.cpp
@@ -14,7 +14,7 @@
#include <nodelistproperty.h>
#include <projectexplorer/kit.h>
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <rewriterview.h>
#include <sqlitedatabase.h>
diff --git a/src/plugins/qmldesigner/components/navigator/navigatorview.cpp b/src/plugins/qmldesigner/components/navigator/navigatorview.cpp
index e6450d45627..2d4cd76cc4b 100644
--- a/src/plugins/qmldesigner/components/navigator/navigatorview.cpp
+++ b/src/plugins/qmldesigner/components/navigator/navigatorview.cpp
@@ -29,7 +29,7 @@
#include <coreplugin/icore.h>
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/projectnodes.h>
#include <utils/algorithm.h>
@@ -454,14 +454,14 @@ const ProjectExplorer::FileNode *NavigatorView::fileNodeForModelNode(const Model
{
QString filename = node.metaInfo().componentFileName();
Utils::FilePath filePath = Utils::FilePath::fromString(filename);
- ProjectExplorer::Project *currentProject = ProjectExplorer::SessionManager::projectForFile(
+ ProjectExplorer::Project *currentProject = ProjectExplorer::ProjectManager::projectForFile(
filePath);
if (!currentProject) {
filePath = Utils::FilePath::fromString(node.model()->fileUrl().toLocalFile());
/* If the component does not belong to the project then we can fallback to the current file */
- currentProject = ProjectExplorer::SessionManager::projectForFile(filePath);
+ currentProject = ProjectExplorer::ProjectManager::projectForFile(filePath);
}
if (!currentProject)
return nullptr;
diff --git a/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.cpp b/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.cpp
index 7164586fea4..02ffd84c93d 100644
--- a/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.cpp
+++ b/src/plugins/qmldesigner/components/propertyeditor/fileresourcesmodel.cpp
@@ -15,7 +15,7 @@
#include <qmlmodelnodeproxy.h>
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
static QString s_lastBrowserPath;
@@ -23,7 +23,7 @@ FileResourcesModel::FileResourcesModel(QObject *parent)
: QObject(parent)
, m_filter(QLatin1String("(*.*)"))
{
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::projectForFile(
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::projectForFile(
QmlDesigner::DocumentManager::currentFilePath());
if (project) {
diff --git a/src/plugins/qmldesigner/designercore/instances/puppetdialog.cpp b/src/plugins/qmldesigner/designercore/instances/puppetdialog.cpp
deleted file mode 100644
index 935b5021680..00000000000
--- a/src/plugins/qmldesigner/designercore/instances/puppetdialog.cpp
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#include "puppetdialog.h"
-#include "ui_puppetdialog.h"
-
-namespace QmlDesigner {
-
-PuppetDialog::PuppetDialog(QWidget *parent) :
- QDialog(parent),
- ui(new Ui::PuppetDialog)
-{
- ui->setupUi(this);
-}
-
-PuppetDialog::~PuppetDialog()
-{
- delete ui;
-}
-
-void PuppetDialog::setDescription(const QString &description)
-{
- ui->descriptionLabel->setText(description);
-}
-
-void PuppetDialog::setCopyAndPasteCode(const QString &text)
-{
- ui->copyAndPasteTextEdit->setText(text);
-}
-
-void PuppetDialog::warning(QWidget *parent, const QString &title, const QString &description, const QString &copyAndPasteCode)
-{
- PuppetDialog dialog(parent);
-
- dialog.setWindowTitle(title);
- dialog.setDescription(description);
- dialog.setCopyAndPasteCode(copyAndPasteCode);
-
- dialog.exec();
-}
-
-} //QmlDesigner
diff --git a/src/plugins/qmldesigner/designercore/instances/puppetdialog.h b/src/plugins/qmldesigner/designercore/instances/puppetdialog.h
deleted file mode 100644
index 32d0b8deb42..00000000000
--- a/src/plugins/qmldesigner/designercore/instances/puppetdialog.h
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#pragma once
-
-#include <QDialog>
-
-namespace QmlDesigner {
-
-namespace Ui {
-class PuppetDialog;
-}
-
-
-class PuppetDialog : public QDialog
-{
- Q_OBJECT
-
-public:
- explicit PuppetDialog(QWidget *parent = nullptr);
- ~PuppetDialog() override;
-
- void setDescription(const QString &description);
- void setCopyAndPasteCode(const QString &text);
-
- static void warning(QWidget *parent,
- const QString &title,
- const QString &description,
- const QString &copyAndPasteCode);
-
-private:
- Ui::PuppetDialog *ui;
-};
-
-} //QmlDesigner
diff --git a/src/plugins/qmldesigner/designercore/instances/puppetdialog.ui b/src/plugins/qmldesigner/designercore/instances/puppetdialog.ui
deleted file mode 100644
index b7efa8c26c3..00000000000
--- a/src/plugins/qmldesigner/designercore/instances/puppetdialog.ui
+++ /dev/null
@@ -1,96 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>QmlDesigner::PuppetDialog</class>
- <widget class="QDialog" name="QmlDesigner::PuppetDialog">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>1148</width>
- <height>344</height>
- </rect>
- </property>
- <property name="windowTitle">
- <string>Dialog</string>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <property name="spacing">
- <number>12</number>
- </property>
- <item>
- <widget class="QLabel" name="descriptionLabel">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>1</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string/>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QTextEdit" name="copyAndPasteTextEdit">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>1</verstretch>
- </sizepolicy>
- </property>
- <property name="readOnly">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QDialogButtonBox" name="buttonBox">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="standardButtons">
- <set>QDialogButtonBox::Close</set>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- <resources/>
- <connections>
- <connection>
- <sender>buttonBox</sender>
- <signal>accepted()</signal>
- <receiver>QmlDesigner::PuppetDialog</receiver>
- <slot>accept()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>248</x>
- <y>254</y>
- </hint>
- <hint type="destinationlabel">
- <x>157</x>
- <y>274</y>
- </hint>
- </hints>
- </connection>
- <connection>
- <sender>buttonBox</sender>
- <signal>rejected()</signal>
- <receiver>QmlDesigner::PuppetDialog</receiver>
- <slot>reject()</slot>
- <hints>
- <hint type="sourcelabel">
- <x>316</x>
- <y>260</y>
- </hint>
- <hint type="destinationlabel">
- <x>286</x>
- <y>274</y>
- </hint>
- </hints>
- </connection>
- </connections>
-</ui>
diff --git a/src/plugins/qmldesigner/documentmanager.cpp b/src/plugins/qmldesigner/documentmanager.cpp
index d2bf9eac514..9fdf4e41198 100644
--- a/src/plugins/qmldesigner/documentmanager.cpp
+++ b/src/plugins/qmldesigner/documentmanager.cpp
@@ -21,10 +21,11 @@
#include <coreplugin/vcsmanager.h>
#include <coreplugin/icore.h>
#include <coreplugin/messagebox.h>
-#include <projectexplorer/projectnodes.h>
+
#include <projectexplorer/project.h>
+#include <projectexplorer/projectmanager.h>
+#include <projectexplorer/projectnodes.h>
#include <projectexplorer/projecttree.h>
-#include <projectexplorer/session.h>
#include <qmakeprojectmanager/qmakenodes.h>
#include <qmakeprojectmanager/qmakeproject.h>
@@ -335,11 +336,11 @@ Utils::FilePath DocumentManager::currentProjectDirPath()
Utils::FilePath qmlFileName = QmlDesignerPlugin::instance()->currentDesignDocument()->fileName();
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::projectForFile(qmlFileName);
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::projectForFile(qmlFileName);
if (project)
return project->projectDirectory();
- const QList projects = ProjectExplorer::SessionManager::projects();
+ const QList projects = ProjectExplorer::ProjectManager::projects();
for (auto p : projects) {
if (qmlFileName.startsWith(p->projectDirectory().toString()))
return p->projectDirectory();
@@ -402,7 +403,7 @@ void DocumentManager::findPathToIsoProFile(bool *iconResourceFileAlreadyExists,
QString *resourceFileProPath, const QString &isoIconsQrcFile)
{
Utils::FilePath qmlFileName = QmlDesignerPlugin::instance()->currentDesignDocument()->fileName();
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::projectForFile(qmlFileName);
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::projectForFile(qmlFileName);
ProjectExplorer::Node *node = ProjectExplorer::ProjectTree::nodeForFile(qmlFileName)->parentFolderNode();
ProjectExplorer::Node *iconQrcFileNode = nullptr;
@@ -492,7 +493,7 @@ bool DocumentManager::belongsToQmakeProject()
return false;
Utils::FilePath qmlFileName = QmlDesignerPlugin::instance()->currentDesignDocument()->fileName();
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::projectForFile(qmlFileName);
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::projectForFile(qmlFileName);
if (!project)
return false;
diff --git a/src/plugins/qmldesigner/generateresource.cpp b/src/plugins/qmldesigner/generateresource.cpp
index 1901a8e2ea1..8b00335ce33 100644
--- a/src/plugins/qmldesigner/generateresource.cpp
+++ b/src/plugins/qmldesigner/generateresource.cpp
@@ -12,8 +12,8 @@
#include <coreplugin/messagemanager.h>
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
#include <projectexplorer/projectexplorerconstants.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <qmlprojectmanager/qmlprojectmanagerconstants.h>
@@ -223,16 +223,16 @@ void GenerateResource::generateMenuEntry(QObject *parent)
auto action = new QAction(QCoreApplication::translate("QmlDesigner::GenerateResource",
"Generate QRC Resource File..."),
parent);
- action->setEnabled(ProjectExplorer::SessionManager::startupProject() != nullptr);
+ action->setEnabled(ProjectExplorer::ProjectManager::startupProject() != nullptr);
// todo make it more intelligent when it gets enabled
- QObject::connect(ProjectExplorer::SessionManager::instance(),
- &ProjectExplorer::SessionManager::startupProjectChanged, [action]() {
- action->setEnabled(ProjectExplorer::SessionManager::startupProject());
+ QObject::connect(ProjectExplorer::ProjectManager::instance(),
+ &ProjectExplorer::ProjectManager::startupProjectChanged, [action]() {
+ action->setEnabled(ProjectExplorer::ProjectManager::startupProject());
});
Core::Command *cmd = Core::ActionManager::registerAction(action, "QmlProject.CreateResource");
QObject::connect(action, &QAction::triggered, [] () {
- auto currentProject = ProjectExplorer::SessionManager::startupProject();
+ auto currentProject = ProjectExplorer::ProjectManager::startupProject();
QTC_ASSERT(currentProject, return);
const FilePath projectPath = currentProject->projectFilePath().parentDir();
@@ -331,16 +331,16 @@ void GenerateResource::generateMenuEntry(QObject *parent)
auto rccAction = new QAction(QCoreApplication::translate("QmlDesigner::GenerateResource",
"Generate Deployable Package..."),
parent);
- rccAction->setEnabled(ProjectExplorer::SessionManager::startupProject() != nullptr);
- QObject::connect(ProjectExplorer::SessionManager::instance(),
- &ProjectExplorer::SessionManager::startupProjectChanged, [rccAction]() {
- rccAction->setEnabled(ProjectExplorer::SessionManager::startupProject());
+ rccAction->setEnabled(ProjectExplorer::ProjectManager::startupProject() != nullptr);
+ QObject::connect(ProjectExplorer::ProjectManager::instance(),
+ &ProjectExplorer::ProjectManager::startupProjectChanged, [rccAction]() {
+ rccAction->setEnabled(ProjectExplorer::ProjectManager::startupProject());
});
Core::Command *cmd2 = Core::ActionManager::registerAction(rccAction,
"QmlProject.CreateRCCResource");
QObject::connect(rccAction, &QAction::triggered, []() {
- auto currentProject = ProjectExplorer::SessionManager::startupProject();
+ auto currentProject = ProjectExplorer::ProjectManager::startupProject();
QTC_ASSERT(currentProject, return);
const FilePath projectPath = currentProject->projectFilePath().parentDir();
diff --git a/src/plugins/qmldesigner/qmldesignerexternaldependencies.cpp b/src/plugins/qmldesigner/qmldesignerexternaldependencies.cpp
index e332f1d49c7..47174ba3d88 100644
--- a/src/plugins/qmldesigner/qmldesignerexternaldependencies.cpp
+++ b/src/plugins/qmldesigner/qmldesignerexternaldependencies.cpp
@@ -9,7 +9,7 @@
#include <edit3d/edit3dviewconfig.h>
#include <itemlibraryimport.h>
#include <projectexplorer/kit.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <puppetenvironmentbuilder.h>
#include <qtsupport/baseqtversion.h>
@@ -109,7 +109,7 @@ QString ExternalDependencies::itemLibraryImportUserComponentsTitle() const
bool ExternalDependencies::isQt6Import() const
{
- auto target = ProjectExplorer::SessionManager::startupTarget();
+ auto target = ProjectExplorer::ProjectManager::startupTarget();
if (target) {
QtSupport::QtVersion *currentQtVersion = QtSupport::QtKitAspect::qtVersion(target->kit());
if (currentQtVersion && currentQtVersion->isValid()) {
@@ -122,7 +122,7 @@ bool ExternalDependencies::isQt6Import() const
bool ExternalDependencies::hasStartupTarget() const
{
- auto target = ProjectExplorer::SessionManager::startupTarget();
+ auto target = ProjectExplorer::ProjectManager::startupTarget();
if (target) {
QtSupport::QtVersion *currentQtVersion = QtSupport::QtKitAspect::qtVersion(target->kit());
if (currentQtVersion && currentQtVersion->isValid()) {
@@ -208,7 +208,7 @@ QString createFreeTypeOption(ProjectExplorer::Target *target)
PuppetStartData ExternalDependencies::puppetStartData(const Model &model) const
{
PuppetStartData data;
- auto target = ProjectExplorer::SessionManager::startupTarget();
+ auto target = ProjectExplorer::ProjectManager::startupTarget();
auto [workingDirectory, puppetPath] = qmlPuppetPaths(target, m_designerSettings);
data.puppetPath = puppetPath.toString();
diff --git a/src/plugins/qmldesigner/qmldesignerplugin.cpp b/src/plugins/qmldesigner/qmldesignerplugin.cpp
index 3fe639b5d8d..b8685ba700a 100644
--- a/src/plugins/qmldesigner/qmldesignerplugin.cpp
+++ b/src/plugins/qmldesigner/qmldesignerplugin.cpp
@@ -54,7 +54,7 @@
#include <extensionsystem/pluginspec.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorerconstants.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <qmljs/qmljsmodelmanagerinterface.h>
#include <sqlitelibraryinitializer.h>
@@ -347,7 +347,7 @@ ExtensionSystem::IPlugin::ShutdownFlag QmlDesignerPlugin::aboutToShutdown()
static QStringList allUiQmlFilesforCurrentProject(const Utils::FilePath &fileName)
{
QStringList list;
- ProjectExplorer::Project *currentProject = ProjectExplorer::SessionManager::projectForFile(fileName);
+ ProjectExplorer::Project *currentProject = ProjectExplorer::ProjectManager::projectForFile(fileName);
if (currentProject) {
const QList<Utils::FilePath> fileNames = currentProject->files(ProjectExplorer::Project::SourceFiles);
@@ -363,7 +363,7 @@ static QStringList allUiQmlFilesforCurrentProject(const Utils::FilePath &fileNam
static QString projectPath(const Utils::FilePath &fileName)
{
QString path;
- ProjectExplorer::Project *currentProject = ProjectExplorer::SessionManager::projectForFile(fileName);
+ ProjectExplorer::Project *currentProject = ProjectExplorer::ProjectManager::projectForFile(fileName);
if (currentProject)
path = currentProject->projectDirectory().toString();
diff --git a/src/plugins/qmldesigner/qmldesignerprojectmanager.cpp b/src/plugins/qmldesigner/qmldesignerprojectmanager.cpp
index e708e8ebf11..60363e5fbd1 100644
--- a/src/plugins/qmldesigner/qmldesignerprojectmanager.cpp
+++ b/src/plugins/qmldesigner/qmldesignerprojectmanager.cpp
@@ -6,7 +6,7 @@
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/icore.h>
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <projectstorage/filestatuscache.h>
#include <projectstorage/filesystem.h>
@@ -199,15 +199,15 @@ QmlDesignerProjectManager::QmlDesignerProjectManager(ExternalDependenciesInterfa
QObject::connect(editorManager, &::Core::EditorManager::editorsClosed, [&](const auto &editors) {
editorsClosed(editors);
});
- auto sessionManager = ::ProjectExplorer::SessionManager::instance();
+ auto sessionManager = ::ProjectExplorer::ProjectManager::instance();
QObject::connect(sessionManager,
- &::ProjectExplorer::SessionManager::projectAdded,
+ &::ProjectExplorer::ProjectManager::projectAdded,
[&](auto *project) { projectAdded(project); });
QObject::connect(sessionManager,
- &::ProjectExplorer::SessionManager::aboutToRemoveProject,
+ &::ProjectExplorer::ProjectManager::aboutToRemoveProject,
[&](auto *project) { aboutToRemoveProject(project); });
QObject::connect(sessionManager,
- &::ProjectExplorer::SessionManager::projectRemoved,
+ &::ProjectExplorer::ProjectManager::projectRemoved,
[&](auto *project) { projectRemoved(project); });
QObject::connect(&m_previewTimer,
@@ -428,7 +428,7 @@ QmlDesignerProjectManager::ImageCacheData *QmlDesignerProjectManager::imageCache
imageCacheData->nodeInstanceCollector.setTarget(target);
};
- if (auto project = ProjectExplorer::SessionManager::startupProject(); project) {
+ if (auto project = ProjectExplorer::ProjectManager::startupProject(); project) {
// TODO wrap in function in image cache data
m_imageCacheData->meshImageCollector.setTarget(project->activeTarget());
m_imageCacheData->nodeInstanceCollector.setTarget(project->activeTarget());
@@ -437,8 +437,8 @@ QmlDesignerProjectManager::ImageCacheData *QmlDesignerProjectManager::imageCache
this,
setTargetInImageCache);
}
- QObject::connect(ProjectExplorer::SessionManager::instance(),
- &ProjectExplorer::SessionManager::startupProjectChanged,
+ QObject::connect(ProjectExplorer::ProjectManager::instance(),
+ &ProjectExplorer::ProjectManager::startupProjectChanged,
this,
[=](ProjectExplorer::Project *project) {
setTargetInImageCache(activeTarget(project));
diff --git a/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp b/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp
index a90c6240125..cba3411ead5 100644
--- a/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp
+++ b/src/plugins/qmldesigner/qmlpreviewplugin/qmlpreviewactions.cpp
@@ -13,7 +13,7 @@
#include <projectexplorer/kitinformation.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectexplorerconstants.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/project.h>
#include <projectexplorer/target.h>
@@ -36,7 +36,7 @@ static void handleAction(const SelectionContext &context)
if (context.view()->isAttached()) {
if (context.toggled()) {
bool skipDeploy = false;
- if (const Target *startupTarget = SessionManager::startupTarget()) {
+ if (const Target *startupTarget = ProjectManager::startupTarget()) {
const Kit *kit = startupTarget->kit();
if (kit
&& (kit->supportedPlatforms().contains(Android::Constants::ANDROID_DEVICE_TYPE)
@@ -241,10 +241,10 @@ QWidget *SwitchLanguageComboboxAction::createWidget(QWidget *parent)
}
}
};
- connect(ProjectExplorer::SessionManager::instance(), &ProjectExplorer::SessionManager::startupProjectChanged,
+ connect(ProjectExplorer::ProjectManager::instance(), &ProjectExplorer::ProjectManager::startupProjectChanged,
comboBox, refreshComboBoxFunction);
- if (auto project = SessionManager::startupProject())
+ if (auto project = ProjectManager::startupProject())
refreshComboBoxFunction(project);
// do this after refreshComboBoxFunction so we do not get currentLocaleChanged signals at initialization
diff --git a/src/plugins/qmljseditor/qmljseditingsettingspage.cpp b/src/plugins/qmljseditor/qmljseditingsettingspage.cpp
index 2a0ecb41771..6d9e95f4389 100644
--- a/src/plugins/qmljseditor/qmljseditingsettingspage.cpp
+++ b/src/plugins/qmljseditor/qmljseditingsettingspage.cpp
@@ -287,8 +287,7 @@ public:
Utils::globalMacroExpander());
const auto updateFormatCommandState = [&, formatCommandLabel, formatCommandOptionsLabel] {
- const bool enabled = useCustomFormatCommand->isChecked()
- && autoFormatOnSave->isChecked();
+ const bool enabled = useCustomFormatCommand->isChecked();
formatCommandLabel->setEnabled(enabled);
formatCommand->setEnabled(enabled);
formatCommandOptionsLabel->setEnabled(enabled);
@@ -298,7 +297,6 @@ public:
connect(autoFormatOnSave, &QCheckBox::toggled, this, [&, updateFormatCommandState]() {
autoFormatOnlyCurrentProject->setEnabled(autoFormatOnSave->isChecked());
- useCustomFormatCommand->setEnabled(autoFormatOnSave->isChecked());
updateFormatCommandState();
});
connect(useCustomFormatCommand, &QCheckBox::toggled, this, updateFormatCommandState);
diff --git a/src/plugins/qmljseditor/qmljsfindreferences.cpp b/src/plugins/qmljseditor/qmljsfindreferences.cpp
index b37d0e8758d..c8ae1f8b8b0 100644
--- a/src/plugins/qmljseditor/qmljsfindreferences.cpp
+++ b/src/plugins/qmljseditor/qmljsfindreferences.cpp
@@ -12,8 +12,8 @@
#include <extensionsystem/pluginmanager.h>
#include <texteditor/basefilefind.h>
#include <utils/algorithm.h>
+#include <utils/asynctask.h>
#include <utils/filesearch.h>
-#include <utils/runextensions.h>
#include <qmljs/qmljsmodelmanagerinterface.h>
#include <qmljs/qmljsbind.h>
@@ -26,19 +26,10 @@
#include <qmljs/parser/qmljsast_p.h>
#include <qmljstools/qmljsmodelmanager.h>
-#include "qmljseditorconstants.h"
-
-#include <QApplication>
#include <QDebug>
-#include <QDir>
#include <QFuture>
-#include <QLabel>
-#include <QTime>
-#include <QTimer>
#include <QtConcurrentMap>
-#include <functional>
-
using namespace Core;
using namespace QmlJS;
using namespace QmlJS::AST;
@@ -704,7 +695,7 @@ class ProcessFile
using Usage = FindReferences::Usage;
const QString name;
const ObjectValue *scope;
- QFutureInterface<Usage> *future;
+ QPromise<Usage> &m_promise;
public:
// needed by QtConcurrent
@@ -714,16 +705,15 @@ public:
ProcessFile(const ContextPtr &context,
const QString &name,
const ObjectValue *scope,
- QFutureInterface<Usage> *future)
- : context(context), name(name), scope(scope), future(future)
+ QPromise<Usage> &promise)
+ : context(context), name(name), scope(scope), m_promise(promise)
{ }
QList<Usage> operator()(const Utils::FilePath &fileName)
{
QList<Usage> usages;
- if (future->isPaused())
- future->waitForResume();
- if (future->isCanceled())
+ m_promise.suspendIfRequested();
+ if (m_promise.isCanceled())
return usages;
ModelManagerInterface *modelManager = ModelManagerInterface::instance();
Document::Ptr doc = context->snapshot().document(fileName);
@@ -739,8 +729,7 @@ public:
loc.startLine,
loc.startColumn - 1,
loc.length));
- if (future->isPaused())
- future->waitForResume();
+ m_promise.suspendIfRequested();
return usages;
}
};
@@ -751,7 +740,7 @@ class SearchFileForType
using Usage = FindReferences::Usage;
const QString name;
const ObjectValue *scope;
- QFutureInterface<Usage> *future;
+ QPromise<Usage> &m_promise;
public:
// needed by QtConcurrent
@@ -761,16 +750,15 @@ public:
SearchFileForType(const ContextPtr &context,
const QString &name,
const ObjectValue *scope,
- QFutureInterface<Usage> *future)
- : context(context), name(name), scope(scope), future(future)
+ QPromise<Usage> &promise)
+ : context(context), name(name), scope(scope), m_promise(promise)
{ }
QList<Usage> operator()(const Utils::FilePath &fileName)
{
QList<Usage> usages;
- if (future->isPaused())
- future->waitForResume();
- if (future->isCanceled())
+ m_promise.suspendIfRequested();
+ if (m_promise.isCanceled())
return usages;
Document::Ptr doc = context->snapshot().document(fileName);
if (!doc)
@@ -781,8 +769,7 @@ public:
const FindTypeUsages::Result results = findUsages(name, scope);
for (const SourceLocation &loc : results)
usages.append(Usage(fileName, matchingLine(loc.offset, doc->source()), loc.startLine, loc.startColumn - 1, loc.length));
- if (future->isPaused())
- future->waitForResume();
+ m_promise.suspendIfRequested();
return usages;
}
};
@@ -790,7 +777,7 @@ public:
class UpdateUI
{
using Usage = FindReferences::Usage;
- QFutureInterface<Usage> *future;
+ QPromise<Usage> &m_promise;
public:
// needed by QtConcurrent
@@ -798,14 +785,13 @@ public:
using second_argument_type = const QList<Usage> &;
using result_type = void;
- UpdateUI(QFutureInterface<Usage> *future): future(future) {}
+ UpdateUI(QPromise<Usage> &promise): m_promise(promise) {}
void operator()(QList<Usage> &, const QList<Usage> &usages)
{
for (const Usage &u : usages)
- future->reportResult(u);
-
- future->setProgressValue(future->progressValue() + 1);
+ m_promise.addResult(u);
+ m_promise.setProgressValue(m_promise.future().progressValue() + 1);
}
};
@@ -822,7 +808,7 @@ FindReferences::FindReferences(QObject *parent)
FindReferences::~FindReferences() = default;
-static void find_helper(QFutureInterface<FindReferences::Usage> &future,
+static void find_helper(QPromise<FindReferences::Usage> &promise,
const ModelManagerInterface::WorkingCopy &workingCopy,
Snapshot snapshot,
const Utils::FilePath &fileName,
@@ -885,7 +871,7 @@ static void find_helper(QFutureInterface<FindReferences::Usage> &future,
}
files = Utils::filteredUnique(files);
- future.setProgressRange(0, files.size());
+ promise.setProgressRange(0, files.size());
// report a dummy usage to indicate the search is starting
FindReferences::Usage searchStarting(Utils::FilePath::fromString(replacement), name, 0, 0, 0);
@@ -894,10 +880,10 @@ static void find_helper(QFutureInterface<FindReferences::Usage> &future,
const ObjectValue *typeValue = value_cast<ObjectValue>(findTarget.targetValue());
if (!typeValue)
return;
- future.reportResult(searchStarting);
+ promise.addResult(searchStarting);
- SearchFileForType process(context, name, typeValue, &future);
- UpdateUI reduce(&future);
+ SearchFileForType process(context, name, typeValue, promise);
+ UpdateUI reduce(promise);
QtConcurrent::blockingMappedReduced<QList<FindReferences::Usage> > (files, process, reduce);
} else {
@@ -909,21 +895,21 @@ static void find_helper(QFutureInterface<FindReferences::Usage> &future,
return;
if (!scope->className().isEmpty())
searchStarting.lineText.prepend(scope->className() + QLatin1Char('.'));
- future.reportResult(searchStarting);
+ promise.addResult(searchStarting);
- ProcessFile process(context, name, scope, &future);
- UpdateUI reduce(&future);
+ ProcessFile process(context, name, scope, promise);
+ UpdateUI reduce(promise);
QtConcurrent::blockingMappedReduced<QList<FindReferences::Usage> > (files, process, reduce);
}
- future.setProgressValue(files.size());
+ promise.setProgressValue(files.size());
}
void FindReferences::findUsages(const Utils::FilePath &fileName, quint32 offset)
{
ModelManagerInterface *modelManager = ModelManagerInterface::instance();
- QFuture<Usage> result = Utils::runAsync(&find_helper, ModelManagerInterface::workingCopy(),
+ QFuture<Usage> result = Utils::asyncRun(&find_helper, ModelManagerInterface::workingCopy(),
modelManager->snapshot(), fileName, offset, QString());
m_watcher.setFuture(result);
m_synchronizer.addFuture(result);
@@ -940,7 +926,7 @@ void FindReferences::renameUsages(const Utils::FilePath &fileName,
if (newName.isNull())
newName = QLatin1String("");
- QFuture<Usage> result = Utils::runAsync(&find_helper, ModelManagerInterface::workingCopy(),
+ QFuture<Usage> result = Utils::asyncRun(&find_helper, ModelManagerInterface::workingCopy(),
modelManager->snapshot(), fileName, offset, newName);
m_watcher.setFuture(result);
m_synchronizer.addFuture(result);
diff --git a/src/plugins/qmljseditor/qmljsquickfix.h b/src/plugins/qmljseditor/qmljsquickfix.h
index 643334a94c7..b050ba4d1f1 100644
--- a/src/plugins/qmljseditor/qmljsquickfix.h
+++ b/src/plugins/qmljseditor/qmljsquickfix.h
@@ -44,7 +44,7 @@ protected:
const QmlJSTools::SemanticInfo &semanticInfo() const;
- /// \returns The name of the file for for which this operation is invoked.
+ /// Returns The name of the file for for which this operation is invoked.
Utils::FilePath fileName() const;
private:
diff --git a/src/plugins/qmljseditor/qmljssemantichighlighter.cpp b/src/plugins/qmljseditor/qmljssemantichighlighter.cpp
index 0ca826cb005..56f8dfd0bb8 100644
--- a/src/plugins/qmljseditor/qmljssemantichighlighter.cpp
+++ b/src/plugins/qmljseditor/qmljssemantichighlighter.cpp
@@ -20,9 +20,9 @@
#include <texteditor/texteditorconstants.h>
#include <texteditor/texteditorsettings.h>
#include <texteditor/fontsettings.h>
+#include <utils/asynctask.h>
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
-#include <utils/runextensions.h>
#include <QDebug>
#include <QTextDocument>
@@ -156,11 +156,11 @@ public:
AddMessagesHighlights,
SkipMessagesHighlights,
};
- CollectionTask(QFutureInterface<SemanticHighlighter::Use> &futureInterface,
+ CollectionTask(QPromise<SemanticHighlighter::Use> &promise,
const QmlJSTools::SemanticInfo &semanticInfo,
const TextEditor::FontSettings &fontSettings,
Flags flags)
- : m_futureInterface(futureInterface)
+ : m_promise(promise)
, m_semanticInfo(semanticInfo)
, m_fontSettings(fontSettings)
, m_scopeChain(semanticInfo.scopeChain())
@@ -211,7 +211,7 @@ public:
protected:
void accept(Node *ast)
{
- if (m_futureInterface.isCanceled())
+ if (m_promise.isCanceled())
return;
if (ast)
ast->accept(this);
@@ -219,7 +219,7 @@ protected:
void scopedAccept(Node *ast, Node *child)
{
- if (m_futureInterface.isCanceled())
+ if (m_promise.isCanceled())
return;
m_scopeBuilder.push(ast);
accept(child);
@@ -510,12 +510,13 @@ private:
return;
Utils::sort(m_uses, sortByLinePredicate);
- m_futureInterface.reportResults(m_uses);
+ for (const SemanticHighlighter::Use &use : std::as_const(m_uses))
+ m_promise.addResult(use);
m_uses.clear();
m_uses.reserve(chunkSize);
}
- QFutureInterface<SemanticHighlighter::Use> &m_futureInterface;
+ QPromise<SemanticHighlighter::Use> &m_promise;
const QmlJSTools::SemanticInfo &m_semanticInfo;
const TextEditor::FontSettings &m_fontSettings;
ScopeChain m_scopeChain;
@@ -549,11 +550,8 @@ void SemanticHighlighter::rerun(const QmlJSTools::SemanticInfo &semanticInfo)
m_watcher.cancel();
m_startRevision = m_document->document()->revision();
- auto future = Utils::runAsync(QThread::LowestPriority,
- &SemanticHighlighter::run,
- this,
- semanticInfo,
- TextEditor::TextEditorSettings::fontSettings());
+ auto future = Utils::asyncRun(QThread::LowestPriority, &SemanticHighlighter::run, this,
+ semanticInfo, TextEditor::TextEditorSettings::fontSettings());
m_watcher.setFuture(future);
m_futureSynchronizer.addFuture(future);
}
@@ -590,11 +588,11 @@ void SemanticHighlighter::finished()
m_document->syntaxHighlighter(), m_watcher.future());
}
-void SemanticHighlighter::run(QFutureInterface<SemanticHighlighter::Use> &futureInterface,
+void SemanticHighlighter::run(QPromise<Use> &promise,
const QmlJSTools::SemanticInfo &semanticInfo,
const TextEditor::FontSettings &fontSettings)
{
- CollectionTask task(futureInterface,
+ CollectionTask task(promise,
semanticInfo,
fontSettings,
(m_enableWarnings ? CollectionTask::AddMessagesHighlights
diff --git a/src/plugins/qmljseditor/qmljssemantichighlighter.h b/src/plugins/qmljseditor/qmljssemantichighlighter.h
index 6ea1e85c6e6..74c51d12d7f 100644
--- a/src/plugins/qmljseditor/qmljssemantichighlighter.h
+++ b/src/plugins/qmljseditor/qmljssemantichighlighter.h
@@ -62,7 +62,7 @@ public:
private:
void applyResults(int from, int to);
void finished();
- void run(QFutureInterface<Use> &futureInterface,
+ void run(QPromise<Use> &promise,
const QmlJSTools::SemanticInfo &semanticInfo,
const TextEditor::FontSettings &fontSettings);
diff --git a/src/plugins/qmljseditor/qmltaskmanager.cpp b/src/plugins/qmljseditor/qmltaskmanager.cpp
index 08019019da0..0d25bb3f13a 100644
--- a/src/plugins/qmljseditor/qmltaskmanager.cpp
+++ b/src/plugins/qmljseditor/qmltaskmanager.cpp
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "qmltaskmanager.h"
-#include "qmljseditor.h"
#include "qmljseditorconstants.h"
#include <coreplugin/idocument.h>
@@ -13,7 +12,7 @@
#include <qmljs/qmljsconstants.h>
#include <qmljs/qmljslink.h>
#include <qmljs/qmljscheck.h>
-#include <utils/runextensions.h>
+#include <utils/asynctask.h>
#include <QDebug>
#include <QtConcurrentRun>
@@ -57,7 +56,7 @@ static Tasks convertToTasks(const QList<StaticAnalysis::Message> &messages, cons
return convertToTasks(diagnostics, fileName, category);
}
-void QmlTaskManager::collectMessages(QFutureInterface<FileErrorMessages> &future,
+void QmlTaskManager::collectMessages(QPromise<FileErrorMessages> &promise,
Snapshot snapshot,
const QList<ModelManagerInterface::ProjectInfo> &projectInfos,
ViewerContext vContext,
@@ -96,8 +95,8 @@ void QmlTaskManager::collectMessages(QFutureInterface<FileErrorMessages> &future
}
if (!result.tasks.isEmpty())
- future.reportResult(result);
- if (future.isCanceled())
+ promise.addResult(result);
+ if (promise.isCanceled())
break;
}
}
@@ -127,8 +126,7 @@ void QmlTaskManager::updateMessagesNow(bool updateSemantic)
ModelManagerInterface *modelManager = ModelManagerInterface::instance();
// process them
- QFuture<FileErrorMessages> future =
- Utils::runAsync(
+ QFuture<FileErrorMessages> future = Utils::asyncRun(
&collectMessages, modelManager->newestSnapshot(), modelManager->projectInfos(),
modelManager->defaultVContext(Dialect::AnyLanguage), updateSemantic);
m_messageCollector.setFuture(future);
diff --git a/src/plugins/qmljseditor/qmltaskmanager.h b/src/plugins/qmljseditor/qmltaskmanager.h
index 26fa3bd347f..226fd2203d4 100644
--- a/src/plugins/qmljseditor/qmltaskmanager.h
+++ b/src/plugins/qmljseditor/qmltaskmanager.h
@@ -45,7 +45,7 @@ private:
Utils::FilePath fileName;
ProjectExplorer::Tasks tasks;
};
- static void collectMessages(QFutureInterface<FileErrorMessages> &future,
+ static void collectMessages(QPromise<FileErrorMessages> &promise,
QmlJS::Snapshot snapshot,
const QList<QmlJS::ModelManagerInterface::ProjectInfo> &projectInfos,
QmlJS::ViewerContext vContext,
diff --git a/src/plugins/qmljstools/qmljsfunctionfilter.cpp b/src/plugins/qmljstools/qmljsfunctionfilter.cpp
index 4d4ce05fb72..e713351e635 100644
--- a/src/plugins/qmljstools/qmljsfunctionfilter.cpp
+++ b/src/plugins/qmljstools/qmljsfunctionfilter.cpp
@@ -5,19 +5,17 @@
#include "qmljslocatordata.h"
#include "qmljstoolstr.h"
-#include <coreplugin/editormanager/editormanager.h>
#include <utils/algorithm.h>
#include <QRegularExpression>
-#include <numeric>
-
+using namespace Core;
using namespace QmlJSTools::Internal;
Q_DECLARE_METATYPE(LocatorData::Entry)
FunctionFilter::FunctionFilter(LocatorData *data, QObject *parent)
- : Core::ILocatorFilter(parent)
+ : ILocatorFilter(parent)
, m_data(data)
{
setId("Functions");
@@ -28,11 +26,10 @@ FunctionFilter::FunctionFilter(LocatorData *data, QObject *parent)
FunctionFilter::~FunctionFilter() = default;
-QList<Core::LocatorFilterEntry> FunctionFilter::matchesFor(
- QFutureInterface<Core::LocatorFilterEntry> &future,
- const QString &entry)
+QList<LocatorFilterEntry> FunctionFilter::matchesFor(QFutureInterface<LocatorFilterEntry> &future,
+ const QString &entry)
{
- QList<Core::LocatorFilterEntry> entries[int(MatchLevel::Count)];
+ QList<LocatorFilterEntry> entries[int(MatchLevel::Count)];
const Qt::CaseSensitivity caseSensitivityForPrefix = caseSensitivity(entry);
const QRegularExpression regexp = createRegExp(entry);
@@ -51,7 +48,8 @@ QList<Core::LocatorFilterEntry> FunctionFilter::matchesFor(
const QRegularExpressionMatch match = regexp.match(info.symbolName);
if (match.hasMatch()) {
QVariant id = QVariant::fromValue(info);
- Core::LocatorFilterEntry filterEntry(this, info.displayName, id/*, info.icon*/);
+ LocatorFilterEntry filterEntry(this, info.displayName, id/*, info.icon*/);
+ filterEntry.linkForEditor = {info.fileName, info.line, info.column};
filterEntry.extraInfo = info.extraInfo;
filterEntry.highlightInfo = highlightInfo(match);
@@ -67,18 +65,7 @@ QList<Core::LocatorFilterEntry> FunctionFilter::matchesFor(
for (auto &entry : entries) {
if (entry.size() < 1000)
- Utils::sort(entry, Core::LocatorFilterEntry::compareLexigraphically);
+ Utils::sort(entry, LocatorFilterEntry::compareLexigraphically);
}
-
- return std::accumulate(std::begin(entries), std::end(entries), QList<Core::LocatorFilterEntry>());
-}
-
-void FunctionFilter::accept(const Core::LocatorFilterEntry &selection,
- QString *newText, int *selectionStart, int *selectionLength) const
-{
- Q_UNUSED(newText)
- Q_UNUSED(selectionStart)
- Q_UNUSED(selectionLength)
- const LocatorData::Entry entry = qvariant_cast<LocatorData::Entry>(selection.internalData);
- Core::EditorManager::openEditorAt({entry.fileName, entry.line, entry.column});
+ return std::accumulate(std::begin(entries), std::end(entries), QList<LocatorFilterEntry>());
}
diff --git a/src/plugins/qmljstools/qmljsfunctionfilter.h b/src/plugins/qmljstools/qmljsfunctionfilter.h
index 83b93e8f361..4334d5d9fca 100644
--- a/src/plugins/qmljstools/qmljsfunctionfilter.h
+++ b/src/plugins/qmljstools/qmljsfunctionfilter.h
@@ -20,9 +20,6 @@ public:
QList<Core::LocatorFilterEntry> matchesFor(QFutureInterface<Core::LocatorFilterEntry> &future,
const QString &entry) override;
- void accept(const Core::LocatorFilterEntry &selection,
- QString *newText, int *selectionStart, int *selectionLength) const override;
-
private:
LocatorData *m_data = nullptr;
};
diff --git a/src/plugins/qmljstools/qmljslocatordata.cpp b/src/plugins/qmljstools/qmljslocatordata.cpp
index 679a230e8e1..477447c9e36 100644
--- a/src/plugins/qmljstools/qmljslocatordata.cpp
+++ b/src/plugins/qmljstools/qmljslocatordata.cpp
@@ -4,7 +4,7 @@
#include "qmljslocatordata.h"
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <qmljs/qmljsmodelmanagerinterface.h>
#include <qmljs/qmljsutils.h>
@@ -40,10 +40,10 @@ LocatorData::LocatorData()
connect(manager, &ModelManagerInterface::aboutToRemoveFiles,
this, &LocatorData::onAboutToRemoveFiles);
- ProjectExplorer::SessionManager *session = ProjectExplorer::SessionManager::instance();
+ ProjectExplorer::ProjectManager *session = ProjectExplorer::ProjectManager::instance();
if (session)
connect(session,
- &ProjectExplorer::SessionManager::projectRemoved,
+ &ProjectExplorer::ProjectManager::projectRemoved,
this,
[this](ProjectExplorer::Project *) { m_entries.clear(); });
}
diff --git a/src/plugins/qmljstools/qmljsmodelmanager.cpp b/src/plugins/qmljstools/qmljsmodelmanager.cpp
index da1d82b73d0..914a4f77945 100644
--- a/src/plugins/qmljstools/qmljsmodelmanager.cpp
+++ b/src/plugins/qmljstools/qmljsmodelmanager.cpp
@@ -18,10 +18,11 @@
#include <projectexplorer/buildsystem.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorerconstants.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/projectnodes.h>
#include <projectexplorer/projecttree.h>
#include <projectexplorer/runconfiguration.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <qmljs/qmljsbind.h>
@@ -273,9 +274,9 @@ void ModelManager::delayedInitialization()
connect(cppModelManager, &CppEditor::CppModelManager::documentUpdated,
this, &ModelManagerInterface::maybeQueueCppQmlTypeUpdate, Qt::DirectConnection);
- connect(SessionManager::instance(), &SessionManager::projectRemoved,
+ connect(ProjectManager::instance(), &ProjectManager::projectRemoved,
this, &ModelManager::removeProjectInfo);
- connect(SessionManager::instance(), &SessionManager::startupProjectChanged,
+ connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged,
this, &ModelManager::updateDefaultProjectInfo);
ViewerContext qbsVContext;
@@ -323,7 +324,7 @@ ModelManagerInterface::WorkingCopy ModelManager::workingCopyInternal() const
void ModelManager::updateDefaultProjectInfo()
{
// needs to be performed in the ui thread
- Project *currentProject = SessionManager::startupProject();
+ Project *currentProject = ProjectManager::startupProject();
setDefaultProject(containsProject(currentProject)
? projectInfo(currentProject)
: defaultProjectInfoForProject(currentProject, {}),
diff --git a/src/plugins/qmljstools/qmljsrefactoringchanges.h b/src/plugins/qmljstools/qmljsrefactoringchanges.h
index d16564832b6..33545e2bfc5 100644
--- a/src/plugins/qmljstools/qmljsrefactoringchanges.h
+++ b/src/plugins/qmljstools/qmljsrefactoringchanges.h
@@ -24,7 +24,7 @@ public:
QmlJS::Document::Ptr qmljsDocument() const;
/*!
- \returns the offset in the document for the start position of the given
+ Returns the offset in the document for the start position of the given
source location.
*/
unsigned startOf(const QmlJS::SourceLocation &loc) const;
diff --git a/src/plugins/qmljstools/qmljstools.qbs b/src/plugins/qmljstools/qmljstools.qbs
index 68c9a9dfc15..d13342e8dd2 100644
--- a/src/plugins/qmljstools/qmljstools.qbs
+++ b/src/plugins/qmljstools/qmljstools.qbs
@@ -54,9 +54,7 @@ QtcPlugin {
"qmljstools.qrc"
]
- Group {
- name: "Tests"
- condition: qtc.testsEnabled
+ QtcTestFiles {
files: ["qmljstools_test.cpp"]
}
diff --git a/src/plugins/qmlpreview/qmlpreview.qbs b/src/plugins/qmlpreview/qmlpreview.qbs
index 7b0f96dde76..e44f432c41a 100644
--- a/src/plugins/qmlpreview/qmlpreview.qbs
+++ b/src/plugins/qmlpreview/qmlpreview.qbs
@@ -38,9 +38,7 @@ QtcPlugin {
]
}
- Group {
- name: "Unit tests"
- condition: qtc.testsEnabled
+ QtcTestFiles {
prefix: "tests/"
files: [
"qmlpreviewclient_test.cpp",
diff --git a/src/plugins/qmlpreview/qmlpreviewplugin.cpp b/src/plugins/qmlpreview/qmlpreviewplugin.cpp
index 6175eac2a2f..8803475d8a6 100644
--- a/src/plugins/qmlpreview/qmlpreviewplugin.cpp
+++ b/src/plugins/qmlpreview/qmlpreviewplugin.cpp
@@ -25,10 +25,10 @@
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectexplorerconstants.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/projectnodes.h>
#include <projectexplorer/projecttree.h>
#include <projectexplorer/runconfiguration.h>
-#include <projectexplorer/session.h>
#include <projectexplorer/target.h>
#include <qmljs/qmljsdocument.h>
@@ -150,15 +150,15 @@ QmlPreviewPluginPrivate::QmlPreviewPluginPrivate(QmlPreviewPlugin *parent)
Constants::M_BUILDPROJECT);
QAction *action = new QAction(Tr::tr("QML Preview"), this);
action->setToolTip(Tr::tr("Preview changes to QML code live in your application."));
- action->setEnabled(SessionManager::startupProject() != nullptr);
- connect(SessionManager::instance(), &SessionManager::startupProjectChanged, action,
+ action->setEnabled(ProjectManager::startupProject() != nullptr);
+ connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged, action,
&QAction::setEnabled);
connect(action, &QAction::triggered, this, [this]() {
if (auto multiLanguageAspect = QmlProjectManager::QmlMultiLanguageAspect::current())
m_localeIsoCode = multiLanguageAspect->currentLocale();
bool skipDeploy = false;
- const Kit *kit = SessionManager::startupTarget()->kit();
- if (SessionManager::startupTarget() && kit)
+ const Kit *kit = ProjectManager::startupTarget()->kit();
+ if (ProjectManager::startupTarget() && kit)
skipDeploy = kit->supportedPlatforms().contains(Android::Constants::ANDROID_DEVICE_TYPE)
|| DeviceTypeKitAspect::deviceTypeId(kit) == Android::Constants::ANDROID_DEVICE_TYPE;
ProjectExplorerPlugin::runStartupProject(Constants::QML_PREVIEW_RUN_MODE, skipDeploy);
diff --git a/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp b/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp
index 57088ad19bf..3aa935db74e 100644
--- a/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp
+++ b/src/plugins/qmlpreview/qmlpreviewruncontrol.cpp
@@ -9,7 +9,7 @@
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectexplorerconstants.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <qmldebug/qmldebugcommandlinearguments.h>
diff --git a/src/plugins/qmlprofiler/qmlprofiler.qbs b/src/plugins/qmlprofiler/qmlprofiler.qbs
index 5cc3604a07b..56a1e00cd6a 100644
--- a/src/plugins/qmlprofiler/qmlprofiler.qbs
+++ b/src/plugins/qmlprofiler/qmlprofiler.qbs
@@ -73,9 +73,7 @@ QtcPlugin {
files: "qml/**"
}
- Group {
- name: "Unit tests"
- condition: qtc.testsEnabled
+ QtcTestFiles {
prefix: "tests/"
files: [
"debugmessagesmodel_test.cpp", "debugmessagesmodel_test.h",
diff --git a/src/plugins/qmlprofiler/qmlprofilerdetailsrewriter.cpp b/src/plugins/qmlprofiler/qmlprofilerdetailsrewriter.cpp
index 396c8305680..e9f0b1a7807 100644
--- a/src/plugins/qmlprofiler/qmlprofilerdetailsrewriter.cpp
+++ b/src/plugins/qmlprofiler/qmlprofilerdetailsrewriter.cpp
@@ -6,7 +6,7 @@
#include <projectexplorer/kit.h>
#include <projectexplorer/kitinformation.h>
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <projectexplorer/runconfiguration.h>
#include <qmljs/parser/qmljsast_p.h>
diff --git a/src/plugins/qmlprofiler/qmlprofilertool.cpp b/src/plugins/qmlprofiler/qmlprofilertool.cpp
index 579a870a4b0..dc33663e895 100644
--- a/src/plugins/qmlprofiler/qmlprofilertool.cpp
+++ b/src/plugins/qmlprofiler/qmlprofilertool.cpp
@@ -39,8 +39,8 @@
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectexplorerconstants.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
-#include <projectexplorer/session.h>
#include <qtsupport/qtkitinformation.h>
@@ -540,7 +540,7 @@ ProjectExplorer::RunControl *QmlProfilerTool::attachToWaitingApplication()
d->m_viewContainer->perspective()->select();
auto runControl = new RunControl(ProjectExplorer::Constants::QML_PROFILER_RUN_MODE);
- runControl->copyDataFromRunConfiguration(SessionManager::startupRunConfiguration());
+ runControl->copyDataFromRunConfiguration(ProjectManager::startupRunConfiguration());
auto profiler = new QmlProfilerRunner(runControl);
profiler->setServerUrl(serverUrl);
diff --git a/src/plugins/qmlprofiler/tests/qmlprofilerclientmanager_test.cpp b/src/plugins/qmlprofiler/tests/qmlprofilerclientmanager_test.cpp
index b14b4962e7f..366a6028bb4 100644
--- a/src/plugins/qmlprofiler/tests/qmlprofilerclientmanager_test.cpp
+++ b/src/plugins/qmlprofiler/tests/qmlprofilerclientmanager_test.cpp
@@ -105,9 +105,9 @@ void QmlProfilerClientManagerTest::testConnectionFailure()
QFETCH(QmlProfilerStateManager *, stateManager);
QFETCH(QUrl, serverUrl);
- QSignalSpy openedSpy(&clientManager, SIGNAL(connectionOpened()));
- QSignalSpy closedSpy(&clientManager, SIGNAL(connectionClosed()));
- QSignalSpy failedSpy(&clientManager, SIGNAL(connectionFailed()));
+ QSignalSpy openedSpy(&clientManager, &QmlProfilerClientManager::connectionOpened);
+ QSignalSpy closedSpy(&clientManager, &QmlProfilerClientManager::connectionClosed);
+ QSignalSpy failedSpy(&clientManager, &QmlProfilerClientManager::connectionFailed);
QVERIFY(!clientManager.isConnected());
@@ -137,9 +137,9 @@ void QmlProfilerClientManagerTest::testConnectionFailure()
void QmlProfilerClientManagerTest::testUnresponsiveTcp()
{
- QSignalSpy openedSpy(&clientManager, SIGNAL(connectionOpened()));
- QSignalSpy closedSpy(&clientManager, SIGNAL(connectionClosed()));
- QSignalSpy failedSpy(&clientManager, SIGNAL(connectionFailed()));
+ QSignalSpy openedSpy(&clientManager, &QmlProfilerClientManager::connectionOpened);
+ QSignalSpy closedSpy(&clientManager, &QmlProfilerClientManager::connectionClosed);
+ QSignalSpy failedSpy(&clientManager, &QmlProfilerClientManager::connectionFailed);
QVERIFY(!clientManager.isConnected());
@@ -150,7 +150,7 @@ void QmlProfilerClientManagerTest::testUnresponsiveTcp()
QTcpServer server;
server.listen(QHostAddress(serverUrl.host()), serverUrl.port());
- QSignalSpy connectionSpy(&server, SIGNAL(newConnection()));
+ QSignalSpy connectionSpy(&server, &QTcpServer::newConnection);
clientManager.connectToServer(serverUrl);
@@ -165,9 +165,9 @@ void QmlProfilerClientManagerTest::testUnresponsiveTcp()
void QmlProfilerClientManagerTest::testUnresponsiveLocal()
{
- QSignalSpy openedSpy(&clientManager, SIGNAL(connectionOpened()));
- QSignalSpy closedSpy(&clientManager, SIGNAL(connectionClosed()));
- QSignalSpy failedSpy(&clientManager, SIGNAL(connectionFailed()));
+ QSignalSpy openedSpy(&clientManager, &QmlProfilerClientManager::connectionOpened);
+ QSignalSpy closedSpy(&clientManager, &QmlProfilerClientManager::connectionClosed);
+ QSignalSpy failedSpy(&clientManager, &QmlProfilerClientManager::connectionFailed);
QVERIFY(!clientManager.isConnected());
@@ -176,7 +176,7 @@ void QmlProfilerClientManagerTest::testUnresponsiveLocal()
QUrl socketUrl = Utils::urlFromLocalSocket();
QLocalSocket socket;
- QSignalSpy connectionSpy(&socket, SIGNAL(connected()));
+ QSignalSpy connectionSpy(&socket, &QLocalSocket::connected);
clientManager.connectToServer(socketUrl);
@@ -209,8 +209,8 @@ void QmlProfilerClientManagerTest::testResponsiveTcp()
QUrl serverUrl = Utils::urlFromLocalHostAndFreePort();
- QSignalSpy openedSpy(&clientManager, SIGNAL(connectionOpened()));
- QSignalSpy closedSpy(&clientManager, SIGNAL(connectionClosed()));
+ QSignalSpy openedSpy(&clientManager, &QmlProfilerClientManager::connectionOpened);
+ QSignalSpy closedSpy(&clientManager, &QmlProfilerClientManager::connectionClosed);
QVERIFY(!clientManager.isConnected());
@@ -267,8 +267,8 @@ void QmlProfilerClientManagerTest::testResponsiveLocal()
QUrl socketUrl = Utils::urlFromLocalSocket();
- QSignalSpy openedSpy(&clientManager, SIGNAL(connectionOpened()));
- QSignalSpy closedSpy(&clientManager, SIGNAL(connectionClosed()));
+ QSignalSpy openedSpy(&clientManager, &QmlProfilerClientManager::connectionOpened);
+ QSignalSpy closedSpy(&clientManager, &QmlProfilerClientManager::connectionClosed);
QVERIFY(!clientManager.isConnected());
@@ -320,9 +320,9 @@ void QmlProfilerClientManagerTest::testInvalidData()
MessageHandler handler(&invalidHelloMessageHandler);
Q_UNUSED(handler)
- QSignalSpy openedSpy(&clientManager, SIGNAL(connectionOpened()));
- QSignalSpy closedSpy(&clientManager, SIGNAL(connectionClosed()));
- QSignalSpy failedSpy(&clientManager, SIGNAL(connectionFailed()));
+ QSignalSpy openedSpy(&clientManager, &QmlProfilerClientManager::connectionOpened);
+ QSignalSpy closedSpy(&clientManager, &QmlProfilerClientManager::connectionClosed);
+ QSignalSpy failedSpy(&clientManager, &QmlProfilerClientManager::connectionFailed);
QVERIFY(!clientManager.isConnected());
@@ -365,8 +365,8 @@ void QmlProfilerClientManagerTest::testStopRecording()
QmlProfilerClientManager clientManager;
clientManager.setRetryInterval(10);
clientManager.setMaximumRetries(10);
- QSignalSpy openedSpy(&clientManager, SIGNAL(connectionOpened()));
- QSignalSpy closedSpy(&clientManager, SIGNAL(connectionClosed()));
+ QSignalSpy openedSpy(&clientManager, &QmlProfilerClientManager::connectionOpened);
+ QSignalSpy closedSpy(&clientManager, &QmlProfilerClientManager::connectionClosed);
QVERIFY(!clientManager.isConnected());
diff --git a/src/plugins/qmlprofiler/tests/qmlprofilerdetailsrewriter_test.cpp b/src/plugins/qmlprofiler/tests/qmlprofilerdetailsrewriter_test.cpp
index e01b6ab5ef3..d71836319cc 100644
--- a/src/plugins/qmlprofiler/tests/qmlprofilerdetailsrewriter_test.cpp
+++ b/src/plugins/qmlprofiler/tests/qmlprofilerdetailsrewriter_test.cpp
@@ -3,15 +3,16 @@
#include "qmlprofilerdetailsrewriter_test.h"
+#include <projectexplorer/buildconfiguration.h>
#include <projectexplorer/buildinfo.h>
#include <projectexplorer/customexecutablerunconfiguration.h>
-#include <projectexplorer/project.h>
-#include <projectexplorer/target.h>
+#include <projectexplorer/kitinformation.h>
#include <projectexplorer/kitmanager.h>
+#include <projectexplorer/project.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/projectnodes.h>
-#include <projectexplorer/session.h>
-#include <projectexplorer/kitinformation.h>
-#include <projectexplorer/buildconfiguration.h>
+#include <projectexplorer/target.h>
+
#include <utils/filepath.h>
#include <QLibraryInfo>
@@ -157,15 +158,15 @@ void QmlProfilerDetailsRewriterTest::testPopulateFileFinder()
// Test that the rewriter will populate from available projects if given nullptr as parameter.
DummyProject *project1 = new DummyProject(":/nix.nix");
- ProjectExplorer::SessionManager::addProject(project1);
+ ProjectExplorer::ProjectManager::addProject(project1);
DummyProject *project2 = new DummyProject(":/qmlprofiler/tests/Test.qml");
- ProjectExplorer::SessionManager::addProject(project2);
+ ProjectExplorer::ProjectManager::addProject(project2);
m_rewriter.populateFileFinder(nullptr);
QCOMPARE(m_rewriter.getLocalFile("Test.qml"),
Utils::FilePath::fromString(":/qmlprofiler/tests/Test.qml"));
- ProjectExplorer::SessionManager::removeProject(project1);
- ProjectExplorer::SessionManager::removeProject(project2);
+ ProjectExplorer::ProjectManager::removeProject(project1);
+ ProjectExplorer::ProjectManager::removeProject(project2);
}
void QmlProfilerDetailsRewriterTest::seedRewriter()
@@ -174,12 +175,11 @@ void QmlProfilerDetailsRewriterTest::seedRewriter()
m_modelManager = new QmlJS::ModelManagerInterface(this);
QString filename = ":/qmlprofiler/tests/Test.qml";
- QFutureInterface<void> result;
QmlJS::PathsAndLanguages lPaths;
lPaths.maybeInsert(
Utils::FilePath::fromString(QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath)),
QmlJS::Dialect::Qml);
- QmlJS::ModelManagerInterface::importScan(result, QmlJS::ModelManagerInterface::workingCopy(),
+ QmlJS::ModelManagerInterface::importScan(QmlJS::ModelManagerInterface::workingCopy(),
lPaths, m_modelManager, false);
QFile file(filename);
@@ -196,11 +196,11 @@ void QmlProfilerDetailsRewriterTest::seedRewriter()
ProjectExplorer::SysRootKitAspect::setSysRoot(kit.get(), "/nowhere");
DummyProject *project = new DummyProject(Utils::FilePath::fromString(filename));
- ProjectExplorer::SessionManager::addProject(project);
+ ProjectExplorer::ProjectManager::addProject(project);
m_rewriter.populateFileFinder(project->addTargetForKit(kit.get()));
- ProjectExplorer::SessionManager::removeProject(project);
+ ProjectExplorer::ProjectManager::removeProject(project);
}
} // namespace Internal
diff --git a/src/plugins/qmlprojectmanager/cmakegen/cmakeprojectconverter.cpp b/src/plugins/qmlprojectmanager/cmakegen/cmakeprojectconverter.cpp
index 99995bc2abe..21a538c7c51 100644
--- a/src/plugins/qmlprojectmanager/cmakegen/cmakeprojectconverter.cpp
+++ b/src/plugins/qmlprojectmanager/cmakegen/cmakeprojectconverter.cpp
@@ -1,5 +1,6 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
#include "cmakeprojectconverter.h"
#include "cmakeprojectconverterdialog.h"
#include "generatecmakelists.h"
@@ -11,7 +12,7 @@
#include <coreplugin/icore.h>
#include <projectexplorer/projectexplorer.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <qmlprojectmanager/qmlprojectmanagerconstants.h>
@@ -41,10 +42,10 @@ void CmakeProjectConverter::generateMenuEntry(QObject *parent)
Core::Command *cmd = Core::ActionManager::registerAction(action, "QmlProject.ConvertToCmakeProject");
exportMenu->addAction(cmd, QmlProjectManager::Constants::G_EXPORT_CONVERT);
- action->setEnabled(isProjectConvertable(ProjectExplorer::SessionManager::startupProject()));
- QObject::connect(ProjectExplorer::SessionManager::instance(),
- &ProjectExplorer::SessionManager::startupProjectChanged, [action]() {
- action->setEnabled(isProjectConvertable(ProjectExplorer::SessionManager::startupProject()));
+ action->setEnabled(isProjectConvertable(ProjectExplorer::ProjectManager::startupProject()));
+ QObject::connect(ProjectExplorer::ProjectManager::instance(),
+ &ProjectExplorer::ProjectManager::startupProjectChanged, [action]() {
+ action->setEnabled(isProjectConvertable(ProjectExplorer::ProjectManager::startupProject()));
});
}
@@ -83,7 +84,7 @@ bool CmakeProjectConverter::isProjectCurrentFormat(const ProjectExplorer::Projec
void CmakeProjectConverter::onConvertProject()
{
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
const QmlProjectManager::QmlProject *qmlProject =
qobject_cast<const QmlProjectManager::QmlProject*>(project);
if (qmlProject) {
diff --git a/src/plugins/qmlprojectmanager/cmakegen/generatecmakelists.cpp b/src/plugins/qmlprojectmanager/cmakegen/generatecmakelists.cpp
index ef83206d982..e1124319196 100644
--- a/src/plugins/qmlprojectmanager/cmakegen/generatecmakelists.cpp
+++ b/src/plugins/qmlprojectmanager/cmakegen/generatecmakelists.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "generatecmakelists.h"
+
#include "generatecmakelistsconstants.h"
#include "cmakegeneratordialog.h"
#include "../qmlprojectmanagertr.h"
@@ -10,9 +11,9 @@
#include <coreplugin/actionmanager/actioncontainer.h>
#include <projectexplorer/buildsystem.h>
-#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectexplorerconstants.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <qmlprojectmanager/qmlmainfileaspect.h>
@@ -79,18 +80,18 @@ void generateMenuEntry(QObject *parent)
exportMenu->addAction(cmd, QmlProjectManager::Constants::G_EXPORT_GENERATE);
action->setEnabled(false);
- QObject::connect(ProjectExplorer::SessionManager::instance(),
- &ProjectExplorer::SessionManager::startupProjectChanged,
+ QObject::connect(ProjectExplorer::ProjectManager::instance(),
+ &ProjectExplorer::ProjectManager::startupProjectChanged,
[action]() {
auto qmlProject = qobject_cast<QmlProject *>(
- ProjectExplorer::SessionManager::startupProject());
+ ProjectExplorer::ProjectManager::startupProject());
action->setEnabled(qmlProject != nullptr);
});
}
void onGenerateCmakeLists()
{
- FilePath rootDir = ProjectExplorer::SessionManager::startupProject()->projectDirectory();
+ FilePath rootDir = ProjectExplorer::ProjectManager::startupProject()->projectDirectory();
int projectDirErrors = isProjectCorrectlyFormed(rootDir);
if (projectDirErrors != NoError) {
@@ -246,7 +247,7 @@ const QString projectEnvironmentVariable(const QString &key)
{
QString value = {};
- auto *target = ProjectExplorer::SessionManager::startupProject()->activeTarget();
+ auto *target = ProjectExplorer::ProjectManager::startupProject()->activeTarget();
if (target && target->buildSystem()) {
auto buildSystem = qobject_cast<QmlProjectManager::QmlBuildSystem *>(target->buildSystem());
if (buildSystem) {
@@ -304,7 +305,7 @@ const char ADD_SUBDIR[] = "add_subdirectory(%1)\n";
void CmakeFileGenerator::generateMainCmake(const FilePath &rootDir)
{
//TODO startupProject() may be a terrible way to try to get "current project". It's not necessarily the same thing at all.
- QString projectName = ProjectExplorer::SessionManager::startupProject()->displayName();
+ QString projectName = ProjectExplorer::ProjectManager::startupProject()->displayName();
QString appName = projectName + "App";
QString fileSection = "";
@@ -523,7 +524,7 @@ bool CmakeFileGenerator::isDirBlacklisted(const FilePath &dir)
bool CmakeFileGenerator::includeFile(const FilePath &filePath)
{
if (m_checkFileIsInProject) {
- ProjectExplorer::Project *project = ProjectExplorer::SessionManager::startupProject();
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
if (!project->isKnownFile(filePath))
return false;
}
@@ -570,7 +571,7 @@ bool CmakeFileGenerator::generateMainCpp(const FilePath &dir)
bool envHeaderOk = true;
QString environment;
- auto *target = ProjectExplorer::SessionManager::startupProject()->activeTarget();
+ auto *target = ProjectExplorer::ProjectManager::startupProject()->activeTarget();
if (target && target->buildSystem()) {
auto buildSystem = qobject_cast<QmlProjectManager::QmlBuildSystem *>(target->buildSystem());
if (buildSystem) {
diff --git a/src/plugins/qmlprojectmanager/projectfilecontenttools.cpp b/src/plugins/qmlprojectmanager/projectfilecontenttools.cpp
index 8ae0efe3119..8cde4e2c222 100644
--- a/src/plugins/qmlprojectmanager/projectfilecontenttools.cpp
+++ b/src/plugins/qmlprojectmanager/projectfilecontenttools.cpp
@@ -18,7 +18,7 @@ QRegularExpression qdsVerRegexp(R"x(qdsVersion: "(.*)")x");
const Utils::FilePaths rootCmakeFiles(ProjectExplorer::Project *project)
{
if (!project)
- project = ProjectExplorer::SessionManager::startupProject();
+ project = ProjectExplorer::ProjectManager::startupProject();
if (!project)
return {};
return project->projectDirectory().dirEntries({QList<QString>({"CMakeLists.txt"}), QDir::Files});
diff --git a/src/plugins/qmlprojectmanager/projectfilecontenttools.h b/src/plugins/qmlprojectmanager/projectfilecontenttools.h
index 843912eb7c8..3c3fcb5847e 100644
--- a/src/plugins/qmlprojectmanager/projectfilecontenttools.h
+++ b/src/plugins/qmlprojectmanager/projectfilecontenttools.h
@@ -6,7 +6,7 @@
#include "qmlprojectmanager_global.h"
#include <projectexplorer/projectmanager.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <utils/fileutils.h>
diff --git a/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp b/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp
index 4ca9f9aa626..db0001ac70f 100644
--- a/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp
+++ b/src/plugins/qmlprojectmanager/qmlmultilanguageaspect.cpp
@@ -10,8 +10,8 @@
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorer.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/runcontrol.h>
-#include <projectexplorer/session.h>
#include <projectexplorer/target.h>
static bool isMultilanguagePresent()
@@ -114,7 +114,7 @@ void QmlMultiLanguageAspect::fromMap(const QVariantMap &map)
QmlMultiLanguageAspect *QmlMultiLanguageAspect::current()
{
- if (auto project = ProjectExplorer::SessionManager::startupProject())
+ if (auto project = ProjectExplorer::ProjectManager::startupProject())
return current(project);
return {};
}
diff --git a/src/plugins/qmlprojectmanager/qmlproject.cpp b/src/plugins/qmlprojectmanager/qmlproject.cpp
index c283ec73e31..f08b9c4bda7 100644
--- a/src/plugins/qmlprojectmanager/qmlproject.cpp
+++ b/src/plugins/qmlprojectmanager/qmlproject.cpp
@@ -23,7 +23,7 @@
#include <projectexplorer/kitinformation.h>
#include <projectexplorer/kitmanager.h>
#include <projectexplorer/projectexplorerconstants.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <qtsupport/baseqtversion.h>
@@ -97,7 +97,7 @@ QmlProject::QmlProject(const Utils::FilePath &fileName)
if (QmlProject::isQtDesignStudio()) {
if (allowOnlySingleProject()) {
EditorManager::closeAllDocuments();
- SessionManager::closeAllProjects();
+ ProjectManager::closeAllProjects();
}
m_openFileConnection
diff --git a/src/plugins/qmlprojectmanager/qmlprojectplugin.cpp b/src/plugins/qmlprojectmanager/qmlprojectplugin.cpp
index 3c156311b41..443d5478fc9 100644
--- a/src/plugins/qmlprojectmanager/qmlprojectplugin.cpp
+++ b/src/plugins/qmlprojectmanager/qmlprojectplugin.cpp
@@ -26,7 +26,7 @@
#include <projectexplorer/projectnodes.h>
#include <projectexplorer/projecttree.h>
#include <projectexplorer/runcontrol.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <qmljs/qmljsmodelmanagerinterface.h>
@@ -181,7 +181,7 @@ const Utils::FilePath findQmlProjectUpwards(const Utils::FilePath &folder)
static bool findAndOpenProject(const Utils::FilePath &filePath)
{
ProjectExplorer::Project *project
- = ProjectExplorer::SessionManager::projectForFile(filePath);
+ = ProjectExplorer::ProjectManager::projectForFile(filePath);
if (project) {
if (project->projectFilePath().suffix() == "qmlproject") {
@@ -437,7 +437,7 @@ void QmlProjectPlugin::updateQmlLandingPageProjectInfo(const Utils::FilePath &pr
Utils::FilePath QmlProjectPlugin::projectFilePath()
{
- auto project = ProjectExplorer::SessionManager::startupProject();
+ auto project = ProjectExplorer::ProjectManager::startupProject();
const QmlProjectManager::QmlProject *qmlProject = qobject_cast<const QmlProjectManager::QmlProject*>(project);
if (qmlProject) {
return qmlProject->projectFilePath();
diff --git a/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp b/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp
index 8e8527b32c0..e0b1e6a9b15 100644
--- a/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp
+++ b/src/plugins/qmlprojectmanager/qmlprojectrunconfiguration.cpp
@@ -21,7 +21,7 @@
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/runconfigurationaspects.h>
#include <projectexplorer/runcontrol.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <qtsupport/qtkitinformation.h>
@@ -274,7 +274,7 @@ void QmlProjectRunConfiguration::createQtVersionAspect()
if (!newTarget)
newTarget = project->addTargetForKit(kits.first());
- SessionManager::setActiveTarget(project, newTarget, SetActive::Cascade);
+ project->setActiveTarget(newTarget, SetActive::Cascade);
/* Reset the aspect. We changed the target and this aspect should not change. */
m_qtversionAspect->blockSignals(true);
diff --git a/src/plugins/qnx/CMakeLists.txt b/src/plugins/qnx/CMakeLists.txt
index 66dd7058770..c36c0db077d 100644
--- a/src/plugins/qnx/CMakeLists.txt
+++ b/src/plugins/qnx/CMakeLists.txt
@@ -10,10 +10,7 @@ add_qtc_plugin(Qnx
qnxdebugsupport.cpp qnxdebugsupport.h
qnxdeployqtlibrariesdialog.cpp qnxdeployqtlibrariesdialog.h
qnxdevice.cpp qnxdevice.h
- qnxdeviceprocesslist.cpp qnxdeviceprocesslist.h
- qnxdeviceprocesssignaloperation.cpp qnxdeviceprocesssignaloperation.h
qnxdevicetester.cpp qnxdevicetester.h
- qnxdevicewizard.cpp qnxdevicewizard.h
qnxplugin.cpp
qnxqtversion.cpp qnxqtversion.h
qnxrunconfiguration.cpp qnxrunconfiguration.h
diff --git a/src/plugins/qnx/qnx.qbs b/src/plugins/qnx/qnx.qbs
index 0389f542a65..ee1752f816f 100644
--- a/src/plugins/qnx/qnx.qbs
+++ b/src/plugins/qnx/qnx.qbs
@@ -28,12 +28,6 @@ QtcPlugin {
"qnxdebugsupport.h",
"qnxdevice.cpp",
"qnxdevice.h",
- "qnxdevicewizard.cpp",
- "qnxdevicewizard.h",
- "qnxdeviceprocesslist.cpp",
- "qnxdeviceprocesslist.h",
- "qnxdeviceprocesssignaloperation.cpp",
- "qnxdeviceprocesssignaloperation.h",
"qnxdevicetester.cpp",
"qnxdevicetester.h",
"qnxconfigurationmanager.cpp",
diff --git a/src/plugins/qnx/qnxconfiguration.cpp b/src/plugins/qnx/qnxconfiguration.cpp
index c1b0e498f0c..0ca024248a8 100644
--- a/src/plugins/qnx/qnxconfiguration.cpp
+++ b/src/plugins/qnx/qnxconfiguration.cpp
@@ -8,10 +8,12 @@
#include "qnxtoolchain.h"
#include "qnxtr.h"
-#include "debugger/debuggeritem.h"
-
#include <coreplugin/icore.h>
+#include <debugger/debuggeritem.h>
+#include <debugger/debuggeritemmanager.h>
+#include <debugger/debuggerkitinformation.h>
+
#include <projectexplorer/toolchainmanager.h>
#include <projectexplorer/toolchain.h>
#include <projectexplorer/kit.h>
@@ -23,10 +25,6 @@
#include <qmakeprojectmanager/qmakeprojectmanagerconstants.h>
-#include <debugger/debuggeritem.h>
-#include <debugger/debuggeritemmanager.h>
-#include <debugger/debuggerkitinformation.h>
-
#include <utils/algorithm.h>
#include <QDebug>
diff --git a/src/plugins/qnx/qnxdebugsupport.cpp b/src/plugins/qnx/qnxdebugsupport.cpp
index cb3932daab5..d6ee8618980 100644
--- a/src/plugins/qnx/qnxdebugsupport.cpp
+++ b/src/plugins/qnx/qnxdebugsupport.cpp
@@ -21,8 +21,8 @@
#include <projectexplorer/kitchooser.h>
#include <projectexplorer/kitinformation.h>
#include <projectexplorer/projectexplorer.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/runconfigurationaspects.h>
-#include <projectexplorer/session.h>
#include <projectexplorer/target.h>
#include <projectexplorer/toolchain.h>
@@ -223,7 +223,7 @@ void showAttachToProcessDialog()
return;
// FIXME: That should be somehow related to the selected kit.
- auto runConfig = SessionManager::startupRunConfiguration();
+ auto runConfig = ProjectManager::startupRunConfiguration();
const int pid = dlg.currentProcess().processId;
// QString projectSourceDirectory = dlg.projectSource();
diff --git a/src/plugins/qnx/qnxdevice.cpp b/src/plugins/qnx/qnxdevice.cpp
index 0c7c99a5c39..e574a8eed78 100644
--- a/src/plugins/qnx/qnxdevice.cpp
+++ b/src/plugins/qnx/qnxdevice.cpp
@@ -6,18 +6,21 @@
#include "qnxconstants.h"
#include "qnxdeployqtlibrariesdialog.h"
#include "qnxdevicetester.h"
-#include "qnxdeviceprocesslist.h"
-#include "qnxdeviceprocesssignaloperation.h"
-#include "qnxdevicewizard.h"
#include "qnxtr.h"
-#include <remotelinux/sshprocessinterface.h>
+#include <coreplugin/icore.h>
+
+#include <projectexplorer/devicesupport/sshparameters.h>
+
+#include <remotelinux/genericlinuxdeviceconfigurationwizardpages.h>
+#include <remotelinux/remotelinuxsignaloperation.h>
+#include <remotelinux/linuxdevice.h>
#include <utils/port.h>
+#include <utils/portlist.h>
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
-
-#include <QRegularExpression>
+#include <utils/wizard.h>
using namespace ProjectExplorer;
using namespace RemoteLinux;
@@ -25,149 +28,101 @@ using namespace Utils;
namespace Qnx::Internal {
-class QnxProcessImpl final : public SshProcessInterface
-{
-public:
- QnxProcessImpl(const LinuxDevice *linuxDevice);
- ~QnxProcessImpl() { killIfRunning(); }
-
-private:
- QString fullCommandLine(const CommandLine &commandLine) const final;
- void handleSendControlSignal(Utils::ControlSignal controlSignal) final;
-
- const QString m_pidFile;
-};
-
-static std::atomic_int s_pidFileCounter = 1;
-
-QnxProcessImpl::QnxProcessImpl(const LinuxDevice *linuxDevice)
- : SshProcessInterface(linuxDevice)
- , m_pidFile(QString("%1/qtc.%2.pid").arg(Constants::QNX_TMP_DIR).arg(s_pidFileCounter.fetch_add(1)))
+static QString signalProcessByNameQnxCommandLine(const QString &filePath, int sig)
{
+ QString executable = filePath;
+ return QString::fromLatin1("for PID in $(ps -f -o pid,comm | grep %1 | awk '/%1/ {print $1}'); "
+ "do "
+ "kill -%2 $PID; "
+ "done").arg(executable.replace(QLatin1String("/"), QLatin1String("\\/"))).arg(sig);
}
-QString QnxProcessImpl::fullCommandLine(const CommandLine &commandLine) const
+class QnxDeviceProcessSignalOperation : public RemoteLinuxSignalOperation
{
- QStringList args = ProcessArgs::splitArgs(commandLine.arguments());
- args.prepend(commandLine.executable().toString());
- const QString cmd = ProcessArgs::createUnixArgs(args).toString();
-
- QString fullCommandLine =
- "test -f /etc/profile && . /etc/profile ; "
- "test -f $HOME/profile && . $HOME/profile ; ";
-
- if (!m_setup.m_workingDirectory.isEmpty())
- fullCommandLine += QString::fromLatin1("cd %1 ; ").arg(
- ProcessArgs::quoteArg(m_setup.m_workingDirectory.toString()));
-
- const Environment env = m_setup.m_environment;
- for (auto it = env.constBegin(); it != env.constEnd(); ++it) {
- fullCommandLine += QString::fromLatin1("%1='%2' ")
- .arg(env.key(it)).arg(env.expandedValueForKey(env.key(it)));
+public:
+ explicit QnxDeviceProcessSignalOperation(const IDeviceConstPtr &device)
+ : RemoteLinuxSignalOperation(device)
+ {}
+
+ QString killProcessByNameCommandLine(const QString &filePath) const override
+ {
+ return QString::fromLatin1("%1; %2").arg(signalProcessByNameQnxCommandLine(filePath, 15),
+ signalProcessByNameQnxCommandLine(filePath, 9));
}
- fullCommandLine += QString::fromLatin1("%1 & echo $! > %2").arg(cmd).arg(m_pidFile);
-
- return fullCommandLine;
-}
-
-void QnxProcessImpl::handleSendControlSignal(Utils::ControlSignal controlSignal)
-{
- QTC_ASSERT(controlSignal != ControlSignal::KickOff, return);
- const QString args = QString::fromLatin1("-%1 `cat %2`")
- .arg(controlSignalToInt(controlSignal)).arg(m_pidFile);
- CommandLine command = { "kill", args, CommandLine::Raw };
- // Note: This blocking call takes up to 2 ms for local remote.
- runInShell(command);
-}
-
-const char QnxVersionKey[] = "QnxVersion";
-
-QnxDevice::QnxDevice()
-{
- setDisplayType(Tr::tr("QNX"));
- setDefaultDisplayName(Tr::tr("QNX Device"));
- setOsType(OsTypeOtherUnix);
-
- addDeviceAction({Tr::tr("Deploy Qt libraries..."), [](const IDevice::Ptr &device, QWidget *parent) {
- QnxDeployQtLibrariesDialog dialog(device, parent);
- dialog.exec();
- }});
-}
+ QString interruptProcessByNameCommandLine(const QString &filePath) const override
+ {
+ return signalProcessByNameQnxCommandLine(filePath, 2);
+ }
+};
-int QnxDevice::qnxVersion() const
+class QnxDevice final : public LinuxDevice
{
- if (m_versionNumber == 0)
- updateVersionNumber();
+public:
+ QnxDevice()
+ {
+ setDisplayType(Tr::tr("QNX"));
+ setDefaultDisplayName(Tr::tr("QNX Device"));
+ setOsType(OsTypeOtherUnix);
+ setupId(IDevice::ManuallyAdded);
+ setType(Constants::QNX_QNX_OS_TYPE);
+ setMachineType(IDevice::Hardware);
+ SshParameters sshParams;
+ sshParams.timeout = 10;
+ setSshParameters(sshParams);
+ setFreePorts(PortList::fromString("10000-10100"));
+
+ addDeviceAction({Tr::tr("Deploy Qt libraries..."), [](const IDevice::Ptr &device, QWidget *parent) {
+ QnxDeployQtLibrariesDialog dialog(device, parent);
+ dialog.exec();
+ }});
+ }
- return m_versionNumber;
-}
+ PortsGatheringMethod portsGatheringMethod() const final
+ {
+ return {
+ [this](QAbstractSocket::NetworkLayerProtocol) {
+ return CommandLine(filePath("netstat"), {"-na"});
+ },
+ &Port::parseFromNetstatOutput
+ };
+ }
-void QnxDevice::updateVersionNumber() const
-{
- QtcProcess versionNumberProcess;
-
- versionNumberProcess.setCommand({filePath("uname"), {"-r"}});
- versionNumberProcess.runBlocking(EventLoopMode::On);
-
- QByteArray output = versionNumberProcess.readAllRawStandardOutput();
- QString versionMessage = QString::fromLatin1(output);
- const QRegularExpression versionNumberRegExp("(\\d+)\\.(\\d+)\\.(\\d+)");
- const QRegularExpressionMatch match = versionNumberRegExp.match(versionMessage);
- if (match.hasMatch()) {
- int major = match.captured(1).toInt();
- int minor = match.captured(2).toInt();
- int patch = match.captured(3).toInt();
- m_versionNumber = (major << 16)|(minor<<8)|(patch);
+ DeviceProcessSignalOperation::Ptr signalOperation() const final
+ {
+ return DeviceProcessSignalOperation::Ptr(new QnxDeviceProcessSignalOperation(sharedFromThis()));
}
-}
-void QnxDevice::fromMap(const QVariantMap &map)
-{
- m_versionNumber = map.value(QLatin1String(QnxVersionKey), 0).toInt();
- LinuxDevice::fromMap(map);
-}
+ DeviceTester *createDeviceTester() const final { return new QnxDeviceTester; }
+};
-QVariantMap QnxDevice::toMap() const
+class QnxDeviceWizard : public Wizard
{
- QVariantMap map(LinuxDevice::toMap());
- map.insert(QLatin1String(QnxVersionKey), m_versionNumber);
- return map;
-}
+public:
+ QnxDeviceWizard() : Wizard(Core::ICore::dialogParent())
+ {
+ setWindowTitle(Tr::tr("New QNX Device Configuration Setup"));
-PortsGatheringMethod QnxDevice::portsGatheringMethod() const
-{
- return {
- // TODO: The command is probably needlessly complicated because the parsing method
- // used to be fixed. These two can now be matched to each other.
- [this](QAbstractSocket::NetworkLayerProtocol protocol) -> CommandLine {
- Q_UNUSED(protocol)
- return {filePath("netstat"), {"-na"}};
- },
-
- &Port::parseFromNetstatOutput
- };
-}
+ addPage(&m_setupPage);
+ addPage(&m_keyDeploymentPage);
+ addPage(&m_finalPage);
+ m_finalPage.setCommitPage(true);
-DeviceProcessList *QnxDevice::createProcessListModel(QObject *parent) const
-{
- return new QnxDeviceProcessList(sharedFromThis(), parent);
-}
+ m_device.reset(new QnxDevice);
-DeviceTester *QnxDevice::createDeviceTester() const
-{
- return new QnxDeviceTester;
-}
+ m_setupPage.setDevice(m_device);
+ m_keyDeploymentPage.setDevice(m_device);
+ }
-Utils::ProcessInterface *QnxDevice::createProcessInterface() const
-{
- return new QnxProcessImpl(this);
-}
+ IDevice::Ptr device() const { return m_device; }
-DeviceProcessSignalOperation::Ptr QnxDevice::signalOperation() const
-{
- return DeviceProcessSignalOperation::Ptr(new QnxDeviceProcessSignalOperation(sharedFromThis()));
-}
+private:
+ GenericLinuxDeviceConfigurationWizardSetupPage m_setupPage;
+ GenericLinuxDeviceConfigurationWizardKeyDeploymentPage m_keyDeploymentPage;
+ GenericLinuxDeviceConfigurationWizardFinalPage m_finalPage;
+
+ LinuxDevice::Ptr m_device;
+};
// Factory
@@ -176,7 +131,8 @@ QnxDeviceFactory::QnxDeviceFactory() : IDeviceFactory(Constants::QNX_QNX_OS_TYPE
setDisplayName(Tr::tr("QNX Device"));
setCombinedIcon(":/qnx/images/qnxdevicesmall.png",
":/qnx/images/qnxdevice.png");
- setConstructionFunction(&QnxDevice::create);
+ setQuickCreationAllowed(true);
+ setConstructionFunction([] { return IDevice::Ptr(new QnxDevice); });
setCreator([] {
QnxDeviceWizard wizard;
if (wizard.exec() != QDialog::Accepted)
diff --git a/src/plugins/qnx/qnxdevice.h b/src/plugins/qnx/qnxdevice.h
index f4efa3e1f25..c4b484f478c 100644
--- a/src/plugins/qnx/qnxdevice.h
+++ b/src/plugins/qnx/qnxdevice.h
@@ -3,42 +3,10 @@
#pragma once
-#include <remotelinux/linuxdevice.h>
+#include <projectexplorer/devicesupport/idevicefactory.h>
namespace Qnx::Internal {
-class QnxDevice final : public RemoteLinux::LinuxDevice
-{
-public:
- using Ptr = QSharedPointer<QnxDevice>;
- using ConstPtr = QSharedPointer<const QnxDevice>;
-
- static Ptr create() { return Ptr(new QnxDevice); }
-
- ProjectExplorer::PortsGatheringMethod portsGatheringMethod() const override;
- ProjectExplorer::DeviceProcessList *createProcessListModel(QObject *parent) const override;
- ProjectExplorer::DeviceProcessSignalOperation::Ptr signalOperation() const override;
-
- ProjectExplorer::DeviceTester *createDeviceTester() const override;
- Utils::ProcessInterface *createProcessInterface() const override;
-
- int qnxVersion() const;
-
-protected:
- void fromMap(const QVariantMap &map) final;
- QVariantMap toMap() const final;
-
- QString interruptProcessByNameCommandLine(const QString &filePath) const;
- QString killProcessByNameCommandLine(const QString &filePath) const;
-
-private:
- QnxDevice();
-
- void updateVersionNumber() const;
-
- mutable int m_versionNumber = 0;
-};
-
class QnxDeviceFactory final : public ProjectExplorer::IDeviceFactory
{
public:
diff --git a/src/plugins/qnx/qnxdeviceprocesslist.cpp b/src/plugins/qnx/qnxdeviceprocesslist.cpp
deleted file mode 100644
index 39735329934..00000000000
--- a/src/plugins/qnx/qnxdeviceprocesslist.cpp
+++ /dev/null
@@ -1,59 +0,0 @@
-// Copyright (C) 2016 BlackBerry Limited. All rights reserved.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#include "qnxdeviceprocesslist.h"
-
-#include <projectexplorer/devicesupport/idevice.h>
-#include <utils/algorithm.h>
-#include <utils/fileutils.h>
-#include <utils/processinfo.h>
-
-#include <QRegularExpression>
-#include <QStringList>
-
-using namespace Utils;
-
-namespace Qnx::Internal {
-
-QnxDeviceProcessList::QnxDeviceProcessList(
- const ProjectExplorer::IDevice::ConstPtr &device, QObject *parent)
- : ProjectExplorer::SshDeviceProcessList(device, parent)
-{
-}
-
-QString QnxDeviceProcessList::listProcessesCommandLine() const
-{
- return QLatin1String("pidin -F '%a %A {/%n}'");
-}
-
-QList<ProcessInfo> QnxDeviceProcessList::buildProcessList(const QString &listProcessesReply) const
-{
- QList<ProcessInfo> processes;
- QStringList lines = listProcessesReply.split(QLatin1Char('\n'));
- if (lines.isEmpty())
- return processes;
-
- lines.pop_front(); // drop headers
- const QRegularExpression re("\\s*(\\d+)\\s+(.*){(.*)}");
-
- for (const QString &line : std::as_const(lines)) {
- const QRegularExpressionMatch match = re.match(line);
- if (match.hasMatch()) {
- const QStringList captures = match.capturedTexts();
- if (captures.size() == 4) {
- const int pid = captures[1].toInt();
- const QString args = captures[2];
- const QString exe = captures[3];
- ProcessInfo deviceProcess;
- deviceProcess.processId = pid;
- deviceProcess.executable = exe.trimmed();
- deviceProcess.commandLine = args.trimmed();
- processes.append(deviceProcess);
- }
- }
- }
-
- return Utils::sorted(std::move(processes));
-}
-
-} // Qnx::Internal
diff --git a/src/plugins/qnx/qnxdeviceprocesslist.h b/src/plugins/qnx/qnxdeviceprocesslist.h
deleted file mode 100644
index 0e71ae7ab01..00000000000
--- a/src/plugins/qnx/qnxdeviceprocesslist.h
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (C) 2016 BlackBerry Limited. All rights reserved.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#pragma once
-
-#include <projectexplorer/devicesupport/sshdeviceprocesslist.h>
-
-namespace Qnx::Internal {
-
-class QnxDeviceProcessList : public ProjectExplorer::SshDeviceProcessList
-{
-public:
- explicit QnxDeviceProcessList(
- const ProjectExplorer::IDeviceConstPtr &device, QObject *parent = nullptr);
-
-private:
- QString listProcessesCommandLine() const override;
- QList<Utils::ProcessInfo> buildProcessList(const QString &listProcessesReply) const override;
-};
-
-} // Qnx::Internal
diff --git a/src/plugins/qnx/qnxdeviceprocesssignaloperation.cpp b/src/plugins/qnx/qnxdeviceprocesssignaloperation.cpp
deleted file mode 100644
index 56df01d8a9e..00000000000
--- a/src/plugins/qnx/qnxdeviceprocesssignaloperation.cpp
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright (C) 2016 BlackBerry Limited. All rights reserved.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#include "qnxdeviceprocesssignaloperation.h"
-
-namespace Qnx::Internal {
-
-QnxDeviceProcessSignalOperation::QnxDeviceProcessSignalOperation(
- const ProjectExplorer::IDeviceConstPtr &device)
- : RemoteLinux::RemoteLinuxSignalOperation(device)
-{
-}
-
-static QString signalProcessByNameQnxCommandLine(const QString &filePath, int sig)
-{
- QString executable = filePath;
- return QString::fromLatin1("for PID in $(ps -f -o pid,comm | grep %1 | awk '/%1/ {print $1}'); "
- "do "
- "kill -%2 $PID; "
- "done").arg(executable.replace(QLatin1String("/"), QLatin1String("\\/"))).arg(sig);
-}
-
-QString QnxDeviceProcessSignalOperation::killProcessByNameCommandLine(
- const QString &filePath) const
-{
- return QString::fromLatin1("%1; %2").arg(signalProcessByNameQnxCommandLine(filePath, 15),
- signalProcessByNameQnxCommandLine(filePath, 9));
-}
-
-QString QnxDeviceProcessSignalOperation::interruptProcessByNameCommandLine(
- const QString &filePath) const
-{
- return signalProcessByNameQnxCommandLine(filePath, 2);
-}
-
-} // Qnx::Internal
diff --git a/src/plugins/qnx/qnxdeviceprocesssignaloperation.h b/src/plugins/qnx/qnxdeviceprocesssignaloperation.h
deleted file mode 100644
index 271aea6a4e0..00000000000
--- a/src/plugins/qnx/qnxdeviceprocesssignaloperation.h
+++ /dev/null
@@ -1,22 +0,0 @@
-// Copyright (C) 2016 BlackBerry Limited. All rights reserved.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#pragma once
-
-#include <remotelinux/remotelinuxsignaloperation.h>
-
-namespace Qnx::Internal {
-
-class QnxDeviceProcessSignalOperation : public RemoteLinux::RemoteLinuxSignalOperation
-{
-protected:
- explicit QnxDeviceProcessSignalOperation(const ProjectExplorer::IDeviceConstPtr &device);
-
-private:
- QString killProcessByNameCommandLine(const QString &filePath) const override;
- QString interruptProcessByNameCommandLine(const QString &filePath) const override;
-
- friend class QnxDevice;
-};
-
-} // Qnx::Internal
diff --git a/src/plugins/qnx/qnxdevicetester.cpp b/src/plugins/qnx/qnxdevicetester.cpp
index fa60bfe69b6..1fd755138de 100644
--- a/src/plugins/qnx/qnxdevicetester.cpp
+++ b/src/plugins/qnx/qnxdevicetester.cpp
@@ -4,7 +4,6 @@
#include "qnxdevicetester.h"
#include "qnxconstants.h"
-#include "qnxdevice.h"
#include "qnxtr.h"
#include <utils/qtcassert.h>
@@ -15,55 +14,41 @@ using namespace Utils;
namespace Qnx::Internal {
QnxDeviceTester::QnxDeviceTester(QObject *parent)
- : ProjectExplorer::DeviceTester(parent)
-{
- m_genericTester = new RemoteLinux::GenericLinuxDeviceTester(this);
- connect(m_genericTester, &DeviceTester::progressMessage,
- this, &DeviceTester::progressMessage);
- connect(m_genericTester, &DeviceTester::errorMessage,
- this, &DeviceTester::errorMessage);
- connect(m_genericTester, &DeviceTester::finished,
- this, &QnxDeviceTester::finished);
-}
+ : RemoteLinux::GenericLinuxDeviceTester(parent)
+{}
-static QStringList versionSpecificCommandsToTest(int versionNumber)
+void QnxDeviceTester::testDevice(const ProjectExplorer::IDevice::Ptr &device)
{
- if (versionNumber > 0x060500)
- return {"slog2info"};
- return {};
-}
+ static const QStringList commandsToTest {
+ "awk",
+ "cat",
+ "cut",
+ "df",
+ "grep",
+ "kill",
+ "netstat",
+ "mkdir",
+ "print",
+ "printf",
+ "pidin",
+ "read",
+ "rm",
+ "sed",
+ "sleep",
+ "tail",
+ "uname",
+ "slog2info"
+ };
-void QnxDeviceTester::testDevice(const ProjectExplorer::IDevice::Ptr &deviceConfiguration)
-{
- static const QStringList s_commandsToTest = {"awk",
- "cat",
- "cut",
- "df",
- "grep",
- "kill",
- "netstat",
- "mkdir",
- "print",
- "printf",
- "pidin",
- "read",
- "rm",
- "sed",
- "sleep",
- "tail",
- "uname"};
- m_deviceConfiguration = deviceConfiguration;
- QnxDevice::ConstPtr qnxDevice = m_deviceConfiguration.dynamicCast<const QnxDevice>();
- m_genericTester->setExtraCommandsToTest(
- s_commandsToTest + versionSpecificCommandsToTest(qnxDevice->qnxVersion()));
+ setExtraCommandsToTest(commandsToTest);
using namespace Tasking;
- auto setupHandler = [this](QtcProcess &process) {
+ auto setupHandler = [device, this](QtcProcess &process) {
emit progressMessage(Tr::tr("Checking that files can be created in %1...")
.arg(Constants::QNX_TMP_DIR));
const QString pidFile = QString("%1/qtc_xxxx.pid").arg(Constants::QNX_TMP_DIR);
- const CommandLine cmd(m_deviceConfiguration->filePath("/bin/sh"),
+ const CommandLine cmd(device->filePath("/bin/sh"),
{"-c", QLatin1String("rm %1 > /dev/null 2>&1; echo ABC > %1 && rm %1").arg(pidFile)});
process.setCommand(cmd);
};
@@ -77,14 +62,9 @@ void QnxDeviceTester::testDevice(const ProjectExplorer::IDevice::Ptr &deviceConf
: Tr::tr("Files cannot be created in %1.").arg(Constants::QNX_TMP_DIR);
emit errorMessage(message + '\n');
};
- m_genericTester->setExtraTests({Process(setupHandler, doneHandler, errorHandler)});
-
- m_genericTester->testDevice(deviceConfiguration);
-}
+ setExtraTests({Process(setupHandler, doneHandler, errorHandler)});
-void QnxDeviceTester::stopTest()
-{
- m_genericTester->stopTest();
+ RemoteLinux::GenericLinuxDeviceTester::testDevice(device);
}
} // Qnx::Internal
diff --git a/src/plugins/qnx/qnxdevicetester.h b/src/plugins/qnx/qnxdevicetester.h
index 96f31f38416..f8fc4be8821 100644
--- a/src/plugins/qnx/qnxdevicetester.h
+++ b/src/plugins/qnx/qnxdevicetester.h
@@ -5,23 +5,14 @@
#include <remotelinux/linuxdevicetester.h>
-namespace Qnx {
-namespace Internal {
+namespace Qnx::Internal {
-class QnxDeviceTester : public ProjectExplorer::DeviceTester
+class QnxDeviceTester : public RemoteLinux::GenericLinuxDeviceTester
{
- Q_OBJECT
-
public:
explicit QnxDeviceTester(QObject *parent = nullptr);
- void testDevice(const ProjectExplorer::IDevice::Ptr &deviceConfiguration) override;
- void stopTest() override;
-
-private:
- RemoteLinux::GenericLinuxDeviceTester *m_genericTester = nullptr;
- ProjectExplorer::IDevice::ConstPtr m_deviceConfiguration;
+ void testDevice(const ProjectExplorer::IDevice::Ptr &device) override;
};
-} // namespace Internal
-} // namespace Qnx
+} // Qnx::Internal
diff --git a/src/plugins/qnx/qnxdevicewizard.cpp b/src/plugins/qnx/qnxdevicewizard.cpp
deleted file mode 100644
index 1e7d1cfcdd6..00000000000
--- a/src/plugins/qnx/qnxdevicewizard.cpp
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (C) 2016 BlackBerry Limited. All rights reserved.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#include "qnxdevicewizard.h"
-
-#include "qnxconstants.h"
-#include "qnxtr.h"
-
-#include <projectexplorer/devicesupport/sshparameters.h>
-#include <remotelinux/genericlinuxdeviceconfigurationwizardpages.h>
-#include <utils/portlist.h>
-
-using namespace ProjectExplorer;
-
-namespace Qnx::Internal {
-
-QnxDeviceWizard::QnxDeviceWizard(QWidget *parent) :
- Utils::Wizard(parent)
-{
- setWindowTitle(Tr::tr("New QNX Device Configuration Setup"));
-
- m_setupPage = new RemoteLinux::GenericLinuxDeviceConfigurationWizardSetupPage(this);
- m_keyDeploymentPage
- = new RemoteLinux::GenericLinuxDeviceConfigurationWizardKeyDeploymentPage(this);
- m_finalPage = new RemoteLinux::GenericLinuxDeviceConfigurationWizardFinalPage(this);
-
- setPage(SetupPageId, m_setupPage);
- setPage(KeyDeploymenPageId, m_keyDeploymentPage);
- setPage(FinalPageId, m_finalPage);
- m_finalPage->setCommitPage(true);
- SshParameters sshParams;
- sshParams.timeout = 10;
- m_device = QnxDevice::create();
- m_device->setupId(IDevice::ManuallyAdded);
- m_device->setType(Constants::QNX_QNX_OS_TYPE);
- m_device->setMachineType(IDevice::Hardware);
- m_device->setSshParameters(sshParams);
- m_device->setFreePorts(Utils::PortList::fromString(QLatin1String("10000-10100")));
- m_setupPage->setDevice(m_device);
- m_keyDeploymentPage->setDevice(m_device);
-}
-
-IDevice::Ptr QnxDeviceWizard::device()
-{
- return m_device;
-}
-
-} // Qnx::Internal
diff --git a/src/plugins/qnx/qnxdevicewizard.h b/src/plugins/qnx/qnxdevicewizard.h
deleted file mode 100644
index ce30658ce11..00000000000
--- a/src/plugins/qnx/qnxdevicewizard.h
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (C) 2016 BlackBerry Limited. All rights reserved.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#pragma once
-
-#include "qnxdevice.h"
-
-#include <utils/wizard.h>
-
-namespace RemoteLinux {
-class GenericLinuxDeviceConfigurationWizardSetupPage;
-class GenericLinuxDeviceConfigurationWizardKeyDeploymentPage;
-class GenericLinuxDeviceConfigurationWizardFinalPage;
-}
-
-namespace Qnx::Internal {
-
-class QnxDeviceWizard : public Utils::Wizard
-{
-public:
- explicit QnxDeviceWizard(QWidget *parent = nullptr);
-
- ProjectExplorer::IDevice::Ptr device();
-
-private:
- enum PageId {
- SetupPageId,
- KeyDeploymenPageId,
- FinalPageId
- };
-
- RemoteLinux::GenericLinuxDeviceConfigurationWizardSetupPage *m_setupPage;
- RemoteLinux::GenericLinuxDeviceConfigurationWizardKeyDeploymentPage *m_keyDeploymentPage;
- RemoteLinux::GenericLinuxDeviceConfigurationWizardFinalPage *m_finalPage;
- QnxDevice::Ptr m_device;
-};
-
-} // Qnx::Internal
diff --git a/src/plugins/qnx/qnxplugin.cpp b/src/plugins/qnx/qnxplugin.cpp
index c6664b9f600..5796668f8ae 100644
--- a/src/plugins/qnx/qnxplugin.cpp
+++ b/src/plugins/qnx/qnxplugin.cpp
@@ -46,7 +46,7 @@ namespace Qnx::Internal {
class QnxUploadStep : public RemoteLinux::GenericDirectUploadStep
{
public:
- QnxUploadStep(BuildStepList *bsl, Utils::Id id) : GenericDirectUploadStep(bsl, id, false) {}
+ QnxUploadStep(BuildStepList *bsl, Utils::Id id) : GenericDirectUploadStep(bsl, id) {}
static Utils::Id stepId() { return "Qnx.DirectUploadStep"; }
};
diff --git a/src/plugins/qnx/slog2inforunner.cpp b/src/plugins/qnx/slog2inforunner.cpp
index 58509782c5b..e350c75f6ec 100644
--- a/src/plugins/qnx/slog2inforunner.cpp
+++ b/src/plugins/qnx/slog2inforunner.cpp
@@ -3,9 +3,9 @@
#include "slog2inforunner.h"
-#include "qnxdevice.h"
#include "qnxtr.h"
+#include <projectexplorer/devicesupport/idevice.h>
#include <projectexplorer/runconfigurationaspects.h>
#include <utils/qtcassert.h>
@@ -41,11 +41,8 @@ void Slog2InfoRunner::start()
m_found = true;
};
const auto testErrorHandler = [this](const QtcProcess &) {
- QnxDevice::ConstPtr qnxDevice = device().dynamicCast<const QnxDevice>();
- if (qnxDevice && qnxDevice->qnxVersion() > 0x060500) {
- appendMessage(Tr::tr("Warning: \"slog2info\" is not found on the device, "
- "debug output not available."), ErrorMessageFormat);
- }
+ appendMessage(Tr::tr("Warning: \"slog2info\" is not found on the device, "
+ "debug output not available."), ErrorMessageFormat);
};
const auto launchTimeStartHandler = [this](QtcProcess &process) {
diff --git a/src/plugins/qtsupport/CMakeLists.txt b/src/plugins/qtsupport/CMakeLists.txt
index 30fe2fb32bf..5d69a7fc38a 100644
--- a/src/plugins/qtsupport/CMakeLists.txt
+++ b/src/plugins/qtsupport/CMakeLists.txt
@@ -8,6 +8,8 @@ add_qtc_plugin(QtSupport
codegensettings.cpp codegensettings.h
codegensettingspage.cpp codegensettingspage.h
exampleslistmodel.cpp exampleslistmodel.h
+ examplesparser.cpp
+ examplesparser.h
externaleditors.cpp externaleditors.h
gettingstartedwelcomepage.cpp gettingstartedwelcomepage.h
profilereader.cpp profilereader.h
diff --git a/src/plugins/qtsupport/baseqtversion.cpp b/src/plugins/qtsupport/baseqtversion.cpp
index 6b1d64600f4..446f7d576a1 100644
--- a/src/plugins/qtsupport/baseqtversion.cpp
+++ b/src/plugins/qtsupport/baseqtversion.cpp
@@ -21,7 +21,8 @@
#include <projectexplorer/headerpath.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorer.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
#include <projectexplorer/toolchain.h>
#include <projectexplorer/toolchainmanager.h>
@@ -1542,10 +1543,10 @@ void QtVersion::populateQmlFileFinder(FileInProjectFinder *finder, const Target
// ... else try the session manager's global startup project ...
if (!startupProject)
- startupProject = SessionManager::startupProject();
+ startupProject = ProjectManager::startupProject();
// ... and if that is null, use the first project available.
- const QList<Project *> projects = SessionManager::projects();
+ const QList<Project *> projects = ProjectManager::projects();
QTC_CHECK(projects.isEmpty() || startupProject);
FilePath projectDirectory;
diff --git a/src/plugins/qtsupport/exampleslistmodel.cpp b/src/plugins/qtsupport/exampleslistmodel.cpp
index 26af348ca76..c567f992e41 100644
--- a/src/plugins/qtsupport/exampleslistmodel.cpp
+++ b/src/plugins/qtsupport/exampleslistmodel.cpp
@@ -3,6 +3,7 @@
#include "exampleslistmodel.h"
+#include "examplesparser.h"
#include "qtsupporttr.h"
#include <QBuffer>
@@ -288,50 +289,24 @@ ExamplesViewController::ExamplesViewController(ExampleSetModel *exampleSetModel,
updateExamples();
}
-static QString fixStringForTags(const QString &string)
-{
- QString returnString = string;
- returnString.remove(QLatin1String("<i>"));
- returnString.remove(QLatin1String("</i>"));
- returnString.remove(QLatin1String("<tt>"));
- returnString.remove(QLatin1String("</tt>"));
- return returnString;
-}
-
-static QStringList trimStringList(const QStringList &stringlist)
-{
- return Utils::transform(stringlist, [](const QString &str) { return str.trimmed(); });
-}
-
-static QString relativeOrInstallPath(const QString &path, const QString &manifestPath,
- const QString &installPath)
-{
- const QChar slash = QLatin1Char('/');
- const QString relativeResolvedPath = manifestPath + slash + path;
- const QString installResolvedPath = installPath + slash + path;
- if (QFile::exists(relativeResolvedPath))
- return relativeResolvedPath;
- if (QFile::exists(installResolvedPath))
- return installResolvedPath;
- // doesn't exist, just return relative
- return relativeResolvedPath;
-}
-
static bool isValidExampleOrDemo(ExampleItem *item)
{
QTC_ASSERT(item, return false);
+ if (item->type == Tutorial)
+ return true;
static QString invalidPrefix = QLatin1String("qthelp:////"); /* means that the qthelp url
doesn't have any namespace */
QString reason;
bool ok = true;
- if (!item->hasSourceCode || !QFileInfo::exists(item->projectPath)) {
+ if (!item->hasSourceCode || !item->projectPath.exists()) {
ok = false;
- reason = QString::fromLatin1("projectPath \"%1\" empty or does not exist").arg(item->projectPath);
+ reason = QString::fromLatin1("projectPath \"%1\" empty or does not exist")
+ .arg(item->projectPath.toUserOutput());
} else if (item->imageUrl.startsWith(invalidPrefix) || !QUrl(item->imageUrl).isValid()) {
ok = false;
reason = QString::fromLatin1("imageUrl \"%1\" not valid").arg(item->imageUrl);
} else if (!item->docUrl.isEmpty()
- && (item->docUrl.startsWith(invalidPrefix) || !QUrl(item->docUrl).isValid())) {
+ && (item->docUrl.startsWith(invalidPrefix) || !QUrl(item->docUrl).isValid())) {
ok = false;
reason = QString::fromLatin1("docUrl \"%1\" non-empty but not valid").arg(item->docUrl);
}
@@ -345,156 +320,54 @@ static bool isValidExampleOrDemo(ExampleItem *item)
return ok || debugExamples();
}
-static QList<ExampleItem *> parseExamples(QXmlStreamReader *reader,
- const QString &projectsOffset,
- const QString &examplesInstallPath)
+static bool sortByHighlightedAndName(ExampleItem *first, ExampleItem *second)
{
- QList<ExampleItem *> result;
- std::unique_ptr<ExampleItem> item;
- const QChar slash = QLatin1Char('/');
- while (!reader->atEnd()) {
- switch (reader->readNext()) {
- case QXmlStreamReader::StartElement:
- if (reader->name() == QLatin1String("example")) {
- item = std::make_unique<ExampleItem>();
- item->type = Example;
- QXmlStreamAttributes attributes = reader->attributes();
- item->name = attributes.value(QLatin1String("name")).toString();
- item->projectPath = attributes.value(QLatin1String("projectPath")).toString();
- item->hasSourceCode = !item->projectPath.isEmpty();
- item->projectPath = relativeOrInstallPath(item->projectPath, projectsOffset, examplesInstallPath);
- item->imageUrl = attributes.value(QLatin1String("imageUrl")).toString();
- QPixmapCache::remove(item->imageUrl);
- item->docUrl = attributes.value(QLatin1String("docUrl")).toString();
- item->isHighlighted = attributes.value(QLatin1String("isHighlighted")).toString() == QLatin1String("true");
-
- } else if (reader->name() == QLatin1String("fileToOpen")) {
- const QString mainFileAttribute = reader->attributes().value(
- QLatin1String("mainFile")).toString();
- const QString filePath = relativeOrInstallPath(
- reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement),
- projectsOffset, examplesInstallPath);
- item->filesToOpen.append(filePath);
- if (mainFileAttribute.compare(QLatin1String("true"), Qt::CaseInsensitive) == 0)
- item->mainFile = filePath;
- } else if (reader->name() == QLatin1String("description")) {
- item->description = fixStringForTags(reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement));
- } else if (reader->name() == QLatin1String("dependency")) {
- item->dependencies.append(projectsOffset + slash + reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement));
- } else if (reader->name() == QLatin1String("tags")) {
- item->tags = trimStringList(reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement).split(QLatin1Char(','), Qt::SkipEmptyParts));
- } else if (reader->name() == QLatin1String("platforms")) {
- item->platforms = trimStringList(reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement).split(QLatin1Char(','), Qt::SkipEmptyParts));
- }
- break;
- case QXmlStreamReader::EndElement:
- if (reader->name() == QLatin1String("example")) {
- if (isValidExampleOrDemo(item.get()))
- result.push_back(item.release());
- } else if (reader->name() == QLatin1String("examples")) {
- return result;
- }
- break;
- default: // nothing
- break;
- }
- }
- return result;
+ if (first->isHighlighted && !second->isHighlighted)
+ return true;
+ if (!first->isHighlighted && second->isHighlighted)
+ return false;
+ return first->name.compare(second->name, Qt::CaseInsensitive) < 0;
}
-static QList<ExampleItem *> parseDemos(QXmlStreamReader *reader,
- const QString &projectsOffset,
- const QString &demosInstallPath)
+static QList<std::pair<QString, QList<ExampleItem *>>> getCategories(
+ const QList<ExampleItem *> &items)
{
- QList<ExampleItem *> result;
- std::unique_ptr<ExampleItem> item;
- const QChar slash = QLatin1Char('/');
- while (!reader->atEnd()) {
- switch (reader->readNext()) {
- case QXmlStreamReader::StartElement:
- if (reader->name() == QLatin1String("demo")) {
- item = std::make_unique<ExampleItem>();
- item->type = Demo;
- QXmlStreamAttributes attributes = reader->attributes();
- item->name = attributes.value(QLatin1String("name")).toString();
- item->projectPath = attributes.value(QLatin1String("projectPath")).toString();
- item->hasSourceCode = !item->projectPath.isEmpty();
- item->projectPath = relativeOrInstallPath(item->projectPath, projectsOffset, demosInstallPath);
- item->imageUrl = attributes.value(QLatin1String("imageUrl")).toString();
- QPixmapCache::remove(item->imageUrl);
- item->docUrl = attributes.value(QLatin1String("docUrl")).toString();
- item->isHighlighted = attributes.value(QLatin1String("isHighlighted")).toString() == QLatin1String("true");
- } else if (reader->name() == QLatin1String("fileToOpen")) {
- item->filesToOpen.append(relativeOrInstallPath(reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement),
- projectsOffset, demosInstallPath));
- } else if (reader->name() == QLatin1String("description")) {
- item->description = fixStringForTags(reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement));
- } else if (reader->name() == QLatin1String("dependency")) {
- item->dependencies.append(projectsOffset + slash + reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement));
- } else if (reader->name() == QLatin1String("tags")) {
- item->tags = reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement).split(QLatin1Char(','));
- }
- break;
- case QXmlStreamReader::EndElement:
- if (reader->name() == QLatin1String("demo")) {
- if (isValidExampleOrDemo(item.get()))
- result.push_back(item.release());
- } else if (reader->name() == QLatin1String("demos")) {
- return result;
- }
- break;
- default: // nothing
- break;
+ static const QString otherDisplayName = Tr::tr("Other", "Category for all other examples");
+ const bool useCategories = qtcEnvironmentVariableIsSet("QTC_USE_EXAMPLE_CATEGORIES");
+ QList<ExampleItem *> other;
+ QMap<QString, QList<ExampleItem *>> categoryMap;
+ if (useCategories) {
+ for (ExampleItem *item : items) {
+ const QStringList itemCategories = item->metaData.value("category");
+ for (const QString &category : itemCategories)
+ categoryMap[category].append(item);
+ if (itemCategories.isEmpty())
+ other.append(item);
}
}
- return result;
-}
-
-static QList<ExampleItem *> parseTutorials(QXmlStreamReader *reader, const QString &projectsOffset)
-{
- QList<ExampleItem *> result;
- std::unique_ptr<ExampleItem> item = std::make_unique<ExampleItem>();
- const QChar slash = QLatin1Char('/');
- while (!reader->atEnd()) {
- switch (reader->readNext()) {
- case QXmlStreamReader::StartElement:
- if (reader->name() == QLatin1String("tutorial")) {
- item = std::make_unique<ExampleItem>();
- item->type = Tutorial;
- QXmlStreamAttributes attributes = reader->attributes();
- item->name = attributes.value(QLatin1String("name")).toString();
- item->projectPath = attributes.value(QLatin1String("projectPath")).toString();
- item->hasSourceCode = !item->projectPath.isEmpty();
- item->projectPath.prepend(slash);
- item->projectPath.prepend(projectsOffset);
- item->imageUrl = Utils::StyleHelper::dpiSpecificImageFile(
- attributes.value(QLatin1String("imageUrl")).toString());
- QPixmapCache::remove(item->imageUrl);
- item->docUrl = attributes.value(QLatin1String("docUrl")).toString();
- item->isVideo = attributes.value(QLatin1String("isVideo")).toString() == QLatin1String("true");
- item->videoUrl = attributes.value(QLatin1String("videoUrl")).toString();
- item->videoLength = attributes.value(QLatin1String("videoLength")).toString();
- } else if (reader->name() == QLatin1String("fileToOpen")) {
- item->filesToOpen.append(projectsOffset + slash + reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement));
- } else if (reader->name() == QLatin1String("description")) {
- item->description = fixStringForTags(reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement));
- } else if (reader->name() == QLatin1String("dependency")) {
- item->dependencies.append(projectsOffset + slash + reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement));
- } else if (reader->name() == QLatin1String("tags")) {
- item->tags = reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement).split(QLatin1Char(','));
- }
- break;
- case QXmlStreamReader::EndElement:
- if (reader->name() == QLatin1String("tutorial"))
- result.push_back(item.release());
- else if (reader->name() == QLatin1String("tutorials"))
- return result;
- break;
- default: // nothing
- break;
- }
+ QList<std::pair<QString, QList<ExampleItem *>>> categories;
+ if (categoryMap.isEmpty()) {
+ // The example set doesn't define categories. Consider the "highlighted" ones as "featured"
+ QList<ExampleItem *> featured;
+ QList<ExampleItem *> allOther;
+ std::tie(featured, allOther) = Utils::partition(items, [](ExampleItem *i) {
+ return i->isHighlighted;
+ });
+ if (!featured.isEmpty())
+ categories.append({Tr::tr("Featured", "Category for highlighted examples"), featured});
+ if (!allOther.isEmpty())
+ categories.append({otherDisplayName, allOther});
+ } else {
+ const auto end = categoryMap.constKeyValueEnd();
+ for (auto it = categoryMap.constKeyValueBegin(); it != end; ++it)
+ categories.append(*it);
+ if (!other.isEmpty())
+ categories.append({otherDisplayName, other});
}
- return result;
+ const auto end = categories.end();
+ for (auto it = categories.begin(); it != end; ++it)
+ sort(it->second, sortByHighlightedAndName);
+ return categories;
}
void ExamplesViewController::updateExamples()
@@ -509,41 +382,27 @@ void ExamplesViewController::updateExamples()
QList<ExampleItem *> items;
for (const QString &exampleSource : sources) {
- QFile exampleFile(exampleSource);
- if (!exampleFile.open(QIODevice::ReadOnly)) {
- if (debugExamples())
- qWarning() << "ERROR: Could not open file" << exampleSource;
- continue;
+ const auto manifest = FilePath::fromUserInput(exampleSource);
+ if (debugExamples()) {
+ qWarning() << QString::fromLatin1("Reading file \"%1\"...")
+ .arg(manifest.absoluteFilePath().toUserOutput());
}
- QFileInfo fi(exampleSource);
- QString offsetPath = fi.path();
- QDir examplesDir(offsetPath);
- QDir demosDir(offsetPath);
-
- if (debugExamples())
- qWarning() << QString::fromLatin1("Reading file \"%1\"...").arg(fi.absoluteFilePath());
- QXmlStreamReader reader(&exampleFile);
- while (!reader.atEnd())
- switch (reader.readNext()) {
- case QXmlStreamReader::StartElement:
- if (m_isExamples && reader.name() == QLatin1String("examples"))
- items += parseExamples(&reader, examplesDir.path(), examplesInstallPath);
- else if (m_isExamples && reader.name() == QLatin1String("demos"))
- items += parseDemos(&reader, demosDir.path(), demosInstallPath);
- else if (!m_isExamples && reader.name() == QLatin1String("tutorials"))
- items += parseTutorials(&reader, examplesDir.path());
- break;
- default: // nothing
- break;
+ const expected_str<QList<ExampleItem *>> result
+ = parseExamples(manifest,
+ FilePath::fromUserInput(examplesInstallPath),
+ FilePath::fromUserInput(demosInstallPath),
+ m_isExamples);
+ if (!result) {
+ if (debugExamples()) {
+ qWarning() << "ERROR: Could not read examples from" << exampleSource << ":"
+ << result.error();
}
-
- if (reader.hasError() && debugExamples()) {
- qWarning().noquote().nospace() << "ERROR: Could not parse file as XML document ("
- << exampleSource << "):" << reader.lineNumber() << ':' << reader.columnNumber()
- << ": " << reader.errorString();
+ continue;
}
+ items += filtered(*result, isValidExampleOrDemo);
}
+
if (m_isExamples) {
if (m_exampleSetModel->selectedQtSupports(Android::Constants::ANDROID_DEVICE_TYPE)) {
items = Utils::filtered(items, [](ExampleItem *item) {
@@ -554,21 +413,12 @@ void ExamplesViewController::updateExamples()
[](ExampleItem *item) { return item->tags.contains("ios"); });
}
}
- Utils::sort(items, [](ExampleItem *first, ExampleItem *second) {
- return first->name.compare(second->name, Qt::CaseInsensitive) < 0;
- });
-
- QList<ExampleItem *> featured;
- QList<ExampleItem *> other;
- std::tie(featured, other) = Utils::partition(items,
- [](ExampleItem *i) { return i->isHighlighted; });
- if (!featured.isEmpty()) {
- m_view->addSection({Tr::tr("Featured", "Category for highlighted examples"), 0},
- static_container_cast<ListItem *>(featured));
+ const QList<std::pair<QString, QList<ExampleItem *>>> sections = getCategories(items);
+ for (int i = 0; i < sections.size(); ++i) {
+ m_view->addSection({sections.at(i).first, i},
+ static_container_cast<ListItem *>(sections.at(i).second));
}
- m_view->addSection({Tr::tr("Other", "Category for all other examples"), 1},
- static_container_cast<ListItem *>(other));
}
void ExampleSetModel::updateQtVersionList()
diff --git a/src/plugins/qtsupport/exampleslistmodel.h b/src/plugins/qtsupport/exampleslistmodel.h
index a4e8fe9eba6..0047bf45e3e 100644
--- a/src/plugins/qtsupport/exampleslistmodel.h
+++ b/src/plugins/qtsupport/exampleslistmodel.h
@@ -77,29 +77,6 @@ private:
bool m_initalized = false;
};
-enum InstructionalType
-{
- Example = 0, Demo, Tutorial
-};
-
-class ExampleItem : public Core::ListItem
-{
-public:
- QString projectPath;
- QString docUrl;
- QStringList filesToOpen;
- QString mainFile; /* file to be visible after opening filesToOpen */
- QStringList dependencies;
- InstructionalType type;
- int difficulty = 0;
- bool hasSourceCode = false;
- bool isVideo = false;
- bool isHighlighted = false;
- QString videoUrl;
- QString videoLength;
- QStringList platforms;
-};
-
class ExamplesViewController : public QObject
{
Q_OBJECT
@@ -119,5 +96,3 @@ private:
} // namespace Internal
} // namespace QtSupport
-
-Q_DECLARE_METATYPE(QtSupport::Internal::ExampleItem *)
diff --git a/src/plugins/qtsupport/examplesparser.cpp b/src/plugins/qtsupport/examplesparser.cpp
new file mode 100644
index 00000000000..99a3dafc5ba
--- /dev/null
+++ b/src/plugins/qtsupport/examplesparser.cpp
@@ -0,0 +1,301 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "examplesparser.h"
+
+#include <utils/algorithm.h>
+#include <utils/filepath.h>
+#include <utils/stylehelper.h>
+
+#include <QPixmapCache>
+#include <QXmlStreamReader>
+
+using namespace Utils;
+
+namespace QtSupport::Internal {
+
+static FilePath relativeOrInstallPath(const FilePath &path,
+ const FilePath &manifestPath,
+ const FilePath &installPath)
+{
+ const FilePath relativeResolvedPath = manifestPath.resolvePath(path);
+ const FilePath installResolvedPath = installPath.resolvePath(path);
+ if (relativeResolvedPath.exists())
+ return relativeResolvedPath;
+ if (installResolvedPath.exists())
+ return installResolvedPath;
+ // doesn't exist, return the preferred resolved install path
+ return installResolvedPath;
+}
+
+static QString fixStringForTags(const QString &string)
+{
+ QString returnString = string;
+ returnString.remove(QLatin1String("<i>"));
+ returnString.remove(QLatin1String("</i>"));
+ returnString.remove(QLatin1String("<tt>"));
+ returnString.remove(QLatin1String("</tt>"));
+ return returnString;
+}
+
+static QStringList trimStringList(const QStringList &stringlist)
+{
+ return Utils::transform(stringlist, [](const QString &str) { return str.trimmed(); });
+}
+
+static QHash<QString, QStringList> parseMeta(QXmlStreamReader *reader)
+{
+ QHash<QString, QStringList> result;
+ while (!reader->atEnd()) {
+ switch (reader->readNext()) {
+ case QXmlStreamReader::StartElement:
+ if (reader->name() == QLatin1String("entry")) {
+ const QString key = reader->attributes().value("name").toString();
+ if (key.isEmpty()) {
+ reader->raiseError("Tag \"entry\" requires \"name\" attribute");
+ break;
+ }
+ const QString value = reader->readElementText(
+ QXmlStreamReader::ErrorOnUnexpectedElement);
+ if (!value.isEmpty())
+ result[key].append(value);
+ }
+ break;
+ case QXmlStreamReader::EndElement:
+ if (reader->name() == QLatin1String("meta"))
+ return result;
+ break;
+ default:
+ break;
+ }
+ }
+ return result;
+}
+
+static QList<ExampleItem *> parseExamples(QXmlStreamReader *reader,
+ const FilePath &projectsOffset,
+ const FilePath &examplesInstallPath)
+{
+ QList<ExampleItem *> result;
+ std::unique_ptr<ExampleItem> item;
+ while (!reader->atEnd()) {
+ switch (reader->readNext()) {
+ case QXmlStreamReader::StartElement:
+ if (reader->name() == QLatin1String("example")) {
+ item = std::make_unique<ExampleItem>();
+ item->type = Example;
+ QXmlStreamAttributes attributes = reader->attributes();
+ item->name = attributes.value(QLatin1String("name")).toString();
+ item->projectPath = FilePath::fromUserInput(
+ attributes.value(QLatin1String("projectPath")).toString());
+ item->hasSourceCode = !item->projectPath.isEmpty();
+ item->projectPath = relativeOrInstallPath(item->projectPath,
+ projectsOffset,
+ examplesInstallPath);
+ item->imageUrl = attributes.value(QLatin1String("imageUrl")).toString();
+ QPixmapCache::remove(item->imageUrl);
+ item->docUrl = attributes.value(QLatin1String("docUrl")).toString();
+ item->isHighlighted = attributes.value(QLatin1String("isHighlighted")).toString()
+ == QLatin1String("true");
+
+ } else if (reader->name() == QLatin1String("fileToOpen")) {
+ const QString mainFileAttribute
+ = reader->attributes().value(QLatin1String("mainFile")).toString();
+ const FilePath filePath
+ = relativeOrInstallPath(FilePath::fromUserInput(reader->readElementText(
+ QXmlStreamReader::ErrorOnUnexpectedElement)),
+ projectsOffset,
+ examplesInstallPath);
+ item->filesToOpen.append(filePath);
+ if (mainFileAttribute.compare(QLatin1String("true"), Qt::CaseInsensitive) == 0)
+ item->mainFile = filePath;
+ } else if (reader->name() == QLatin1String("description")) {
+ item->description = fixStringForTags(
+ reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement));
+ } else if (reader->name() == QLatin1String("dependency")) {
+ item->dependencies.append(
+ projectsOffset
+ / reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement));
+ } else if (reader->name() == QLatin1String("tags")) {
+ item->tags = trimStringList(
+ reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement)
+ .split(QLatin1Char(','), Qt::SkipEmptyParts));
+ } else if (reader->name() == QLatin1String("platforms")) {
+ item->platforms = trimStringList(
+ reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement)
+ .split(QLatin1Char(','), Qt::SkipEmptyParts));
+ } else if (reader->name() == QLatin1String("meta")) {
+ item->metaData = parseMeta(reader);
+ }
+ break;
+ case QXmlStreamReader::EndElement:
+ if (reader->name() == QLatin1String("example")) {
+ result.push_back(item.release());
+ } else if (reader->name() == QLatin1String("examples")) {
+ return result;
+ }
+ break;
+ default: // nothing
+ break;
+ }
+ }
+ return result;
+}
+
+static QList<ExampleItem *> parseDemos(QXmlStreamReader *reader,
+ const FilePath &projectsOffset,
+ const FilePath &demosInstallPath)
+{
+ QList<ExampleItem *> result;
+ std::unique_ptr<ExampleItem> item;
+ while (!reader->atEnd()) {
+ switch (reader->readNext()) {
+ case QXmlStreamReader::StartElement:
+ if (reader->name() == QLatin1String("demo")) {
+ item = std::make_unique<ExampleItem>();
+ item->type = Demo;
+ QXmlStreamAttributes attributes = reader->attributes();
+ item->name = attributes.value(QLatin1String("name")).toString();
+ item->projectPath = FilePath::fromUserInput(
+ attributes.value(QLatin1String("projectPath")).toString());
+ item->hasSourceCode = !item->projectPath.isEmpty();
+ item->projectPath = relativeOrInstallPath(item->projectPath,
+ projectsOffset,
+ demosInstallPath);
+ item->imageUrl = attributes.value(QLatin1String("imageUrl")).toString();
+ QPixmapCache::remove(item->imageUrl);
+ item->docUrl = attributes.value(QLatin1String("docUrl")).toString();
+ item->isHighlighted = attributes.value(QLatin1String("isHighlighted")).toString()
+ == QLatin1String("true");
+ } else if (reader->name() == QLatin1String("fileToOpen")) {
+ item->filesToOpen.append(
+ relativeOrInstallPath(FilePath::fromUserInput(reader->readElementText(
+ QXmlStreamReader::ErrorOnUnexpectedElement)),
+ projectsOffset,
+ demosInstallPath));
+ } else if (reader->name() == QLatin1String("description")) {
+ item->description = fixStringForTags(
+ reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement));
+ } else if (reader->name() == QLatin1String("dependency")) {
+ item->dependencies.append(
+ projectsOffset
+ / reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement));
+ } else if (reader->name() == QLatin1String("tags")) {
+ item->tags = reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement)
+ .split(QLatin1Char(','));
+ }
+ break;
+ case QXmlStreamReader::EndElement:
+ if (reader->name() == QLatin1String("demo")) {
+ result.push_back(item.release());
+ } else if (reader->name() == QLatin1String("demos")) {
+ return result;
+ }
+ break;
+ default: // nothing
+ break;
+ }
+ }
+ return result;
+}
+
+static QList<ExampleItem *> parseTutorials(QXmlStreamReader *reader, const FilePath &projectsOffset)
+{
+ QList<ExampleItem *> result;
+ std::unique_ptr<ExampleItem> item = std::make_unique<ExampleItem>();
+ while (!reader->atEnd()) {
+ switch (reader->readNext()) {
+ case QXmlStreamReader::StartElement:
+ if (reader->name() == QLatin1String("tutorial")) {
+ item = std::make_unique<ExampleItem>();
+ item->type = Tutorial;
+ QXmlStreamAttributes attributes = reader->attributes();
+ item->name = attributes.value(QLatin1String("name")).toString();
+ item->projectPath = projectsOffset
+ / attributes.value(QLatin1String("projectPath")).toString();
+ item->hasSourceCode = !item->projectPath.isEmpty();
+ item->imageUrl = Utils::StyleHelper::dpiSpecificImageFile(
+ attributes.value(QLatin1String("imageUrl")).toString());
+ QPixmapCache::remove(item->imageUrl);
+ item->docUrl = attributes.value(QLatin1String("docUrl")).toString();
+ item->isVideo = attributes.value(QLatin1String("isVideo")).toString()
+ == QLatin1String("true");
+ item->videoUrl = attributes.value(QLatin1String("videoUrl")).toString();
+ item->videoLength = attributes.value(QLatin1String("videoLength")).toString();
+ } else if (reader->name() == QLatin1String("fileToOpen")) {
+ item->filesToOpen.append(
+ projectsOffset
+ / reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement));
+ } else if (reader->name() == QLatin1String("description")) {
+ item->description = fixStringForTags(
+ reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement));
+ } else if (reader->name() == QLatin1String("dependency")) {
+ item->dependencies.append(
+ projectsOffset
+ / reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement));
+ } else if (reader->name() == QLatin1String("tags")) {
+ item->tags = reader->readElementText(QXmlStreamReader::ErrorOnUnexpectedElement)
+ .split(QLatin1Char(','));
+ }
+ break;
+ case QXmlStreamReader::EndElement:
+ if (reader->name() == QLatin1String("tutorial"))
+ result.push_back(item.release());
+ else if (reader->name() == QLatin1String("tutorials"))
+ return result;
+ break;
+ default: // nothing
+ break;
+ }
+ }
+ return result;
+}
+
+expected_str<QList<ExampleItem *>> parseExamples(const FilePath &manifest,
+ const FilePath &examplesInstallPath,
+ const FilePath &demosInstallPath,
+ const bool examples)
+{
+ const expected_str<QByteArray> contents = manifest.fileContents();
+ if (!contents)
+ return make_unexpected(contents.error());
+
+ return parseExamples(*contents, manifest, examplesInstallPath, demosInstallPath, examples);
+}
+
+expected_str<QList<ExampleItem *>> parseExamples(const QByteArray &manifestData,
+ const Utils::FilePath &manifestPath,
+ const FilePath &examplesInstallPath,
+ const FilePath &demosInstallPath,
+ const bool examples)
+{
+ const FilePath path = manifestPath.parentDir();
+ QList<ExampleItem *> items;
+ QXmlStreamReader reader(manifestData);
+ while (!reader.atEnd()) {
+ switch (reader.readNext()) {
+ case QXmlStreamReader::StartElement:
+ if (examples && reader.name() == QLatin1String("examples"))
+ items += parseExamples(&reader, path, examplesInstallPath);
+ else if (examples && reader.name() == QLatin1String("demos"))
+ items += parseDemos(&reader, path, demosInstallPath);
+ else if (!examples && reader.name() == QLatin1String("tutorials"))
+ items += parseTutorials(&reader, path);
+ break;
+ default: // nothing
+ break;
+ }
+ }
+
+ if (reader.hasError()) {
+ qDeleteAll(items);
+ return make_unexpected(QString("Could not parse file \"%1\" as XML document: %2:%3: %4")
+ .arg(manifestPath.toUserOutput())
+ .arg(reader.lineNumber())
+ .arg(reader.columnNumber())
+ .arg(reader.errorString()));
+ }
+ return items;
+}
+
+} // namespace QtSupport::Internal
diff --git a/src/plugins/qtsupport/examplesparser.h b/src/plugins/qtsupport/examplesparser.h
new file mode 100644
index 00000000000..2d1afa54838
--- /dev/null
+++ b/src/plugins/qtsupport/examplesparser.h
@@ -0,0 +1,49 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "qtsupport_global.h"
+
+#include <coreplugin/welcomepagehelper.h>
+#include <utils/expected.h>
+#include <utils/filepath.h>
+
+namespace QtSupport::Internal {
+
+enum InstructionalType { Example = 0, Demo, Tutorial };
+
+class QTSUPPORT_EXPORT ExampleItem : public Core::ListItem
+{
+public:
+ Utils::FilePath projectPath;
+ QString docUrl;
+ Utils::FilePaths filesToOpen;
+ Utils::FilePath mainFile; /* file to be visible after opening filesToOpen */
+ Utils::FilePaths dependencies;
+ InstructionalType type;
+ bool hasSourceCode = false;
+ bool isVideo = false;
+ bool isHighlighted = false;
+ QString videoUrl;
+ QString videoLength;
+ QStringList platforms;
+ QHash<QString, QStringList> metaData;
+};
+
+QTSUPPORT_EXPORT Utils::expected_str<QList<ExampleItem *>> parseExamples(
+ const Utils::FilePath &manifest,
+ const Utils::FilePath &examplesInstallPath,
+ const Utils::FilePath &demosInstallPath,
+ bool examples);
+
+QTSUPPORT_EXPORT Utils::expected_str<QList<ExampleItem *>> parseExamples(
+ const QByteArray &manifestData,
+ const Utils::FilePath &manifestPath,
+ const Utils::FilePath &examplesInstallPath,
+ const Utils::FilePath &demosInstallPath,
+ bool examples);
+
+} // namespace QtSupport::Internal
+
+Q_DECLARE_METATYPE(QtSupport::Internal::ExampleItem *)
diff --git a/src/plugins/qtsupport/externaleditors.cpp b/src/plugins/qtsupport/externaleditors.cpp
index bed8b7618fa..ee3a3611031 100644
--- a/src/plugins/qtsupport/externaleditors.cpp
+++ b/src/plugins/qtsupport/externaleditors.cpp
@@ -8,8 +8,8 @@
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectexplorerconstants.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/target.h>
-#include <projectexplorer/session.h>
#include <qtsupport/qtkitinformation.h>
@@ -181,7 +181,7 @@ static bool getEditorLaunchData(const CommandForQtVersion &commandForQtVersion,
// As fallback check PATH
data->workingDirectory.clear();
QVector<QtSupport::QtVersion *> qtVersionsToCheck; // deduplicated after being filled
- if (const Project *project = SessionManager::projectForFile(filePath)) {
+ if (const Project *project = ProjectManager::projectForFile(filePath)) {
data->workingDirectory = project->projectDirectory();
// active kit
if (const Target *target = project->activeTarget()) {
diff --git a/src/plugins/qtsupport/gettingstartedwelcomepage.cpp b/src/plugins/qtsupport/gettingstartedwelcomepage.cpp
index 36bbcfb902b..bf19233c211 100644
--- a/src/plugins/qtsupport/gettingstartedwelcomepage.cpp
+++ b/src/plugins/qtsupport/gettingstartedwelcomepage.cpp
@@ -4,6 +4,7 @@
#include "gettingstartedwelcomepage.h"
#include "exampleslistmodel.h"
+#include "examplesparser.h"
#include "qtsupporttr.h"
#include <coreplugin/coreconstants.h>
@@ -68,16 +69,18 @@ Id ExamplesWelcomePage::id() const
return m_showExamples ? "Examples" : "Tutorials";
}
-QString ExamplesWelcomePage::copyToAlternativeLocation(const QFileInfo& proFileInfo, QStringList &filesToOpen, const QStringList& dependencies)
+FilePath ExamplesWelcomePage::copyToAlternativeLocation(const FilePath &proFile,
+ FilePaths &filesToOpen,
+ const FilePaths &dependencies)
{
- const QString projectDir = proFileInfo.canonicalPath();
+ const FilePath projectDir = proFile.canonicalPath().parentDir();
QDialog d(ICore::dialogParent());
auto lay = new QGridLayout(&d);
auto descrLbl = new QLabel;
d.setWindowTitle(Tr::tr("Copy Project to writable Location?"));
descrLbl->setTextFormat(Qt::RichText);
descrLbl->setWordWrap(false);
- const QString nativeProjectDir = QDir::toNativeSeparators(projectDir);
+ const QString nativeProjectDir = projectDir.toUserOutput();
descrLbl->setText(QString::fromLatin1("<blockquote>%1</blockquote>").arg(nativeProjectDir));
descrLbl->setMinimumWidth(descrLbl->sizeHint().width());
descrLbl->setWordWrap(true);
@@ -94,9 +97,10 @@ QString ExamplesWelcomePage::copyToAlternativeLocation(const QFileInfo& proFileI
txt->setBuddy(chooser);
chooser->setExpectedKind(PathChooser::ExistingDirectory);
chooser->setHistoryCompleter(QLatin1String("Qt.WritableExamplesDir.History"));
- const QString defaultRootDirectory = DocumentManager::projectsDirectory().toString();
+ const FilePath defaultRootDirectory = DocumentManager::projectsDirectory();
QtcSettings *settings = ICore::settings();
- chooser->setFilePath(FilePath::fromSettings(settings->value(C_FALLBACK_ROOT, defaultRootDirectory)));
+ chooser->setFilePath(
+ FilePath::fromSettings(settings->value(C_FALLBACK_ROOT, defaultRootDirectory.toVariant())));
lay->addWidget(txt, 1, 0);
lay->addWidget(chooser, 1, 1);
enum { Copy = QDialog::Accepted + 1, Keep = QDialog::Accepted + 2 };
@@ -110,35 +114,32 @@ QString ExamplesWelcomePage::copyToAlternativeLocation(const QFileInfo& proFileI
connect(chooser, &PathChooser::validChanged, copyBtn, &QWidget::setEnabled);
int code = d.exec();
if (code == Copy) {
- QString exampleDirName = proFileInfo.dir().dirName();
- QString destBaseDir = chooser->filePath().toString();
+ const QString exampleDirName = projectDir.fileName();
+ const FilePath destBaseDir = chooser->filePath();
settings->setValueWithDefault(C_FALLBACK_ROOT, destBaseDir, defaultRootDirectory);
- QDir toDirWithExamplesDir(destBaseDir);
- if (toDirWithExamplesDir.cd(exampleDirName)) {
- toDirWithExamplesDir.cdUp(); // step out, just to not be in the way
+ const FilePath targetDir = destBaseDir / exampleDirName;
+ if (targetDir.exists()) {
QMessageBox::warning(ICore::dialogParent(),
Tr::tr("Cannot Use Location"),
Tr::tr("The specified location already exists. "
"Please specify a valid location."),
QMessageBox::Ok,
QMessageBox::NoButton);
- return QString();
+ return {};
} else {
- QString targetDir = destBaseDir + QLatin1Char('/') + exampleDirName;
-
- expected_str<void> result
- = FilePath::fromString(projectDir).copyRecursively(FilePath::fromString(targetDir));
+ expected_str<void> result = projectDir.copyRecursively(targetDir);
if (result) {
// set vars to new location
- const QStringList::Iterator end = filesToOpen.end();
- for (QStringList::Iterator it = filesToOpen.begin(); it != end; ++it)
- it->replace(projectDir, targetDir);
-
- for (const QString &dependency : dependencies) {
- const FilePath targetFile = FilePath::fromString(targetDir)
- .pathAppended(QDir(dependency).dirName());
- result = FilePath::fromString(dependency).copyRecursively(targetFile);
+ const FilePaths::Iterator end = filesToOpen.end();
+ for (FilePaths::Iterator it = filesToOpen.begin(); it != end; ++it) {
+ const FilePath relativePath = it->relativeChildPath(projectDir);
+ *it = targetDir.resolvePath(relativePath);
+ }
+
+ for (const FilePath &dependency : dependencies) {
+ const FilePath targetFile = targetDir.pathAppended(dependency.fileName());
+ result = dependency.copyRecursively(targetFile);
if (!result) {
QMessageBox::warning(ICore::dialogParent(),
Tr::tr("Cannot Copy Project"),
@@ -147,7 +148,7 @@ QString ExamplesWelcomePage::copyToAlternativeLocation(const QFileInfo& proFileI
}
}
- return targetDir + QLatin1Char('/') + proFileInfo.fileName();
+ return targetDir / proFile.fileName();
} else {
QMessageBox::warning(ICore::dialogParent(),
Tr::tr("Cannot Copy Project"),
@@ -156,46 +157,43 @@ QString ExamplesWelcomePage::copyToAlternativeLocation(const QFileInfo& proFileI
}
}
if (code == Keep)
- return proFileInfo.absoluteFilePath();
- return QString();
+ return proFile.absoluteFilePath();
+ return {};
}
void ExamplesWelcomePage::openProject(const ExampleItem *item)
{
using namespace ProjectExplorer;
- QString proFile = item->projectPath;
+ FilePath proFile = item->projectPath;
if (proFile.isEmpty())
return;
- QStringList filesToOpen = item->filesToOpen;
+ FilePaths filesToOpen = item->filesToOpen;
if (!item->mainFile.isEmpty()) {
// ensure that the main file is opened on top (i.e. opened last)
filesToOpen.removeAll(item->mainFile);
filesToOpen.append(item->mainFile);
}
- QFileInfo proFileInfo(proFile);
- if (!proFileInfo.exists())
+ if (!proFile.exists())
return;
// If the Qt is a distro Qt on Linux, it will not be writable, hence compilation will fail
// Same if it is installed in non-writable location for other reasons
- const bool needsCopy = withNtfsPermissions<bool>([proFileInfo] {
- QFileInfo pathInfo(proFileInfo.path());
- return !proFileInfo.isWritable()
- || !pathInfo.isWritable() /* path of .pro file */
- || !QFileInfo(pathInfo.path()).isWritable() /* shadow build directory */;
+ const bool needsCopy = withNtfsPermissions<bool>([proFile] {
+ return !proFile.isWritableFile()
+ || !proFile.parentDir().isWritableDir() /* path of project file */
+ || !proFile.parentDir().parentDir().isWritableDir() /* shadow build directory */;
});
if (needsCopy)
- proFile = copyToAlternativeLocation(proFileInfo, filesToOpen, item->dependencies);
+ proFile = copyToAlternativeLocation(proFile, filesToOpen, item->dependencies);
// don't try to load help and files if loading the help request is being cancelled
if (proFile.isEmpty())
return;
- ProjectExplorerPlugin::OpenProjectResult result =
- ProjectExplorerPlugin::openProject(FilePath::fromString(proFile));
+ ProjectExplorerPlugin::OpenProjectResult result = ProjectExplorerPlugin::openProject(proFile);
if (result) {
- ICore::openFiles(FileUtils::toFilePathList(filesToOpen));
+ ICore::openFiles(filesToOpen);
ModeManager::activateMode(Core::Constants::MODE_EDIT);
QUrl docUrl = QUrl::fromUserInput(item->docUrl);
if (docUrl.isValid())
diff --git a/src/plugins/qtsupport/gettingstartedwelcomepage.h b/src/plugins/qtsupport/gettingstartedwelcomepage.h
index 2781666a923..aa07f669167 100644
--- a/src/plugins/qtsupport/gettingstartedwelcomepage.h
+++ b/src/plugins/qtsupport/gettingstartedwelcomepage.h
@@ -4,10 +4,7 @@
#pragma once
#include <coreplugin/iwelcomepage.h>
-
-QT_BEGIN_NAMESPACE
-class QFileInfo;
-QT_END_NAMESPACE
+#include <utils/filepath.h>
namespace QtSupport {
namespace Internal {
@@ -29,7 +26,9 @@ public:
static void openProject(const ExampleItem *item);
private:
- static QString copyToAlternativeLocation(const QFileInfo &fileInfo, QStringList &filesToOpen, const QStringList &dependencies);
+ static Utils::FilePath copyToAlternativeLocation(const Utils::FilePath &fileInfo,
+ Utils::FilePaths &filesToOpen,
+ const Utils::FilePaths &dependencies);
const bool m_showExamples;
};
diff --git a/src/plugins/qtsupport/qtsupport.qbs b/src/plugins/qtsupport/qtsupport.qbs
index 58f0ea61c3b..526cf4a24fe 100644
--- a/src/plugins/qtsupport/qtsupport.qbs
+++ b/src/plugins/qtsupport/qtsupport.qbs
@@ -77,6 +77,8 @@ Project {
"qtsupport.qrc",
"exampleslistmodel.cpp",
"exampleslistmodel.h",
+ "examplesparser.cpp",
+ "examplesparser.h",
"profilereader.cpp",
"profilereader.h",
"qscxmlcgenerator.cpp",
diff --git a/src/plugins/qtsupport/qtsupportplugin.cpp b/src/plugins/qtsupport/qtsupportplugin.cpp
index c54c1bfc232..8a1c94bddca 100644
--- a/src/plugins/qtsupport/qtsupportplugin.cpp
+++ b/src/plugins/qtsupport/qtsupportplugin.cpp
@@ -24,8 +24,8 @@
#include <projectexplorer/jsonwizard/jsonwizardfactory.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorer.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/projecttree.h>
-#include <projectexplorer/session.h>
#include <projectexplorer/target.h>
#include <proparser/qmakeevaluator.h>
@@ -170,7 +170,7 @@ void QtSupportPlugin::extensionsInitialized()
});
static const auto activeQtVersion = []() -> const QtVersion * {
- ProjectExplorer::Project *project = SessionManager::startupProject();
+ ProjectExplorer::Project *project = ProjectManager::startupProject();
if (!project || !project->activeTarget())
return nullptr;
return QtKitAspect::qtVersion(project->activeTarget()->kit());
@@ -208,7 +208,7 @@ void QtSupportPlugin::extensionsInitialized()
const FilePath filePath = item.filePath();
if (filePath.isEmpty())
return links;
- const Project *project = SessionManager::projectForFile(filePath);
+ const Project *project = ProjectManager::projectForFile(filePath);
Target *target = project ? project->activeTarget() : nullptr;
QtVersion *qt = target ? QtKitAspect::qtVersion(target->kit()) : nullptr;
if (!qt)
diff --git a/src/plugins/remotelinux/CMakeLists.txt b/src/plugins/remotelinux/CMakeLists.txt
index 2899475f13d..dbb2b8e143d 100644
--- a/src/plugins/remotelinux/CMakeLists.txt
+++ b/src/plugins/remotelinux/CMakeLists.txt
@@ -5,7 +5,6 @@ add_qtc_plugin(RemoteLinux
abstractremotelinuxdeploystep.cpp abstractremotelinuxdeploystep.h
customcommanddeploystep.cpp customcommanddeploystep.h
deploymenttimeinfo.cpp deploymenttimeinfo.h
- genericdirectuploadservice.cpp genericdirectuploadservice.h
genericdirectuploadstep.cpp genericdirectuploadstep.h
genericlinuxdeviceconfigurationwidget.cpp genericlinuxdeviceconfigurationwidget.h
genericlinuxdeviceconfigurationwizard.cpp genericlinuxdeviceconfigurationwizard.h
@@ -28,7 +27,6 @@ add_qtc_plugin(RemoteLinux
remotelinuxsignaloperation.cpp remotelinuxsignaloperation.h
rsyncdeploystep.cpp rsyncdeploystep.h
sshkeycreationdialog.cpp sshkeycreationdialog.h
- sshprocessinterface.h
tarpackagecreationstep.cpp tarpackagecreationstep.h
tarpackagedeploystep.cpp tarpackagedeploystep.h
)
diff --git a/src/plugins/remotelinux/abstractremotelinuxdeploystep.cpp b/src/plugins/remotelinux/abstractremotelinuxdeploystep.cpp
index 7e7e3d9a19a..60982c32872 100644
--- a/src/plugins/remotelinux/abstractremotelinuxdeploystep.cpp
+++ b/src/plugins/remotelinux/abstractremotelinuxdeploystep.cpp
@@ -24,137 +24,60 @@ using namespace Utils;
namespace RemoteLinux {
namespace Internal {
-class AbstractRemoteLinuxDeployServicePrivate
-{
-public:
- IDevice::ConstPtr deviceConfiguration;
- QPointer<Target> target;
-
- DeploymentTimeInfo deployTimes;
- std::unique_ptr<TaskTree> m_taskTree;
-};
-
class AbstractRemoteLinuxDeployStepPrivate
{
public:
bool hasError;
std::function<CheckResult()> internalInit;
std::function<void()> runPreparer;
- AbstractRemoteLinuxDeployService *deployService = nullptr;
+
+ DeploymentTimeInfo deployTimes;
+ std::unique_ptr<TaskTree> m_taskTree;
};
} // Internal
using namespace Internal;
-AbstractRemoteLinuxDeployService::AbstractRemoteLinuxDeployService(QObject *parent)
- : QObject(parent), d(new AbstractRemoteLinuxDeployServicePrivate)
-{
-}
+AbstractRemoteLinuxDeployStep::AbstractRemoteLinuxDeployStep(BuildStepList *bsl, Id id)
+ : BuildStep(bsl, id), d(new AbstractRemoteLinuxDeployStepPrivate)
+{}
-AbstractRemoteLinuxDeployService::~AbstractRemoteLinuxDeployService()
+AbstractRemoteLinuxDeployStep::~AbstractRemoteLinuxDeployStep()
{
delete d;
}
-const Target *AbstractRemoteLinuxDeployService::target() const
+IDevice::ConstPtr AbstractRemoteLinuxDeployStep::deviceConfiguration() const
{
- return d->target;
+ return DeviceKitAspect::device(kit());
}
-const Kit *AbstractRemoteLinuxDeployService::kit() const
-{
- return d->target ? d->target->kit() : nullptr;
-}
-
-IDevice::ConstPtr AbstractRemoteLinuxDeployService::deviceConfiguration() const
-{
- return d->deviceConfiguration;
-}
-
-void AbstractRemoteLinuxDeployService::saveDeploymentTimeStamp(const DeployableFile &deployableFile,
+void AbstractRemoteLinuxDeployStep::saveDeploymentTimeStamp(const DeployableFile &deployableFile,
const QDateTime &remoteTimestamp)
{
d->deployTimes.saveDeploymentTimeStamp(deployableFile, kit(), remoteTimestamp);
}
-bool AbstractRemoteLinuxDeployService::hasLocalFileChanged(
+bool AbstractRemoteLinuxDeployStep::hasLocalFileChanged(
const DeployableFile &deployableFile) const
{
return d->deployTimes.hasLocalFileChanged(deployableFile, kit());
}
-bool AbstractRemoteLinuxDeployService::hasRemoteFileChanged(
+bool AbstractRemoteLinuxDeployStep::hasRemoteFileChanged(
const DeployableFile &deployableFile, const QDateTime &remoteTimestamp) const
{
return d->deployTimes.hasRemoteFileChanged(deployableFile, kit(), remoteTimestamp);
}
-void AbstractRemoteLinuxDeployService::setTarget(Target *target)
-{
- d->target = target;
- d->deviceConfiguration = DeviceKitAspect::device(kit());
-}
-
-void AbstractRemoteLinuxDeployService::start()
-{
- QTC_ASSERT(!d->m_taskTree, return);
-
- const CheckResult check = isDeploymentPossible();
- if (!check) {
- emit errorMessage(check.errorMessage());
- emit finished();
- return;
- }
-
- if (!isDeploymentNecessary()) {
- emit progressMessage(Tr::tr("No deployment action necessary. Skipping."));
- emit finished();
- return;
- }
-
- d->m_taskTree.reset(new TaskTree(deployRecipe()));
- const auto endHandler = [this] {
- d->m_taskTree.release()->deleteLater();
- emit finished();
- };
- connect(d->m_taskTree.get(), &TaskTree::done, this, endHandler);
- connect(d->m_taskTree.get(), &TaskTree::errorOccurred, this, endHandler);
- d->m_taskTree->start();
-}
-
-void AbstractRemoteLinuxDeployService::stop()
-{
- if (!d->m_taskTree)
- return;
- d->m_taskTree.reset();
- emit finished();
-}
-
-CheckResult AbstractRemoteLinuxDeployService::isDeploymentPossible() const
+CheckResult AbstractRemoteLinuxDeployStep::isDeploymentPossible() const
{
if (!deviceConfiguration())
return CheckResult::failure(Tr::tr("No device configuration set."));
return CheckResult::success();
}
-QVariantMap AbstractRemoteLinuxDeployService::exportDeployTimes() const
-{
- return d->deployTimes.exportDeployTimes();
-}
-
-void AbstractRemoteLinuxDeployService::importDeployTimes(const QVariantMap &map)
-{
- d->deployTimes.importDeployTimes(map);
-}
-
-
-
-AbstractRemoteLinuxDeployStep::AbstractRemoteLinuxDeployStep(BuildStepList *bsl, Utils::Id id)
- : BuildStep(bsl, id), d(new Internal::AbstractRemoteLinuxDeployStepPrivate)
-{
-}
-
void AbstractRemoteLinuxDeployStep::setInternalInitializer(const std::function<CheckResult ()> &init)
{
d->internalInit = init;
@@ -165,36 +88,23 @@ void AbstractRemoteLinuxDeployStep::setRunPreparer(const std::function<void ()>
d->runPreparer = prep;
}
-void AbstractRemoteLinuxDeployStep::setDeployService(AbstractRemoteLinuxDeployService *service)
-{
- d->deployService = service;
-}
-
-AbstractRemoteLinuxDeployStep::~AbstractRemoteLinuxDeployStep()
-{
- delete d->deployService;
- delete d;
-}
-
bool AbstractRemoteLinuxDeployStep::fromMap(const QVariantMap &map)
{
if (!BuildStep::fromMap(map))
return false;
- d->deployService->importDeployTimes(map);
+ d->deployTimes.importDeployTimes(map);
return true;
}
QVariantMap AbstractRemoteLinuxDeployStep::toMap() const
{
QVariantMap map = BuildStep::toMap();
- map.insert(d->deployService->exportDeployTimes());
+ map.insert(d->deployTimes.exportDeployTimes());
return map;
}
bool AbstractRemoteLinuxDeployStep::init()
{
- d->deployService->setTarget(target());
-
QTC_ASSERT(d->internalInit, return false);
const CheckResult canDeploy = d->internalInit();
if (!canDeploy) {
@@ -209,21 +119,31 @@ void AbstractRemoteLinuxDeployStep::doRun()
if (d->runPreparer)
d->runPreparer();
- connect(d->deployService, &AbstractRemoteLinuxDeployService::errorMessage,
- this, &AbstractRemoteLinuxDeployStep::handleErrorMessage);
- connect(d->deployService, &AbstractRemoteLinuxDeployService::progressMessage,
- this, &AbstractRemoteLinuxDeployStep::handleProgressMessage);
- connect(d->deployService, &AbstractRemoteLinuxDeployService::warningMessage,
- this, &AbstractRemoteLinuxDeployStep::handleWarningMessage);
- connect(d->deployService, &AbstractRemoteLinuxDeployService::stdOutData,
- this, &AbstractRemoteLinuxDeployStep::handleStdOutData);
- connect(d->deployService, &AbstractRemoteLinuxDeployService::stdErrData,
- this, &AbstractRemoteLinuxDeployStep::handleStdErrData);
- connect(d->deployService, &AbstractRemoteLinuxDeployService::finished,
- this, &AbstractRemoteLinuxDeployStep::handleFinished);
-
d->hasError = false;
- d->deployService->start();
+
+ QTC_ASSERT(!d->m_taskTree, return);
+
+ const CheckResult check = isDeploymentPossible();
+ if (!check) {
+ addErrorMessage(check.errorMessage());
+ handleFinished();
+ return;
+ }
+
+ if (!isDeploymentNecessary()) {
+ addProgressMessage(Tr::tr("No deployment action necessary. Skipping."));
+ handleFinished();
+ return;
+ }
+
+ d->m_taskTree.reset(new TaskTree(deployRecipe()));
+ const auto endHandler = [this] {
+ d->m_taskTree.release()->deleteLater();
+ handleFinished();
+ };
+ connect(d->m_taskTree.get(), &TaskTree::done, this, endHandler);
+ connect(d->m_taskTree.get(), &TaskTree::errorOccurred, this, endHandler);
+ d->m_taskTree->start();
}
void AbstractRemoteLinuxDeployStep::doCancel()
@@ -234,22 +154,26 @@ void AbstractRemoteLinuxDeployStep::doCancel()
emit addOutput(Tr::tr("User requests deployment to stop; cleaning up."),
OutputFormat::NormalMessage);
d->hasError = true;
- d->deployService->stop();
+
+ if (!d->m_taskTree)
+ return;
+ d->m_taskTree.reset();
+ handleFinished();
}
-void AbstractRemoteLinuxDeployStep::handleProgressMessage(const QString &message)
+void AbstractRemoteLinuxDeployStep::addProgressMessage(const QString &message)
{
emit addOutput(message, OutputFormat::NormalMessage);
}
-void AbstractRemoteLinuxDeployStep::handleErrorMessage(const QString &message)
+void AbstractRemoteLinuxDeployStep::addErrorMessage(const QString &message)
{
emit addOutput(message, OutputFormat::ErrorMessage);
emit addTask(DeploymentTask(Task::Error, message), 1); // TODO correct?
d->hasError = true;
}
-void AbstractRemoteLinuxDeployStep::handleWarningMessage(const QString &message)
+void AbstractRemoteLinuxDeployStep::addWarningMessage(const QString &message)
{
emit addOutput(message, OutputFormat::ErrorMessage);
emit addTask(DeploymentTask(Task::Warning, message), 1); // TODO correct?
@@ -261,7 +185,7 @@ void AbstractRemoteLinuxDeployStep::handleFinished()
emit addOutput(Tr::tr("Deploy step failed."), OutputFormat::ErrorMessage);
else
emit addOutput(Tr::tr("Deploy step finished."), OutputFormat::NormalMessage);
- disconnect(d->deployService, nullptr, this, nullptr);
+
emit finished(!d->hasError);
}
@@ -275,4 +199,14 @@ void AbstractRemoteLinuxDeployStep::handleStdErrData(const QString &data)
emit addOutput(data, OutputFormat::Stderr, DontAppendNewline);
}
+bool AbstractRemoteLinuxDeployStep::isDeploymentNecessary() const
+{
+ return true;
+}
+
+Tasking::Group AbstractRemoteLinuxDeployStep::deployRecipe()
+{
+ return {};
+}
+
} // namespace RemoteLinux
diff --git a/src/plugins/remotelinux/abstractremotelinuxdeploystep.h b/src/plugins/remotelinux/abstractremotelinuxdeploystep.h
index 67855064bb0..5b4eb8bbe92 100644
--- a/src/plugins/remotelinux/abstractremotelinuxdeploystep.h
+++ b/src/plugins/remotelinux/abstractremotelinuxdeploystep.h
@@ -8,55 +8,15 @@
#include <projectexplorer/buildstep.h>
#include <projectexplorer/devicesupport/idevicefwd.h>
-#include <QtCore/qcontainerfwd.h>
#include <QObject>
-namespace ProjectExplorer {
-class DeployableFile;
-class Kit;
-class Target;
-}
+namespace ProjectExplorer { class DeployableFile; }
namespace Utils::Tasking { class Group; }
namespace RemoteLinux {
-class AbstractRemoteLinuxDeployService;
-class CheckResult;
-
namespace Internal { class AbstractRemoteLinuxDeployStepPrivate; }
-namespace Internal { class AbstractRemoteLinuxDeployServicePrivate; }
-
-class REMOTELINUX_EXPORT AbstractRemoteLinuxDeployStep : public ProjectExplorer::BuildStep
-{
- Q_OBJECT
-
-public:
- ~AbstractRemoteLinuxDeployStep() override;
-
-protected:
- bool fromMap(const QVariantMap &map) override;
- QVariantMap toMap() const override;
- bool init() override;
- void doRun() final;
- void doCancel() override;
-
- explicit AbstractRemoteLinuxDeployStep(ProjectExplorer::BuildStepList *bsl, Utils::Id id);
-
- void setInternalInitializer(const std::function<CheckResult()> &init);
- void setRunPreparer(const std::function<void()> &prep);
- void setDeployService(AbstractRemoteLinuxDeployService *service);
-
-private:
- void handleProgressMessage(const QString &message);
- void handleErrorMessage(const QString &message);
- void handleWarningMessage(const QString &message);
- void handleFinished();
- void handleStdOutData(const QString &data);
- void handleStdErrData(const QString &data);
-
- Internal::AbstractRemoteLinuxDeployStepPrivate *d;
-};
class REMOTELINUX_EXPORT CheckResult
{
@@ -74,36 +34,28 @@ private:
QString m_error;
};
-class REMOTELINUX_EXPORT AbstractRemoteLinuxDeployService : public QObject
+class REMOTELINUX_EXPORT AbstractRemoteLinuxDeployStep : public ProjectExplorer::BuildStep
{
- Q_OBJECT
- Q_DISABLE_COPY(AbstractRemoteLinuxDeployService)
public:
- explicit AbstractRemoteLinuxDeployService(QObject *parent = nullptr);
- ~AbstractRemoteLinuxDeployService() override;
-
- void setTarget(ProjectExplorer::Target *bc);
-
- void start();
- void stop();
+ explicit AbstractRemoteLinuxDeployStep(ProjectExplorer::BuildStepList *bsl, Utils::Id id);
+ ~AbstractRemoteLinuxDeployStep() override;
- QVariantMap exportDeployTimes() const;
- void importDeployTimes(const QVariantMap &map);
+ ProjectExplorer::IDeviceConstPtr deviceConfiguration() const;
virtual CheckResult isDeploymentPossible() const;
-signals:
- void errorMessage(const QString &message);
- void progressMessage(const QString &message);
- void warningMessage(const QString &message);
- void stdOutData(const QString &data);
- void stdErrData(const QString &data);
- void finished(); // Used by Qnx.
+ void handleStdOutData(const QString &data);
+ void handleStdErrData(const QString &data);
protected:
- const ProjectExplorer::Target *target() const;
- const ProjectExplorer::Kit *kit() const;
- ProjectExplorer::IDeviceConstPtr deviceConfiguration() const;
+ bool fromMap(const QVariantMap &map) override;
+ QVariantMap toMap() const override;
+ bool init() override;
+ void doRun() final;
+ void doCancel() override;
+
+ void setInternalInitializer(const std::function<CheckResult()> &init);
+ void setRunPreparer(const std::function<void()> &prep);
void saveDeploymentTimeStamp(const ProjectExplorer::DeployableFile &deployableFile,
const QDateTime &remoteTimestamp);
@@ -111,11 +63,17 @@ protected:
bool hasRemoteFileChanged(const ProjectExplorer::DeployableFile &deployableFile,
const QDateTime &remoteTimestamp) const;
+ void addProgressMessage(const QString &message);
+ void addErrorMessage(const QString &message);
+ void addWarningMessage(const QString &message);
+
+ void handleFinished();
+
private:
- virtual bool isDeploymentNecessary() const = 0;
- virtual Utils::Tasking::Group deployRecipe() = 0;
+ virtual bool isDeploymentNecessary() const;
+ virtual Utils::Tasking::Group deployRecipe();
- Internal::AbstractRemoteLinuxDeployServicePrivate * const d;
+ Internal::AbstractRemoteLinuxDeployStepPrivate *d;
};
} // RemoteLinux
diff --git a/src/plugins/remotelinux/customcommanddeploystep.cpp b/src/plugins/remotelinux/customcommanddeploystep.cpp
index b7abdd83044..db8f9988b2b 100644
--- a/src/plugins/remotelinux/customcommanddeploystep.cpp
+++ b/src/plugins/remotelinux/customcommanddeploystep.cpp
@@ -19,87 +19,71 @@ using namespace Utils::Tasking;
namespace RemoteLinux::Internal {
-class CustomCommandDeployService : public AbstractRemoteLinuxDeployService
+class CustomCommandDeployStep : public AbstractRemoteLinuxDeployStep
{
public:
- void setCommandLine(const QString &commandLine);
- CheckResult isDeploymentPossible() const final;
+ CustomCommandDeployStep(BuildStepList *bsl, Id id)
+ : AbstractRemoteLinuxDeployStep(bsl, id)
+ {
+ auto commandLine = addAspect<StringAspect>();
+ commandLine->setSettingsKey("RemoteLinuxCustomCommandDeploymentStep.CommandLine");
+ commandLine->setLabelText(Tr::tr("Command line:"));
+ commandLine->setDisplayStyle(StringAspect::LineEditDisplay);
+ commandLine->setHistoryCompleter("RemoteLinuxCustomCommandDeploymentStep.History");
-protected:
- Group deployRecipe() final;
+ setInternalInitializer([this, commandLine] {
+ m_commandLine = commandLine->value().trimmed();
+ return isDeploymentPossible();
+ });
+
+ addMacroExpander();
+ }
+
+ CheckResult isDeploymentPossible() const final;
private:
- bool isDeploymentNecessary() const final { return true; }
+ Group deployRecipe() final;
QString m_commandLine;
};
-void CustomCommandDeployService::setCommandLine(const QString &commandLine)
-{
- m_commandLine = commandLine;
-}
-
-CheckResult CustomCommandDeployService::isDeploymentPossible() const
+CheckResult CustomCommandDeployStep::isDeploymentPossible() const
{
if (m_commandLine.isEmpty())
return CheckResult::failure(Tr::tr("No command line given."));
- return AbstractRemoteLinuxDeployService::isDeploymentPossible();
+ return AbstractRemoteLinuxDeployStep::isDeploymentPossible();
}
-Group CustomCommandDeployService::deployRecipe()
+Group CustomCommandDeployStep::deployRecipe()
{
const auto setupHandler = [this](QtcProcess &process) {
- emit progressMessage(Tr::tr("Starting remote command \"%1\"...").arg(m_commandLine));
+ addProgressMessage(Tr::tr("Starting remote command \"%1\"...").arg(m_commandLine));
process.setCommand({deviceConfiguration()->filePath("/bin/sh"),
{"-c", m_commandLine}});
QtcProcess *proc = &process;
connect(proc, &QtcProcess::readyReadStandardOutput, this, [this, proc] {
- emit stdOutData(proc->readAllStandardOutput());
+ handleStdOutData(proc->readAllStandardOutput());
});
connect(proc, &QtcProcess::readyReadStandardError, this, [this, proc] {
- emit stdErrData(proc->readAllStandardError());
+ handleStdErrData(proc->readAllStandardError());
});
};
const auto doneHandler = [this](const QtcProcess &) {
- emit progressMessage(Tr::tr("Remote command finished successfully."));
+ addProgressMessage(Tr::tr("Remote command finished successfully."));
};
const auto errorHandler = [this](const QtcProcess &process) {
if (process.error() != QProcess::UnknownError
|| process.exitStatus() != QProcess::NormalExit) {
- emit errorMessage(Tr::tr("Remote process failed: %1").arg(process.errorString()));
+ addErrorMessage(Tr::tr("Remote process failed: %1").arg(process.errorString()));
} else if (process.exitCode() != 0) {
- emit errorMessage(Tr::tr("Remote process finished with exit code %1.")
+ addErrorMessage(Tr::tr("Remote process finished with exit code %1.")
.arg(process.exitCode()));
}
};
return Group { Process(setupHandler, doneHandler, errorHandler) };
}
-class CustomCommandDeployStep : public AbstractRemoteLinuxDeployStep
-{
-public:
- CustomCommandDeployStep(BuildStepList *bsl, Id id)
- : AbstractRemoteLinuxDeployStep(bsl, id)
- {
- auto service = new CustomCommandDeployService;
- setDeployService(service);
-
- auto commandLine = addAspect<StringAspect>();
- commandLine->setSettingsKey("RemoteLinuxCustomCommandDeploymentStep.CommandLine");
- commandLine->setLabelText(Tr::tr("Command line:"));
- commandLine->setDisplayStyle(StringAspect::LineEditDisplay);
- commandLine->setHistoryCompleter("RemoteLinuxCustomCommandDeploymentStep.History");
-
- setInternalInitializer([service, commandLine] {
- service->setCommandLine(commandLine->value().trimmed());
- return service->isDeploymentPossible();
- });
-
- addMacroExpander();
- }
-};
-
// CustomCommandDeployStepFactory
diff --git a/src/plugins/remotelinux/filesystemaccess_test.cpp b/src/plugins/remotelinux/filesystemaccess_test.cpp
index fcc8477974a..13e46619ff3 100644
--- a/src/plugins/remotelinux/filesystemaccess_test.cpp
+++ b/src/plugins/remotelinux/filesystemaccess_test.cpp
@@ -9,6 +9,8 @@
#include <projectexplorer/devicesupport/filetransfer.h>
#include <projectexplorer/devicesupport/sshparameters.h>
#include <utils/filepath.h>
+#include <utils/filestreamer.h>
+#include <utils/filestreamermanager.h>
#include <utils/processinterface.h>
#include <QDebug>
@@ -86,6 +88,45 @@ void FileSystemAccessTest::initTestCase()
QVERIFY(!filePath.exists());
QVERIFY(filePath.createDir());
QVERIFY(filePath.exists());
+
+ const QString streamerLocalDir("streamerLocalDir");
+ const QString streamerRemoteDir("streamerRemoteDir");
+ const QString sourceDir("source");
+ const QString destDir("dest");
+ const QString localDir("local");
+ const QString remoteDir("remote");
+ const FilePath localRoot;
+ const FilePath remoteRoot = m_device->rootPath();
+ const FilePath localTempDir = *localRoot.tmpDir();
+ const FilePath remoteTempDir = *remoteRoot.tmpDir();
+ m_localStreamerDir = localTempDir / streamerLocalDir;
+ m_remoteStreamerDir = remoteTempDir / streamerRemoteDir;
+ m_localSourceDir = m_localStreamerDir / sourceDir;
+ m_remoteSourceDir = m_remoteStreamerDir / sourceDir;
+ m_localDestDir = m_localStreamerDir / destDir;
+ m_remoteDestDir = m_remoteStreamerDir / destDir;
+ m_localLocalDestDir = m_localDestDir / localDir;
+ m_localRemoteDestDir = m_localDestDir / remoteDir;
+ m_remoteLocalDestDir = m_remoteDestDir / localDir;
+ m_remoteRemoteDestDir = m_remoteDestDir / remoteDir;
+
+ QVERIFY(m_localSourceDir.createDir());
+ QVERIFY(m_remoteSourceDir.createDir());
+ QVERIFY(m_localDestDir.createDir());
+ QVERIFY(m_remoteDestDir.createDir());
+ QVERIFY(m_localLocalDestDir.createDir());
+ QVERIFY(m_localRemoteDestDir.createDir());
+ QVERIFY(m_remoteLocalDestDir.createDir());
+ QVERIFY(m_remoteRemoteDestDir.createDir());
+
+ QVERIFY(m_localSourceDir.exists());
+ QVERIFY(m_remoteSourceDir.exists());
+ QVERIFY(m_localDestDir.exists());
+ QVERIFY(m_remoteDestDir.exists());
+ QVERIFY(m_localLocalDestDir.exists());
+ QVERIFY(m_localRemoteDestDir.exists());
+ QVERIFY(m_remoteLocalDestDir.exists());
+ QVERIFY(m_remoteRemoteDestDir.exists());
}
void FileSystemAccessTest::cleanupTestCase()
@@ -94,6 +135,14 @@ void FileSystemAccessTest::cleanupTestCase()
return;
QVERIFY(baseFilePath().exists());
QVERIFY(baseFilePath().removeRecursively());
+
+ QVERIFY(m_localStreamerDir.removeRecursively());
+ QVERIFY(m_remoteStreamerDir.removeRecursively());
+
+ QVERIFY(!m_localStreamerDir.exists());
+ QVERIFY(!m_remoteStreamerDir.exists());
+
+ FileStreamerManager::stopAll();
}
void FileSystemAccessTest::testCreateRemoteFile_data()
@@ -102,13 +151,13 @@ void FileSystemAccessTest::testCreateRemoteFile_data()
QTest::newRow("Spaces") << QByteArray("Line with spaces");
QTest::newRow("Newlines") << QByteArray("Some \n\n newlines \n");
- QTest::newRow("Carriage return") << QByteArray("Line with carriage \r return");
+ QTest::newRow("CarriageReturn") << QByteArray("Line with carriage \r return");
QTest::newRow("Tab") << QByteArray("Line with \t tab");
QTest::newRow("Apostrophe") << QByteArray("Line with apostrophe's character");
- QTest::newRow("Quotation marks") << QByteArray("Line with \"quotation marks\"");
- QTest::newRow("Backslash 1") << QByteArray("Line with \\ backslash");
- QTest::newRow("Backslash 2") << QByteArray("Line with \\\" backslash");
- QTest::newRow("Command output") << QByteArray("The date is: $(date +%D)");
+ QTest::newRow("QuotationMarks") << QByteArray("Line with \"quotation marks\"");
+ QTest::newRow("Backslash1") << QByteArray("Line with \\ backslash");
+ QTest::newRow("Backslash2") << QByteArray("Line with \\\" backslash");
+ QTest::newRow("CommandOutput") << QByteArray("The date is: $(date +%D)");
const int charSize = sizeof(char) * 0x100;
QByteArray charString(charSize, Qt::Uninitialized);
@@ -201,6 +250,8 @@ void FileSystemAccessTest::testFileTransfer_data()
QTest::addColumn<FileTransferMethod>("fileTransferMethod");
QTest::addRow("Sftp") << FileTransferMethod::Sftp;
+ // TODO: By default rsync doesn't support creating target directories,
+ // needs to be done manually - see RsyncDeployService.
// QTest::addRow("Rsync") << FileTransferMethod::Rsync;
}
@@ -282,5 +333,320 @@ void FileSystemAccessTest::testFileTransfer()
QVERIFY2(remoteDir.removeRecursively(&errorString), qPrintable(errorString));
}
+void FileSystemAccessTest::testFileStreamer_data()
+{
+ QTest::addColumn<QString>("fileName");
+ QTest::addColumn<QByteArray>("data");
+
+ const QByteArray spaces("Line with spaces");
+ const QByteArray newlines("Some \n\n newlines \n");
+ const QByteArray carriageReturn("Line with carriage \r return");
+ const QByteArray tab("Line with \t tab");
+ const QByteArray apostrophe("Line with apostrophe's character");
+ const QByteArray quotationMarks("Line with \"quotation marks\"");
+ const QByteArray backslash1("Line with \\ backslash");
+ const QByteArray backslash2("Line with \\\" backslash");
+ const QByteArray commandOutput("The date is: $(date +%D)");
+
+ const int charSize = sizeof(char) * 0x100;
+ QByteArray charString(charSize, Qt::Uninitialized);
+ char *data = charString.data();
+ for (int c = 0; c < charSize; ++c)
+ data[c] = c;
+
+ const int bigSize = 1024 * 1024; // = 256 * 1024 * 1024 = 268.435.456 bytes
+ QByteArray bigString;
+ for (int i = 0; i < bigSize; ++i)
+ bigString += charString;
+
+ QTest::newRow("Spaces") << QString("spaces") << spaces;
+ QTest::newRow("Newlines") << QString("newlines") << newlines;
+ QTest::newRow("CarriageReturn") << QString("carriageReturn") << carriageReturn;
+ QTest::newRow("Tab") << QString("tab") << tab;
+ QTest::newRow("Apostrophe") << QString("apostrophe") << apostrophe;
+ QTest::newRow("QuotationMarks") << QString("quotationMarks") << quotationMarks;
+ QTest::newRow("Backslash1") << QString("backslash1") << backslash1;
+ QTest::newRow("Backslash2") << QString("backslash2") << backslash2;
+ QTest::newRow("CommandOutput") << QString("commandOutput") << commandOutput;
+ QTest::newRow("AllCharacters") << QString("charString") << charString;
+ QTest::newRow("BigString") << QString("bigString") << bigString;
+}
+
+void FileSystemAccessTest::testFileStreamer()
+{
+ QElapsedTimer timer;
+ timer.start();
+
+ QFETCH(QString, fileName);
+ QFETCH(QByteArray, data);
+
+ const FilePath localSourcePath = m_localSourceDir / fileName;
+ const FilePath remoteSourcePath = m_remoteSourceDir / fileName;
+ const FilePath localLocalDestPath = m_localDestDir / "local" / fileName;
+ const FilePath localRemoteDestPath = m_localDestDir / "remote" / fileName;
+ const FilePath remoteLocalDestPath = m_remoteDestDir / "local" / fileName;
+ const FilePath remoteRemoteDestPath = m_remoteDestDir / "remote" / fileName;
+
+ localSourcePath.removeFile();
+ remoteSourcePath.removeFile();
+ localLocalDestPath.removeFile();
+ localRemoteDestPath.removeFile();
+ remoteLocalDestPath.removeFile();
+ remoteRemoteDestPath.removeFile();
+
+ QVERIFY(!localSourcePath.exists());
+ QVERIFY(!remoteSourcePath.exists());
+ QVERIFY(!localLocalDestPath.exists());
+ QVERIFY(!localRemoteDestPath.exists());
+ QVERIFY(!remoteLocalDestPath.exists());
+ QVERIFY(!remoteRemoteDestPath.exists());
+
+ std::optional<QByteArray> localData;
+ std::optional<QByteArray> remoteData;
+ std::optional<QByteArray> localLocalData;
+ std::optional<QByteArray> localRemoteData;
+ std::optional<QByteArray> remoteLocalData;
+ std::optional<QByteArray> remoteRemoteData;
+
+ using namespace Tasking;
+
+ const auto localWriter = [&] {
+ const auto setup = [&](FileStreamer &streamer) {
+ streamer.setStreamMode(StreamMode::Writer);
+ streamer.setDestination(localSourcePath);
+ streamer.setWriteData(data);
+ };
+ return Streamer(setup);
+ };
+ const auto remoteWriter = [&] {
+ const auto setup = [&](FileStreamer &streamer) {
+ streamer.setStreamMode(StreamMode::Writer);
+ streamer.setDestination(remoteSourcePath);
+ streamer.setWriteData(data);
+ };
+ return Streamer(setup);
+ };
+ const auto localReader = [&] {
+ const auto setup = [&](FileStreamer &streamer) {
+ streamer.setStreamMode(StreamMode::Reader);
+ streamer.setSource(localSourcePath);
+ };
+ const auto onDone = [&](const FileStreamer &streamer) {
+ localData = streamer.readData();
+ };
+ return Streamer(setup, onDone);
+ };
+ const auto remoteReader = [&] {
+ const auto setup = [&](FileStreamer &streamer) {
+ streamer.setStreamMode(StreamMode::Reader);
+ streamer.setSource(remoteSourcePath);
+ };
+ const auto onDone = [&](const FileStreamer &streamer) {
+ remoteData = streamer.readData();
+ };
+ return Streamer(setup, onDone);
+ };
+ const auto transfer = [](const FilePath &source, const FilePath &dest,
+ std::optional<QByteArray> *result) {
+ const auto setupTransfer = [=](FileStreamer &streamer) {
+ streamer.setSource(source);
+ streamer.setDestination(dest);
+ };
+ const auto setupReader = [=](FileStreamer &streamer) {
+ streamer.setStreamMode(StreamMode::Reader);
+ streamer.setSource(dest);
+ };
+ const auto onReaderDone = [result](const FileStreamer &streamer) {
+ *result = streamer.readData();
+ };
+ const Group root {
+ Streamer(setupTransfer),
+ Streamer(setupReader, onReaderDone)
+ };
+ return root;
+ };
+
+ // In total: 5 local reads, 3 local writes, 5 remote reads, 3 remote writes
+ const Group root {
+ Group {
+ parallel,
+ localWriter(),
+ remoteWriter()
+ },
+ Group {
+ parallel,
+ localReader(),
+ remoteReader()
+ },
+ Group {
+ parallel,
+ transfer(localSourcePath, localLocalDestPath, &localLocalData),
+ transfer(remoteSourcePath, localRemoteDestPath, &localRemoteData),
+ transfer(localSourcePath, remoteLocalDestPath, &remoteLocalData),
+ transfer(remoteSourcePath, remoteRemoteDestPath, &remoteRemoteData),
+ }
+ };
+
+ QEventLoop eventLoop;
+ TaskTree taskTree(root);
+ int doneCount = 0;
+ int errorCount = 0;
+ connect(&taskTree, &TaskTree::done, this, [&doneCount, &eventLoop] {
+ ++doneCount;
+ eventLoop.quit();
+ });
+ connect(&taskTree, &TaskTree::errorOccurred, this, [&errorCount, &eventLoop] {
+ ++errorCount;
+ eventLoop.quit();
+ });
+ taskTree.start();
+ QVERIFY(taskTree.isRunning());
+
+ QTimer timeoutTimer;
+ bool timedOut = false;
+ connect(&timeoutTimer, &QTimer::timeout, &eventLoop, [&eventLoop, &timedOut] {
+ timedOut = true;
+ eventLoop.quit();
+ });
+ timeoutTimer.setInterval(10000);
+ timeoutTimer.setSingleShot(true);
+ timeoutTimer.start();
+ eventLoop.exec();
+ QCOMPARE(timedOut, false);
+ QCOMPARE(taskTree.isRunning(), false);
+ QCOMPARE(doneCount, 1);
+ QCOMPARE(errorCount, 0);
+
+ QVERIFY(localData);
+ QCOMPARE(*localData, data);
+ QVERIFY(remoteData);
+ QCOMPARE(*remoteData, data);
+
+ QVERIFY(localLocalData);
+ QCOMPARE(*localLocalData, data);
+ QVERIFY(localRemoteData);
+ QCOMPARE(*localRemoteData, data);
+ QVERIFY(remoteLocalData);
+ QCOMPARE(*remoteLocalData, data);
+ QVERIFY(remoteRemoteData);
+ QCOMPARE(*remoteRemoteData, data);
+
+ qDebug() << "Elapsed time:" << timer.elapsed() << "ms.";
+}
+
+void FileSystemAccessTest::testFileStreamerManager_data()
+{
+ testFileStreamer_data();
+}
+
+void FileSystemAccessTest::testFileStreamerManager()
+{
+ QElapsedTimer timer;
+ timer.start();
+
+ QFETCH(QString, fileName);
+ QFETCH(QByteArray, data);
+
+ const FilePath localSourcePath = m_localSourceDir / fileName;
+ const FilePath remoteSourcePath = m_remoteSourceDir / fileName;
+ const FilePath localLocalDestPath = m_localDestDir / "local" / fileName;
+ const FilePath localRemoteDestPath = m_localDestDir / "remote" / fileName;
+ const FilePath remoteLocalDestPath = m_remoteDestDir / "local" / fileName;
+ const FilePath remoteRemoteDestPath = m_remoteDestDir / "remote" / fileName;
+
+ localSourcePath.removeFile();
+ remoteSourcePath.removeFile();
+ localLocalDestPath.removeFile();
+ localRemoteDestPath.removeFile();
+ remoteLocalDestPath.removeFile();
+ remoteRemoteDestPath.removeFile();
+
+ QVERIFY(!localSourcePath.exists());
+ QVERIFY(!remoteSourcePath.exists());
+ QVERIFY(!localLocalDestPath.exists());
+ QVERIFY(!localRemoteDestPath.exists());
+ QVERIFY(!remoteLocalDestPath.exists());
+ QVERIFY(!remoteRemoteDestPath.exists());
+
+ std::optional<QByteArray> localData;
+ std::optional<QByteArray> remoteData;
+ std::optional<QByteArray> localLocalData;
+ std::optional<QByteArray> localRemoteData;
+ std::optional<QByteArray> remoteLocalData;
+ std::optional<QByteArray> remoteRemoteData;
+
+ QEventLoop eventLoop1;
+ QEventLoop *loop = &eventLoop1;
+ int counter = 0;
+ int *hitCount = &counter;
+
+ const auto writeAndRead = [hitCount, loop, data](const FilePath &destination,
+ std::optional<QByteArray> *result) {
+ const auto onWrite = [hitCount, loop, destination, result]
+ (const expected_str<qint64> &writeResult) {
+ QVERIFY(writeResult);
+ const auto onRead = [hitCount, loop, result]
+ (const expected_str<QByteArray> &readResult) {
+ QVERIFY(readResult);
+ *result = *readResult;
+ ++(*hitCount);
+ if (*hitCount == 2)
+ loop->quit();
+ };
+ FileStreamerManager::read(destination, onRead);
+ };
+ FileStreamerManager::write(destination, data, onWrite);
+ };
+
+ writeAndRead(localSourcePath, &localData);
+ writeAndRead(remoteSourcePath, &remoteData);
+ loop->exec();
+
+ QVERIFY(localData);
+ QCOMPARE(*localData, data);
+ QVERIFY(remoteData);
+ QCOMPARE(*remoteData, data);
+
+ QEventLoop eventLoop2;
+ loop = &eventLoop2;
+ counter = 0;
+
+ const auto transferAndRead = [hitCount, loop, data](const FilePath &source,
+ const FilePath &destination,
+ std::optional<QByteArray> *result) {
+ const auto onTransfer = [hitCount, loop, destination, result]
+ (const expected_str<void> &transferResult) {
+ QVERIFY(transferResult);
+ const auto onRead = [hitCount, loop, result]
+ (const expected_str<QByteArray> &readResult) {
+ QVERIFY(readResult);
+ *result = *readResult;
+ ++(*hitCount);
+ if (*hitCount == 4)
+ loop->quit();
+ };
+ FileStreamerManager::read(destination, onRead);
+ };
+ FileStreamerManager::copy(source, destination, onTransfer);
+ };
+
+ transferAndRead(localSourcePath, localLocalDestPath, &localLocalData);
+ transferAndRead(remoteSourcePath, localRemoteDestPath, &localRemoteData);
+ transferAndRead(localSourcePath, remoteLocalDestPath, &remoteLocalData);
+ transferAndRead(remoteSourcePath, remoteRemoteDestPath, &remoteRemoteData);
+ loop->exec();
+
+ QVERIFY(localLocalData);
+ QCOMPARE(*localLocalData, data);
+ QVERIFY(localRemoteData);
+ QCOMPARE(*localRemoteData, data);
+ QVERIFY(remoteLocalData);
+ QCOMPARE(*remoteLocalData, data);
+ QVERIFY(remoteRemoteData);
+ QCOMPARE(*remoteRemoteData, data);
+
+ qDebug() << "Elapsed time:" << timer.elapsed() << "ms.";
+}
+
} // Internal
} // RemoteLinux
diff --git a/src/plugins/remotelinux/filesystemaccess_test.h b/src/plugins/remotelinux/filesystemaccess_test.h
index 9684cbc9264..84321db6ce8 100644
--- a/src/plugins/remotelinux/filesystemaccess_test.h
+++ b/src/plugins/remotelinux/filesystemaccess_test.h
@@ -4,6 +4,7 @@
#pragma once
#include <projectexplorer/devicesupport/idevicefactory.h>
+#include <utils/filepath.h>
namespace RemoteLinux {
namespace Internal {
@@ -28,6 +29,10 @@ private slots:
void testFileActions();
void testFileTransfer_data();
void testFileTransfer();
+ void testFileStreamer_data();
+ void testFileStreamer();
+ void testFileStreamerManager_data();
+ void testFileStreamerManager();
void cleanupTestCase();
@@ -35,6 +40,16 @@ private:
TestLinuxDeviceFactory m_testLinuxDeviceFactory;
bool m_skippedAtWhole = false;
ProjectExplorer::IDeviceConstPtr m_device;
+ Utils::FilePath m_localStreamerDir;
+ Utils::FilePath m_remoteStreamerDir;
+ Utils::FilePath m_localSourceDir;
+ Utils::FilePath m_remoteSourceDir;
+ Utils::FilePath m_localDestDir;
+ Utils::FilePath m_remoteDestDir;
+ Utils::FilePath m_localLocalDestDir;
+ Utils::FilePath m_localRemoteDestDir;
+ Utils::FilePath m_remoteLocalDestDir;
+ Utils::FilePath m_remoteRemoteDestDir;
};
} // Internal
diff --git a/src/plugins/remotelinux/genericdirectuploadservice.cpp b/src/plugins/remotelinux/genericdirectuploadservice.cpp
deleted file mode 100644
index 6cd3c799376..00000000000
--- a/src/plugins/remotelinux/genericdirectuploadservice.cpp
+++ /dev/null
@@ -1,314 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#include "genericdirectuploadservice.h"
-
-#include "remotelinuxtr.h"
-
-#include <projectexplorer/deployablefile.h>
-#include <projectexplorer/devicesupport/filetransfer.h>
-#include <projectexplorer/devicesupport/idevice.h>
-
-#include <utils/hostosinfo.h>
-#include <utils/processinterface.h>
-#include <utils/qtcassert.h>
-#include <utils/qtcprocess.h>
-
-#include <QDateTime>
-#include <QDir>
-
-using namespace ProjectExplorer;
-using namespace Utils;
-using namespace Utils::Tasking;
-
-namespace RemoteLinux {
-namespace Internal {
-
-const int MaxConcurrentStatCalls = 10;
-
-struct UploadStorage
-{
- QList<DeployableFile> filesToUpload;
-};
-
-class GenericDirectUploadServicePrivate
-{
-public:
- GenericDirectUploadServicePrivate(GenericDirectUploadService *service) : q(service) {}
-
- QDateTime timestampFromStat(const DeployableFile &file, QtcProcess *statProc);
-
- using FilesToStat = std::function<QList<DeployableFile>(UploadStorage *)>;
- using StatEndHandler
- = std::function<void(UploadStorage *, const DeployableFile &, const QDateTime &)>;
- TaskItem statTask(UploadStorage *storage, const DeployableFile &file,
- StatEndHandler statEndHandler);
- TaskItem statTree(const TreeStorage<UploadStorage> &storage, FilesToStat filesToStat,
- StatEndHandler statEndHandler);
- TaskItem uploadTask(const TreeStorage<UploadStorage> &storage);
- TaskItem chmodTask(const DeployableFile &file);
- TaskItem chmodTree(const TreeStorage<UploadStorage> &storage);
-
- GenericDirectUploadService *q = nullptr;
- IncrementalDeployment incremental = IncrementalDeployment::NotSupported;
- bool ignoreMissingFiles = false;
- QList<DeployableFile> deployableFiles;
-};
-
-QList<DeployableFile> collectFilesToUpload(const DeployableFile &deployable)
-{
- QList<DeployableFile> collected;
- FilePath localFile = deployable.localFilePath();
- if (localFile.isDir()) {
- const FilePaths files = localFile.dirEntries(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
- const QString remoteDir = deployable.remoteDirectory() + '/' + localFile.fileName();
- for (const FilePath &localFilePath : files)
- collected.append(collectFilesToUpload(DeployableFile(localFilePath, remoteDir)));
- } else {
- collected << deployable;
- }
- return collected;
-}
-
-} // namespace Internal
-
-using namespace Internal;
-
-GenericDirectUploadService::GenericDirectUploadService(QObject *parent)
- : AbstractRemoteLinuxDeployService(parent), d(new GenericDirectUploadServicePrivate(this))
-{
-}
-
-GenericDirectUploadService::~GenericDirectUploadService()
-{
- delete d;
-}
-
-void GenericDirectUploadService::setDeployableFiles(const QList<DeployableFile> &deployableFiles)
-{
- d->deployableFiles = deployableFiles;
-}
-
-void GenericDirectUploadService::setIncrementalDeployment(IncrementalDeployment incremental)
-{
- d->incremental = incremental;
-}
-
-void GenericDirectUploadService::setIgnoreMissingFiles(bool ignoreMissingFiles)
-{
- d->ignoreMissingFiles = ignoreMissingFiles;
-}
-
-bool GenericDirectUploadService::isDeploymentNecessary() const
-{
- QList<DeployableFile> collected;
- for (int i = 0; i < d->deployableFiles.count(); ++i)
- collected.append(collectFilesToUpload(d->deployableFiles.at(i)));
-
- QTC_CHECK(collected.size() >= d->deployableFiles.size());
- d->deployableFiles = collected;
- return !d->deployableFiles.isEmpty();
-}
-
-QDateTime GenericDirectUploadServicePrivate::timestampFromStat(const DeployableFile &file,
- QtcProcess *statProc)
-{
- bool succeeded = false;
- QString error;
- if (statProc->error() == QProcess::FailedToStart) {
- error = Tr::tr("Failed to start \"stat\": %1").arg(statProc->errorString());
- } else if (statProc->exitStatus() == QProcess::CrashExit) {
- error = Tr::tr("\"stat\" crashed.");
- } else if (statProc->exitCode() != 0) {
- error = Tr::tr("\"stat\" failed with exit code %1: %2")
- .arg(statProc->exitCode()).arg(statProc->cleanedStdErr());
- } else {
- succeeded = true;
- }
- if (!succeeded) {
- emit q->warningMessage(Tr::tr("Failed to retrieve remote timestamp for file \"%1\". "
- "Incremental deployment will not work. Error message was: %2")
- .arg(file.remoteFilePath(), error));
- return {};
- }
- const QByteArray output = statProc->readAllRawStandardOutput().trimmed();
- const QString warningString(Tr::tr("Unexpected stat output for remote file \"%1\": %2")
- .arg(file.remoteFilePath()).arg(QString::fromUtf8(output)));
- if (!output.startsWith(file.remoteFilePath().toUtf8())) {
- emit q->warningMessage(warningString);
- return {};
- }
- const QByteArrayList columns = output.mid(file.remoteFilePath().toUtf8().size() + 1).split(' ');
- if (columns.size() < 14) { // Normal Linux stat: 16 columns in total, busybox stat: 15 columns
- emit q->warningMessage(warningString);
- return {};
- }
- bool isNumber;
- const qint64 secsSinceEpoch = columns.at(11).toLongLong(&isNumber);
- if (!isNumber) {
- emit q->warningMessage(warningString);
- return {};
- }
- return QDateTime::fromSecsSinceEpoch(secsSinceEpoch);
-}
-
-TaskItem GenericDirectUploadServicePrivate::statTask(UploadStorage *storage,
- const DeployableFile &file,
- StatEndHandler statEndHandler)
-{
- const auto setupHandler = [=](QtcProcess &process) {
- // We'd like to use --format=%Y, but it's not supported by busybox.
- process.setCommand({q->deviceConfiguration()->filePath("stat"),
- {"-t", Utils::ProcessArgs::quoteArgUnix(file.remoteFilePath())}});
- };
- const auto endHandler = [=](const QtcProcess &process) {
- QtcProcess *proc = const_cast<QtcProcess *>(&process);
- const QDateTime timestamp = timestampFromStat(file, proc);
- statEndHandler(storage, file, timestamp);
- };
- return Process(setupHandler, endHandler, endHandler);
-}
-
-TaskItem GenericDirectUploadServicePrivate::statTree(const TreeStorage<UploadStorage> &storage,
- FilesToStat filesToStat, StatEndHandler statEndHandler)
-{
- const auto setupHandler = [=](TaskTree &tree) {
- UploadStorage *storagePtr = storage.activeStorage();
- const QList<DeployableFile> files = filesToStat(storagePtr);
- QList<TaskItem> statList{optional, ParallelLimit(MaxConcurrentStatCalls)};
- for (const DeployableFile &file : std::as_const(files)) {
- QTC_ASSERT(file.isValid(), continue);
- statList.append(statTask(storagePtr, file, statEndHandler));
- }
- tree.setupRoot({statList});
- };
- return Tree(setupHandler);
-}
-
-TaskItem GenericDirectUploadServicePrivate::uploadTask(const TreeStorage<UploadStorage> &storage)
-{
- const auto setupHandler = [this, storage](FileTransfer &transfer) {
- if (storage->filesToUpload.isEmpty()) {
- emit q->progressMessage(Tr::tr("No files need to be uploaded."));
- return TaskAction::StopWithDone;
- }
- emit q->progressMessage(Tr::tr("%n file(s) need to be uploaded.", "",
- storage->filesToUpload.size()));
- FilesToTransfer files;
- for (const DeployableFile &file : std::as_const(storage->filesToUpload)) {
- if (!file.localFilePath().exists()) {
- const QString message = Tr::tr("Local file \"%1\" does not exist.")
- .arg(file.localFilePath().toUserOutput());
- if (ignoreMissingFiles) {
- emit q->warningMessage(message);
- continue;
- }
- emit q->errorMessage(message);
- return TaskAction::StopWithError;
- }
- files.append({file.localFilePath(),
- q->deviceConfiguration()->filePath(file.remoteFilePath())});
- }
- if (files.isEmpty()) {
- emit q->progressMessage(Tr::tr("No files need to be uploaded."));
- return TaskAction::StopWithDone;
- }
- transfer.setFilesToTransfer(files);
- QObject::connect(&transfer, &FileTransfer::progress,
- q, &GenericDirectUploadService::progressMessage);
- return TaskAction::Continue;
- };
- const auto errorHandler = [this](const FileTransfer &transfer) {
- emit q->errorMessage(transfer.resultData().m_errorString);
- };
-
- return Transfer(setupHandler, {}, errorHandler);
-}
-
-TaskItem GenericDirectUploadServicePrivate::chmodTask(const DeployableFile &file)
-{
- const auto setupHandler = [=](QtcProcess &process) {
- process.setCommand({q->deviceConfiguration()->filePath("chmod"),
- {"a+x", Utils::ProcessArgs::quoteArgUnix(file.remoteFilePath())}});
- };
- const auto errorHandler = [=](const QtcProcess &process) {
- const QString error = process.errorString();
- if (!error.isEmpty()) {
- emit q->warningMessage(Tr::tr("Remote chmod failed for file \"%1\": %2")
- .arg(file.remoteFilePath(), error));
- } else if (process.exitCode() != 0) {
- emit q->warningMessage(Tr::tr("Remote chmod failed for file \"%1\": %2")
- .arg(file.remoteFilePath(), process.cleanedStdErr()));
- }
- };
- return Process(setupHandler, {}, errorHandler);
-}
-
-TaskItem GenericDirectUploadServicePrivate::chmodTree(const TreeStorage<UploadStorage> &storage)
-{
- const auto setupChmodHandler = [=](TaskTree &tree) {
- QList<DeployableFile> filesToChmod;
- for (const DeployableFile &file : std::as_const(storage->filesToUpload)) {
- if (file.isExecutable())
- filesToChmod << file;
- }
- QList<TaskItem> chmodList{optional, ParallelLimit(MaxConcurrentStatCalls)};
- for (const DeployableFile &file : std::as_const(filesToChmod)) {
- QTC_ASSERT(file.isValid(), continue);
- chmodList.append(chmodTask(file));
- }
- tree.setupRoot({chmodList});
- };
- return Tree(setupChmodHandler);
-}
-
-Group GenericDirectUploadService::deployRecipe()
-{
- const auto preFilesToStat = [this](UploadStorage *storage) {
- QList<DeployableFile> filesToStat;
- for (const DeployableFile &file : std::as_const(d->deployableFiles)) {
- if (d->incremental != IncrementalDeployment::Enabled || hasLocalFileChanged(file)) {
- storage->filesToUpload.append(file);
- continue;
- }
- if (d->incremental == IncrementalDeployment::NotSupported)
- continue;
- filesToStat << file;
- }
- return filesToStat;
- };
- const auto preStatEndHandler = [this](UploadStorage *storage, const DeployableFile &file,
- const QDateTime &timestamp) {
- if (!timestamp.isValid() || hasRemoteFileChanged(file, timestamp))
- storage->filesToUpload.append(file);
- };
-
- const auto postFilesToStat = [this](UploadStorage *storage) {
- return d->incremental == IncrementalDeployment::NotSupported
- ? QList<DeployableFile>() : storage->filesToUpload;
- };
- const auto postStatEndHandler = [this](UploadStorage *storage, const DeployableFile &file,
- const QDateTime &timestamp) {
- Q_UNUSED(storage)
- if (timestamp.isValid())
- saveDeploymentTimeStamp(file, timestamp);
- };
- const auto doneHandler = [this] {
- emit progressMessage(Tr::tr("All files successfully deployed."));
- };
-
- const TreeStorage<UploadStorage> storage;
- const Group root {
- Storage(storage),
- d->statTree(storage, preFilesToStat, preStatEndHandler),
- d->uploadTask(storage),
- Group {
- d->chmodTree(storage),
- d->statTree(storage, postFilesToStat, postStatEndHandler)
- },
- OnGroupDone(doneHandler)
- };
- return root;
-}
-
-} //namespace RemoteLinux
diff --git a/src/plugins/remotelinux/genericdirectuploadservice.h b/src/plugins/remotelinux/genericdirectuploadservice.h
deleted file mode 100644
index 31799f063ad..00000000000
--- a/src/plugins/remotelinux/genericdirectuploadservice.h
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright (C) 2016 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#pragma once
-
-#include "remotelinux_export.h"
-
-#include "abstractremotelinuxdeploystep.h"
-
-#include <QList>
-
-namespace ProjectExplorer { class DeployableFile; }
-
-namespace RemoteLinux {
-namespace Internal { class GenericDirectUploadServicePrivate; }
-
-enum class IncrementalDeployment { Enabled, Disabled, NotSupported };
-
-class REMOTELINUX_EXPORT GenericDirectUploadService : public AbstractRemoteLinuxDeployService
-{
- Q_OBJECT
-public:
- GenericDirectUploadService(QObject *parent = nullptr);
- ~GenericDirectUploadService();
-
- void setDeployableFiles(const QList<ProjectExplorer::DeployableFile> &deployableFiles);
- void setIncrementalDeployment(IncrementalDeployment incremental);
- void setIgnoreMissingFiles(bool ignoreMissingFiles);
-
-private:
- bool isDeploymentNecessary() const final;
- Utils::Tasking::Group deployRecipe() final;
-
- friend class Internal::GenericDirectUploadServicePrivate;
- Internal::GenericDirectUploadServicePrivate * const d;
-};
-
-} //namespace RemoteLinux
diff --git a/src/plugins/remotelinux/genericdirectuploadstep.cpp b/src/plugins/remotelinux/genericdirectuploadstep.cpp
index 2b778e2b9c8..8345f76e6d7 100644
--- a/src/plugins/remotelinux/genericdirectuploadstep.cpp
+++ b/src/plugins/remotelinux/genericdirectuploadstep.cpp
@@ -3,35 +3,301 @@
#include "genericdirectuploadstep.h"
-#include "genericdirectuploadservice.h"
#include "remotelinux_constants.h"
#include "remotelinuxtr.h"
+#include <projectexplorer/deployablefile.h>
#include <projectexplorer/deploymentdata.h>
-#include <projectexplorer/target.h>
+#include <projectexplorer/devicesupport/filetransfer.h>
+#include <projectexplorer/devicesupport/idevice.h>
#include <projectexplorer/runconfigurationaspects.h>
+#include <projectexplorer/target.h>
+
+#include <utils/hostosinfo.h>
+#include <utils/processinterface.h>
+#include <utils/qtcassert.h>
+#include <utils/qtcprocess.h>
+
+#include <QDateTime>
using namespace ProjectExplorer;
using namespace Utils;
+using namespace Utils::Tasking;
namespace RemoteLinux {
-GenericDirectUploadStep::GenericDirectUploadStep(BuildStepList *bsl, Utils::Id id,
- bool offerIncrementalDeployment)
- : AbstractRemoteLinuxDeployStep(bsl, id)
-{
- auto service = new GenericDirectUploadService;
- setDeployService(service);
-
- BoolAspect *incremental = nullptr;
- if (offerIncrementalDeployment) {
- incremental = addAspect<BoolAspect>();
- incremental->setSettingsKey("RemoteLinux.GenericDirectUploadStep.Incremental");
- incremental->setLabel(Tr::tr("Incremental deployment"),
- BoolAspect::LabelPlacement::AtCheckBox);
- incremental->setValue(true);
- incremental->setDefaultValue(true);
+const int MaxConcurrentStatCalls = 10;
+
+struct UploadStorage
+{
+ QList<DeployableFile> filesToUpload;
+};
+
+enum class IncrementalDeployment { Enabled, Disabled, NotSupported };
+
+class GenericDirectUploadStepPrivate
+{
+public:
+ GenericDirectUploadStepPrivate(GenericDirectUploadStep *parent)
+ : q(parent)
+ {}
+
+ QDateTime timestampFromStat(const DeployableFile &file, QtcProcess *statProc);
+
+ using FilesToStat = std::function<QList<DeployableFile>(UploadStorage *)>;
+ using StatEndHandler
+ = std::function<void(UploadStorage *, const DeployableFile &, const QDateTime &)>;
+ TaskItem statTask(UploadStorage *storage, const DeployableFile &file,
+ StatEndHandler statEndHandler);
+ TaskItem statTree(const TreeStorage<UploadStorage> &storage, FilesToStat filesToStat,
+ StatEndHandler statEndHandler);
+ TaskItem uploadTask(const TreeStorage<UploadStorage> &storage);
+ TaskItem chmodTask(const DeployableFile &file);
+ TaskItem chmodTree(const TreeStorage<UploadStorage> &storage);
+
+ GenericDirectUploadStep *q;
+ IncrementalDeployment m_incremental = IncrementalDeployment::NotSupported;
+ bool m_ignoreMissingFiles = false;
+ mutable QList<DeployableFile> m_deployableFiles;
+};
+
+QList<DeployableFile> collectFilesToUpload(const DeployableFile &deployable)
+{
+ QList<DeployableFile> collected;
+ FilePath localFile = deployable.localFilePath();
+ if (localFile.isDir()) {
+ const FilePaths files = localFile.dirEntries(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
+ const QString remoteDir = deployable.remoteDirectory() + '/' + localFile.fileName();
+ for (const FilePath &localFilePath : files)
+ collected.append(collectFilesToUpload(DeployableFile(localFilePath, remoteDir)));
+ } else {
+ collected << deployable;
}
+ return collected;
+}
+
+bool GenericDirectUploadStep::isDeploymentNecessary() const
+{
+ QList<DeployableFile> collected;
+ for (int i = 0; i < d->m_deployableFiles.count(); ++i)
+ collected.append(collectFilesToUpload(d->m_deployableFiles.at(i)));
+
+ QTC_CHECK(collected.size() >= d->m_deployableFiles.size());
+ d->m_deployableFiles = collected;
+ return !d->m_deployableFiles.isEmpty();
+}
+
+QDateTime GenericDirectUploadStepPrivate::timestampFromStat(const DeployableFile &file,
+ QtcProcess *statProc)
+{
+ bool succeeded = false;
+ QString error;
+ if (statProc->error() == QProcess::FailedToStart) {
+ error = Tr::tr("Failed to start \"stat\": %1").arg(statProc->errorString());
+ } else if (statProc->exitStatus() == QProcess::CrashExit) {
+ error = Tr::tr("\"stat\" crashed.");
+ } else if (statProc->exitCode() != 0) {
+ error = Tr::tr("\"stat\" failed with exit code %1: %2")
+ .arg(statProc->exitCode()).arg(statProc->cleanedStdErr());
+ } else {
+ succeeded = true;
+ }
+ if (!succeeded) {
+ q->addWarningMessage(Tr::tr("Failed to retrieve remote timestamp for file \"%1\". "
+ "Incremental deployment will not work. Error message was: %2")
+ .arg(file.remoteFilePath(), error));
+ return {};
+ }
+ const QByteArray output = statProc->readAllRawStandardOutput().trimmed();
+ const QString warningString(Tr::tr("Unexpected stat output for remote file \"%1\": %2")
+ .arg(file.remoteFilePath()).arg(QString::fromUtf8(output)));
+ if (!output.startsWith(file.remoteFilePath().toUtf8())) {
+ q->addWarningMessage(warningString);
+ return {};
+ }
+ const QByteArrayList columns = output.mid(file.remoteFilePath().toUtf8().size() + 1).split(' ');
+ if (columns.size() < 14) { // Normal Linux stat: 16 columns in total, busybox stat: 15 columns
+ q->addWarningMessage(warningString);
+ return {};
+ }
+ bool isNumber;
+ const qint64 secsSinceEpoch = columns.at(11).toLongLong(&isNumber);
+ if (!isNumber) {
+ q->addWarningMessage(warningString);
+ return {};
+ }
+ return QDateTime::fromSecsSinceEpoch(secsSinceEpoch);
+}
+
+TaskItem GenericDirectUploadStepPrivate::statTask(UploadStorage *storage,
+ const DeployableFile &file,
+ StatEndHandler statEndHandler)
+{
+ const auto setupHandler = [=](QtcProcess &process) {
+ // We'd like to use --format=%Y, but it's not supported by busybox.
+ process.setCommand({q->deviceConfiguration()->filePath("stat"),
+ {"-t", Utils::ProcessArgs::quoteArgUnix(file.remoteFilePath())}});
+ };
+ const auto endHandler = [=](const QtcProcess &process) {
+ QtcProcess *proc = const_cast<QtcProcess *>(&process);
+ const QDateTime timestamp = timestampFromStat(file, proc);
+ statEndHandler(storage, file, timestamp);
+ };
+ return Process(setupHandler, endHandler, endHandler);
+}
+
+TaskItem GenericDirectUploadStepPrivate::statTree(const TreeStorage<UploadStorage> &storage,
+ FilesToStat filesToStat, StatEndHandler statEndHandler)
+{
+ const auto setupHandler = [=](TaskTree &tree) {
+ UploadStorage *storagePtr = storage.activeStorage();
+ const QList<DeployableFile> files = filesToStat(storagePtr);
+ QList<TaskItem> statList{optional, ParallelLimit(MaxConcurrentStatCalls)};
+ for (const DeployableFile &file : std::as_const(files)) {
+ QTC_ASSERT(file.isValid(), continue);
+ statList.append(statTask(storagePtr, file, statEndHandler));
+ }
+ tree.setupRoot({statList});
+ };
+ return Tree(setupHandler);
+}
+
+TaskItem GenericDirectUploadStepPrivate::uploadTask(const TreeStorage<UploadStorage> &storage)
+{
+ const auto setupHandler = [this, storage](FileTransfer &transfer) {
+ if (storage->filesToUpload.isEmpty()) {
+ q->addProgressMessage(Tr::tr("No files need to be uploaded."));
+ return TaskAction::StopWithDone;
+ }
+ q->addProgressMessage(Tr::tr("%n file(s) need to be uploaded.", "",
+ storage->filesToUpload.size()));
+ FilesToTransfer files;
+ for (const DeployableFile &file : std::as_const(storage->filesToUpload)) {
+ if (!file.localFilePath().exists()) {
+ const QString message = Tr::tr("Local file \"%1\" does not exist.")
+ .arg(file.localFilePath().toUserOutput());
+ if (m_ignoreMissingFiles) {
+ q->addWarningMessage(message);
+ continue;
+ }
+ q->addErrorMessage(message);
+ return TaskAction::StopWithError;
+ }
+ files.append({file.localFilePath(),
+ q->deviceConfiguration()->filePath(file.remoteFilePath())});
+ }
+ if (files.isEmpty()) {
+ q->addProgressMessage(Tr::tr("No files need to be uploaded."));
+ return TaskAction::StopWithDone;
+ }
+ transfer.setFilesToTransfer(files);
+ QObject::connect(&transfer, &FileTransfer::progress,
+ q, &GenericDirectUploadStep::addProgressMessage);
+ return TaskAction::Continue;
+ };
+ const auto errorHandler = [this](const FileTransfer &transfer) {
+ q->addErrorMessage(transfer.resultData().m_errorString);
+ };
+
+ return Transfer(setupHandler, {}, errorHandler);
+}
+
+TaskItem GenericDirectUploadStepPrivate::chmodTask(const DeployableFile &file)
+{
+ const auto setupHandler = [=](QtcProcess &process) {
+ process.setCommand({q->deviceConfiguration()->filePath("chmod"),
+ {"a+x", Utils::ProcessArgs::quoteArgUnix(file.remoteFilePath())}});
+ };
+ const auto errorHandler = [=](const QtcProcess &process) {
+ const QString error = process.errorString();
+ if (!error.isEmpty()) {
+ q->addWarningMessage(Tr::tr("Remote chmod failed for file \"%1\": %2")
+ .arg(file.remoteFilePath(), error));
+ } else if (process.exitCode() != 0) {
+ q->addWarningMessage(Tr::tr("Remote chmod failed for file \"%1\": %2")
+ .arg(file.remoteFilePath(), process.cleanedStdErr()));
+ }
+ };
+ return Process(setupHandler, {}, errorHandler);
+}
+
+TaskItem GenericDirectUploadStepPrivate::chmodTree(const TreeStorage<UploadStorage> &storage)
+{
+ const auto setupChmodHandler = [=](TaskTree &tree) {
+ QList<DeployableFile> filesToChmod;
+ for (const DeployableFile &file : std::as_const(storage->filesToUpload)) {
+ if (file.isExecutable())
+ filesToChmod << file;
+ }
+ QList<TaskItem> chmodList{optional, ParallelLimit(MaxConcurrentStatCalls)};
+ for (const DeployableFile &file : std::as_const(filesToChmod)) {
+ QTC_ASSERT(file.isValid(), continue);
+ chmodList.append(chmodTask(file));
+ }
+ tree.setupRoot({chmodList});
+ };
+ return Tree(setupChmodHandler);
+}
+
+Group GenericDirectUploadStep::deployRecipe()
+{
+ const auto preFilesToStat = [this](UploadStorage *storage) {
+ QList<DeployableFile> filesToStat;
+ for (const DeployableFile &file : std::as_const(d->m_deployableFiles)) {
+ if (d->m_incremental != IncrementalDeployment::Enabled || hasLocalFileChanged(file)) {
+ storage->filesToUpload.append(file);
+ continue;
+ }
+ if (d->m_incremental == IncrementalDeployment::NotSupported)
+ continue;
+ filesToStat << file;
+ }
+ return filesToStat;
+ };
+ const auto preStatEndHandler = [this](UploadStorage *storage, const DeployableFile &file,
+ const QDateTime &timestamp) {
+ if (!timestamp.isValid() || hasRemoteFileChanged(file, timestamp))
+ storage->filesToUpload.append(file);
+ };
+
+ const auto postFilesToStat = [this](UploadStorage *storage) {
+ return d->m_incremental == IncrementalDeployment::NotSupported
+ ? QList<DeployableFile>() : storage->filesToUpload;
+ };
+ const auto postStatEndHandler = [this](UploadStorage *storage, const DeployableFile &file,
+ const QDateTime &timestamp) {
+ Q_UNUSED(storage)
+ if (timestamp.isValid())
+ saveDeploymentTimeStamp(file, timestamp);
+ };
+ const auto doneHandler = [this] {
+ addProgressMessage(Tr::tr("All files successfully deployed."));
+ };
+
+ const TreeStorage<UploadStorage> storage;
+ const Group root {
+ Storage(storage),
+ d->statTree(storage, preFilesToStat, preStatEndHandler),
+ d->uploadTask(storage),
+ Group {
+ d->chmodTree(storage),
+ d->statTree(storage, postFilesToStat, postStatEndHandler)
+ },
+ OnGroupDone(doneHandler)
+ };
+ return root;
+}
+
+GenericDirectUploadStep::GenericDirectUploadStep(BuildStepList *bsl, Utils::Id id)
+ : AbstractRemoteLinuxDeployStep(bsl, id),
+ d(new GenericDirectUploadStepPrivate(this))
+{
+ auto incremental = addAspect<BoolAspect>();
+ incremental->setSettingsKey("RemoteLinux.GenericDirectUploadStep.Incremental");
+ incremental->setLabel(Tr::tr("Incremental deployment"),
+ BoolAspect::LabelPlacement::AtCheckBox);
+ incremental->setValue(true);
+ incremental->setDefaultValue(true);
auto ignoreMissingFiles = addAspect<BoolAspect>();
ignoreMissingFiles->setSettingsKey("RemoteLinux.GenericDirectUploadStep.IgnoreMissingFiles");
@@ -39,23 +305,22 @@ GenericDirectUploadStep::GenericDirectUploadStep(BuildStepList *bsl, Utils::Id i
BoolAspect::LabelPlacement::AtCheckBox);
ignoreMissingFiles->setValue(false);
- setInternalInitializer([incremental, ignoreMissingFiles, service] {
- if (incremental) {
- service->setIncrementalDeployment(incremental->value()
- ? IncrementalDeployment::Enabled : IncrementalDeployment::Disabled);
- } else {
- service->setIncrementalDeployment(IncrementalDeployment::NotSupported);
- }
- service->setIgnoreMissingFiles(ignoreMissingFiles->value());
- return service->isDeploymentPossible();
+ setInternalInitializer([this, incremental, ignoreMissingFiles] {
+ d->m_incremental = incremental->value()
+ ? IncrementalDeployment::Enabled : IncrementalDeployment::Disabled;
+ d->m_ignoreMissingFiles = ignoreMissingFiles->value();
+ return isDeploymentPossible();
});
- setRunPreparer([this, service] {
- service->setDeployableFiles(target()->deploymentData().allFiles());
+ setRunPreparer([this] {
+ d->m_deployableFiles = target()->deploymentData().allFiles();
});
}
-GenericDirectUploadStep::~GenericDirectUploadStep() = default;
+GenericDirectUploadStep::~GenericDirectUploadStep()
+{
+ delete d;
+}
Utils::Id GenericDirectUploadStep::stepId()
{
@@ -67,4 +332,12 @@ QString GenericDirectUploadStep::displayName()
return Tr::tr("Upload files via SFTP");
}
+// Factory
+
+GenericDirectUploadStepFactory::GenericDirectUploadStepFactory()
+{
+ registerStep<GenericDirectUploadStep>(Constants::DirectUploadStepId);
+ setDisplayName(Tr::tr("Upload files via SFTP"));
+}
+
} //namespace RemoteLinux
diff --git a/src/plugins/remotelinux/genericdirectuploadstep.h b/src/plugins/remotelinux/genericdirectuploadstep.h
index 0c2fb1b67d8..048be324a89 100644
--- a/src/plugins/remotelinux/genericdirectuploadstep.h
+++ b/src/plugins/remotelinux/genericdirectuploadstep.h
@@ -14,12 +14,25 @@ class REMOTELINUX_EXPORT GenericDirectUploadStep : public AbstractRemoteLinuxDep
Q_OBJECT
public:
- GenericDirectUploadStep(ProjectExplorer::BuildStepList *bsl, Utils::Id id,
- bool offerIncrementalDeployment = true);
+ GenericDirectUploadStep(ProjectExplorer::BuildStepList *bsl, Utils::Id id);
~GenericDirectUploadStep() override;
+ bool isDeploymentNecessary() const final;
+ Utils::Tasking::Group deployRecipe() final;
+
static Utils::Id stepId();
static QString displayName();
+
+private:
+ friend class GenericDirectUploadStepPrivate;
+ class GenericDirectUploadStepPrivate *d;
+};
+
+class REMOTELINUX_EXPORT GenericDirectUploadStepFactory
+ : public ProjectExplorer::BuildStepFactory
+{
+public:
+ GenericDirectUploadStepFactory();
};
-} //namespace RemoteLinux
+} // RemoteLinux
diff --git a/src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.cpp b/src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.cpp
index 50c553437f6..d0c7cc1edc4 100644
--- a/src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.cpp
+++ b/src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.cpp
@@ -3,9 +3,11 @@
#include "genericlinuxdeviceconfigurationwidget.h"
+#include "remotelinux_constants.h"
#include "remotelinuxtr.h"
#include "sshkeycreationdialog.h"
+#include <projectexplorer/devicesupport/devicemanager.h>
#include <projectexplorer/devicesupport/idevice.h>
#include <projectexplorer/devicesupport/sshparameters.h>
@@ -16,6 +18,7 @@
#include <utils/utilsicons.h>
#include <QCheckBox>
+#include <QComboBox>
#include <QLabel>
#include <QLineEdit>
#include <QPushButton>
@@ -38,8 +41,9 @@ GenericLinuxDeviceConfigurationWidget::GenericLinuxDeviceConfigurationWidget(
m_keyButton = new QRadioButton(Tr::tr("Specific &key"));
- m_hostLineEdit = new QLineEdit(this);
+ m_hostLineEdit = new FancyLineEdit(this);
m_hostLineEdit->setPlaceholderText(Tr::tr("IP or host name of the device"));
+ m_hostLineEdit->setHistoryCompleter("HostName");
m_sshPortSpinBox = new QSpinBox(this);
m_sshPortSpinBox->setMinimum(0);
@@ -48,8 +52,9 @@ GenericLinuxDeviceConfigurationWidget::GenericLinuxDeviceConfigurationWidget(
m_hostKeyCheckBox = new QCheckBox(Tr::tr("&Check host key"));
- m_portsLineEdit = new QLineEdit(this);
+ m_portsLineEdit = new FancyLineEdit(this);
m_portsLineEdit->setToolTip(Tr::tr("You can enter lists and ranges like this: '1024,1026-1028,1030'."));
+ m_portsLineEdit->setHistoryCompleter("PortRange");
m_portsWarningLabel = new QLabel(this);
@@ -59,7 +64,8 @@ GenericLinuxDeviceConfigurationWidget::GenericLinuxDeviceConfigurationWidget(
m_timeoutSpinBox->setValue(1000);
m_timeoutSpinBox->setSuffix(Tr::tr("s"));
- m_userLineEdit = new QLineEdit(this);
+ m_userLineEdit = new FancyLineEdit(this);
+ m_userLineEdit->setHistoryCompleter("UserName");
m_keyLabel = new QLabel(Tr::tr("Private key file:"));
@@ -74,11 +80,26 @@ GenericLinuxDeviceConfigurationWidget::GenericLinuxDeviceConfigurationWidget(
m_gdbServerLineEdit->setExpectedKind(PathChooser::ExistingCommand);
m_gdbServerLineEdit->setPlaceholderText(hint);
m_gdbServerLineEdit->setToolTip(hint);
+ m_gdbServerLineEdit->setHistoryCompleter("GdbServer");
m_qmlRuntimeLineEdit = new PathChooser(this);
m_qmlRuntimeLineEdit->setExpectedKind(PathChooser::ExistingCommand);
m_qmlRuntimeLineEdit->setPlaceholderText(hint);
m_qmlRuntimeLineEdit->setToolTip(hint);
+ m_qmlRuntimeLineEdit->setHistoryCompleter("QmlRuntime");
+
+ m_sourceProfileCheckBox =
+ new QCheckBox(Tr::tr("Source %1 and %2").arg("/etc/profile").arg("$HOME/.profile"));
+
+ m_linkDeviceComboBox = new QComboBox;
+ m_linkDeviceComboBox->addItem(Tr::tr("Direct"), QVariant());
+
+ auto dm = DeviceManager::instance();
+ const int dmCount = dm->deviceCount();
+ for (int i = 0; i < dmCount; ++i) {
+ IDevice::ConstPtr dev = dm->deviceAt(i);
+ m_linkDeviceComboBox->addItem(dev->displayName(), dev->id().toSetting());
+ }
auto sshPortLabel = new QLabel(Tr::tr("&SSH port:"));
sshPortLabel->setBuddy(m_sshPortSpinBox);
@@ -93,7 +114,9 @@ GenericLinuxDeviceConfigurationWidget::GenericLinuxDeviceConfigurationWidget(
Tr::tr("&Username:"), m_userLineEdit, st, br,
m_keyLabel, m_keyFileLineEdit, createKeyButton, br,
Tr::tr("GDB server executable:"), m_gdbServerLineEdit, br,
- Tr::tr("QML runtime executable:"), m_qmlRuntimeLineEdit, br
+ Tr::tr("QML runtime executable:"), m_qmlRuntimeLineEdit, br,
+ QString(), m_sourceProfileCheckBox, br,
+ Tr::tr("Access via:"), m_linkDeviceComboBox
}.attachTo(this);
connect(m_hostLineEdit, &QLineEdit::editingFinished,
@@ -124,6 +147,10 @@ GenericLinuxDeviceConfigurationWidget::GenericLinuxDeviceConfigurationWidget(
this, &GenericLinuxDeviceConfigurationWidget::qmlRuntimeEditingFinished);
connect(m_hostKeyCheckBox, &QCheckBox::toggled,
this, &GenericLinuxDeviceConfigurationWidget::hostKeyCheckingChanged);
+ connect(m_sourceProfileCheckBox, &QCheckBox::toggled,
+ this, &GenericLinuxDeviceConfigurationWidget::sourceProfileCheckingChanged);
+ connect(m_linkDeviceComboBox, &QComboBox::currentIndexChanged,
+ this, &GenericLinuxDeviceConfigurationWidget::linkDeviceChanged);
initGui();
}
@@ -214,6 +241,17 @@ void GenericLinuxDeviceConfigurationWidget::hostKeyCheckingChanged(bool doCheck)
device()->setSshParameters(sshParams);
}
+void GenericLinuxDeviceConfigurationWidget::sourceProfileCheckingChanged(bool doCheck)
+{
+ device()->setExtraData(Constants::SourceProfile, doCheck);
+}
+
+void GenericLinuxDeviceConfigurationWidget::linkDeviceChanged(int index)
+{
+ const QVariant deviceId = m_linkDeviceComboBox->itemData(index);
+ device()->setExtraData(Constants::LinkDevice, deviceId);
+}
+
void GenericLinuxDeviceConfigurationWidget::updateDeviceFromUi()
{
hostNameEditingFinished();
@@ -223,6 +261,10 @@ void GenericLinuxDeviceConfigurationWidget::updateDeviceFromUi()
keyFileEditingFinished();
handleFreePortsChanged();
gdbServerEditingFinished();
+ sshPortEditingFinished();
+ timeoutEditingFinished();
+ sourceProfileCheckingChanged(m_sourceProfileCheckBox->isChecked());
+ linkDeviceChanged(m_linkDeviceComboBox->currentIndex());
qmlRuntimeEditingFinished();
}
@@ -261,6 +303,17 @@ void GenericLinuxDeviceConfigurationWidget::initGui()
m_hostLineEdit->setEnabled(!device()->isAutoDetected());
m_sshPortSpinBox->setEnabled(!device()->isAutoDetected());
m_hostKeyCheckBox->setChecked(sshParams.hostKeyCheckingMode != SshHostKeyCheckingNone);
+ m_sourceProfileCheckBox->setChecked(device()->extraData(Constants::SourceProfile).toBool());
+ Id linkDeviceId = Id::fromSetting(device()->extraData(Constants::LinkDevice));
+ auto dm = DeviceManager::instance();
+ int found = -1;
+ for (int i = 0, n = dm->deviceCount(); i < n; ++i) {
+ if (dm->deviceAt(i)->id() == linkDeviceId) {
+ found = i;
+ break;
+ }
+ }
+ m_linkDeviceComboBox->setCurrentIndex(found + 1); // There's the "Direct" entry first.
m_hostLineEdit->setText(sshParams.host());
m_sshPortSpinBox->setValue(sshParams.port());
diff --git a/src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.h b/src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.h
index 4e00cedcb62..aba251b969e 100644
--- a/src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.h
+++ b/src/plugins/remotelinux/genericlinuxdeviceconfigurationwidget.h
@@ -7,13 +7,14 @@
QT_BEGIN_NAMESPACE
class QCheckBox;
+class QComboBox;
class QLabel;
-class QLineEdit;
class QRadioButton;
class QSpinBox;
QT_END_NAMESPACE
namespace Utils {
+class FancyLineEdit;
class FilePath;
class PathChooser;
} // Utils
@@ -42,6 +43,8 @@ private:
void setPrivateKey(const Utils::FilePath &path);
void createNewKey();
void hostKeyCheckingChanged(bool doCheck);
+ void sourceProfileCheckingChanged(bool doCheck);
+ void linkDeviceChanged(int index);
void updateDeviceFromUi() override;
void updatePortsWarningLabel();
@@ -50,17 +53,19 @@ private:
QRadioButton *m_defaultAuthButton;
QLabel *m_keyLabel;
QRadioButton *m_keyButton;
- QLineEdit *m_hostLineEdit;
+ Utils::FancyLineEdit *m_hostLineEdit;
QSpinBox *m_sshPortSpinBox;
QCheckBox *m_hostKeyCheckBox;
- QLineEdit *m_portsLineEdit;
+ Utils::FancyLineEdit *m_portsLineEdit;
QLabel *m_portsWarningLabel;
- QLineEdit *m_userLineEdit;
+ Utils::FancyLineEdit *m_userLineEdit;
QSpinBox *m_timeoutSpinBox;
Utils::PathChooser *m_keyFileLineEdit;
QLabel *m_machineTypeValueLabel;
Utils::PathChooser *m_gdbServerLineEdit;
Utils::PathChooser *m_qmlRuntimeLineEdit;
+ QCheckBox *m_sourceProfileCheckBox;
+ QComboBox *m_linkDeviceComboBox;
};
} // RemoteLinux::Internal
diff --git a/src/plugins/remotelinux/genericlinuxdeviceconfigurationwizard.cpp b/src/plugins/remotelinux/genericlinuxdeviceconfigurationwizard.cpp
index 4de31d898de..7bd047a590e 100644
--- a/src/plugins/remotelinux/genericlinuxdeviceconfigurationwizard.cpp
+++ b/src/plugins/remotelinux/genericlinuxdeviceconfigurationwizard.cpp
@@ -11,7 +11,6 @@
#include <projectexplorer/devicesupport/idevice.h>
#include <projectexplorer/devicesupport/sshparameters.h>
-#include <utils/portlist.h>
#include <utils/fileutils.h>
using namespace ProjectExplorer;
@@ -45,13 +44,6 @@ GenericLinuxDeviceConfigurationWizard::GenericLinuxDeviceConfigurationWizard(QWi
setPage(Internal::FinalPageId, &d->finalPage);
d->finalPage.setCommitPage(true);
d->device = LinuxDevice::create();
- d->device->setupId(IDevice::ManuallyAdded, Utils::Id());
- d->device->setType(Constants::GenericLinuxOsType);
- d->device->setMachineType(IDevice::Hardware);
- d->device->setFreePorts(Utils::PortList::fromString(QLatin1String("10000-10100")));
- SshParameters sshParams;
- sshParams.timeout = 10;
- d->device->setSshParameters(sshParams);
d->setupPage.setDevice(d->device);
d->keyDeploymentPage.setDevice(d->device);
}
diff --git a/src/plugins/remotelinux/genericlinuxdeviceconfigurationwizardpages.cpp b/src/plugins/remotelinux/genericlinuxdeviceconfigurationwizardpages.cpp
index 89e4c2e4838..0a34683bdeb 100644
--- a/src/plugins/remotelinux/genericlinuxdeviceconfigurationwizardpages.cpp
+++ b/src/plugins/remotelinux/genericlinuxdeviceconfigurationwizardpages.cpp
@@ -9,14 +9,15 @@
#include <projectexplorer/devicesupport/sshparameters.h>
+#include <utils/filepath.h>
#include <utils/fileutils.h>
+#include <utils/fancylineedit.h>
#include <utils/layoutbuilder.h>
#include <utils/pathchooser.h>
#include <utils/utilsicons.h>
#include <QHBoxLayout>
#include <QLabel>
-#include <QLineEdit>
#include <QPushButton>
#include <QSpinBox>
@@ -29,10 +30,10 @@ namespace Internal {
class GenericLinuxDeviceConfigurationWizardSetupPagePrivate
{
public:
- QLineEdit *nameLineEdit;
- QLineEdit *hostNameLineEdit;
+ FancyLineEdit *nameLineEdit;
+ FancyLineEdit *hostNameLineEdit;
QSpinBox *sshPortSpinBox;
- QLineEdit *userNameLineEdit;
+ FancyLineEdit *userNameLineEdit;
LinuxDevice::Ptr device;
};
@@ -52,10 +53,16 @@ GenericLinuxDeviceConfigurationWizardSetupPage::GenericLinuxDeviceConfigurationW
setTitle(Tr::tr("Connection"));
setWindowTitle(Tr::tr("WizardPage"));
- d->nameLineEdit = new QLineEdit(this);
- d->hostNameLineEdit = new QLineEdit(this);
+ d->nameLineEdit = new FancyLineEdit(this);
+ d->nameLineEdit->setHistoryCompleter("DeviceName");
+
+ d->hostNameLineEdit = new FancyLineEdit(this);
+ d->hostNameLineEdit->setHistoryCompleter("HostName");
+
d->sshPortSpinBox = new QSpinBox(this);
- d->userNameLineEdit = new QLineEdit(this);
+
+ d->userNameLineEdit = new FancyLineEdit(this);
+ d->userNameLineEdit->setHistoryCompleter("UserName");
using namespace Layouting;
Form {
@@ -97,7 +104,9 @@ bool GenericLinuxDeviceConfigurationWizardSetupPage::validatePage()
{
d->device->setDisplayName(configurationName());
SshParameters sshParams = d->device->sshParameters();
- sshParams.url = url();
+ sshParams.setHost(d->hostNameLineEdit->text().trimmed());
+ sshParams.setUserName(d->userNameLineEdit->text().trimmed());
+ sshParams.setPort(d->sshPortSpinBox->value());
d->device->setSshParameters(sshParams);
return true;
}
@@ -107,15 +116,6 @@ QString GenericLinuxDeviceConfigurationWizardSetupPage::configurationName() cons
return d->nameLineEdit->text().trimmed();
}
-QUrl GenericLinuxDeviceConfigurationWizardSetupPage::url() const
-{
- QUrl url;
- url.setHost(d->hostNameLineEdit->text().trimmed());
- url.setUserName(d->userNameLineEdit->text().trimmed());
- url.setPort(d->sshPortSpinBox->value());
- return url;
-}
-
void GenericLinuxDeviceConfigurationWizardSetupPage::setDevice(const LinuxDevice::Ptr &device)
{
d->device = device;
diff --git a/src/plugins/remotelinux/genericlinuxdeviceconfigurationwizardpages.h b/src/plugins/remotelinux/genericlinuxdeviceconfigurationwizardpages.h
index 6e5e00119ad..2e2d5298913 100644
--- a/src/plugins/remotelinux/genericlinuxdeviceconfigurationwizardpages.h
+++ b/src/plugins/remotelinux/genericlinuxdeviceconfigurationwizardpages.h
@@ -32,7 +32,6 @@ private:
bool validatePage() override;
QString configurationName() const;
- QUrl url() const;
Internal::GenericLinuxDeviceConfigurationWizardSetupPagePrivate * const d;
};
@@ -64,7 +63,7 @@ class REMOTELINUX_EXPORT GenericLinuxDeviceConfigurationWizardFinalPage final :
{
Q_OBJECT
public:
- GenericLinuxDeviceConfigurationWizardFinalPage(QWidget *parent);
+ GenericLinuxDeviceConfigurationWizardFinalPage(QWidget *parent = nullptr);
~GenericLinuxDeviceConfigurationWizardFinalPage() override;
protected:
diff --git a/src/plugins/remotelinux/killappstep.cpp b/src/plugins/remotelinux/killappstep.cpp
index 03473d95a23..d82844fa157 100644
--- a/src/plugins/remotelinux/killappstep.cpp
+++ b/src/plugins/remotelinux/killappstep.cpp
@@ -20,10 +20,21 @@ using namespace Utils::Tasking;
namespace RemoteLinux::Internal {
-class KillAppService : public AbstractRemoteLinuxDeployService
+class KillAppStep : public AbstractRemoteLinuxDeployStep
{
public:
- void setRemoteExecutable(const FilePath &filePath) { m_remoteExecutable = filePath; }
+ KillAppStep(BuildStepList *bsl, Id id) : AbstractRemoteLinuxDeployStep(bsl, id)
+ {
+ setWidgetExpandedByDefault(false);
+
+ setInternalInitializer([this] {
+ Target * const theTarget = target();
+ QTC_ASSERT(theTarget, return CheckResult::failure());
+ RunConfiguration * const rc = theTarget->activeRunConfiguration();
+ m_remoteExecutable = rc ? rc->runnable().command.executable() : FilePath();
+ return CheckResult::success();
+ });
+ }
private:
bool isDeploymentNecessary() const final { return !m_remoteExecutable.isEmpty(); }
@@ -32,44 +43,23 @@ private:
FilePath m_remoteExecutable;
};
-Group KillAppService::deployRecipe()
+Group KillAppStep::deployRecipe()
{
const auto setupHandler = [this](DeviceProcessKiller &killer) {
killer.setProcessPath(m_remoteExecutable);
- emit progressMessage(Tr::tr("Trying to kill \"%1\" on remote device...")
- .arg(m_remoteExecutable.path()));
+ addProgressMessage(Tr::tr("Trying to kill \"%1\" on remote device...")
+ .arg(m_remoteExecutable.path()));
};
const auto doneHandler = [this](const DeviceProcessKiller &) {
- emit progressMessage(Tr::tr("Remote application killed."));
+ addProgressMessage(Tr::tr("Remote application killed."));
};
const auto errorHandler = [this](const DeviceProcessKiller &) {
- emit progressMessage(Tr::tr("Failed to kill remote application. "
+ addProgressMessage(Tr::tr("Failed to kill remote application. "
"Assuming it was not running."));
};
return Group { Killer(setupHandler, doneHandler, errorHandler) };
}
-class KillAppStep : public AbstractRemoteLinuxDeployStep
-{
-public:
- KillAppStep(BuildStepList *bsl, Id id) : AbstractRemoteLinuxDeployStep(bsl, id)
- {
- auto service = new Internal::KillAppService;
- setDeployService(service);
-
- setWidgetExpandedByDefault(false);
-
- setInternalInitializer([this, service] {
- Target * const theTarget = target();
- QTC_ASSERT(theTarget, return CheckResult::failure());
- RunConfiguration * const rc = theTarget->activeRunConfiguration();
- const FilePath remoteExe = rc ? rc->runnable().command.executable() : FilePath();
- service->setRemoteExecutable(remoteExe);
- return CheckResult::success();
- });
- }
-};
-
KillAppStepFactory::KillAppStepFactory()
{
registerStep<KillAppStep>(Constants::KillAppStepId);
diff --git a/src/plugins/remotelinux/linuxdevice.cpp b/src/plugins/remotelinux/linuxdevice.cpp
index 5d0d2d6b92e..615ac56f4c7 100644
--- a/src/plugins/remotelinux/linuxdevice.cpp
+++ b/src/plugins/remotelinux/linuxdevice.cpp
@@ -11,14 +11,14 @@
#include "remotelinux_constants.h"
#include "remotelinuxsignaloperation.h"
#include "remotelinuxtr.h"
-#include "sshprocessinterface.h"
#include <coreplugin/icore.h>
#include <coreplugin/messagemanager.h>
+#include <projectexplorer/devicesupport/devicemanager.h>
#include <projectexplorer/devicesupport/filetransfer.h>
#include <projectexplorer/devicesupport/filetransferinterface.h>
-#include <projectexplorer/devicesupport/sshdeviceprocesslist.h>
+#include <projectexplorer/devicesupport/processlist.h>
#include <projectexplorer/devicesupport/sshparameters.h>
#include <projectexplorer/devicesupport/sshsettings.h>
@@ -28,6 +28,7 @@
#include <utils/environment.h>
#include <utils/hostosinfo.h>
#include <utils/port.h>
+#include <utils/portlist.h>
#include <utils/processinfo.h>
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
@@ -51,9 +52,6 @@ namespace RemoteLinux {
const QByteArray s_pidMarker = "__qtc";
-const char Delimiter0[] = "x--";
-const char Delimiter1[] = "---";
-
static Q_LOGGING_CATEGORY(linuxDeviceLog, "qtc.remotelinux.device", QtWarningMsg);
#define DEBUG(x) qCDebug(linuxDeviceLog) << x << '\n'
@@ -276,77 +274,6 @@ private:
IDevice::ConstPtr m_device;
};
-static QString visualizeNull(QString s)
-{
- return s.replace(QLatin1Char('\0'), QLatin1String("<null>"));
-}
-
-class LinuxDeviceProcessList : public SshDeviceProcessList
-{
-public:
- LinuxDeviceProcessList(const IDevice::ConstPtr &device, QObject *parent)
- : SshDeviceProcessList(device, parent)
- {
- }
-
-private:
- QString listProcessesCommandLine() const override
- {
- return QString::fromLatin1(
- "for dir in `ls -d /proc/[0123456789]*`; do "
- "test -d $dir || continue;" // Decrease the likelihood of a race condition.
- "echo $dir;"
- "cat $dir/cmdline;echo;" // cmdline does not end in newline
- "cat $dir/stat;"
- "readlink $dir/exe;"
- "printf '%1''%2';"
- "done").arg(QLatin1String(Delimiter0)).arg(QLatin1String(Delimiter1));
- }
-
- QList<ProcessInfo> buildProcessList(const QString &listProcessesReply) const override
- {
- QList<ProcessInfo> processes;
- const QStringList lines = listProcessesReply.split(QString::fromLatin1(Delimiter0)
- + QString::fromLatin1(Delimiter1), Qt::SkipEmptyParts);
- for (const QString &line : lines) {
- const QStringList elements = line.split(QLatin1Char('\n'));
- if (elements.count() < 4) {
- qDebug("%s: Expected four list elements, got %d. Line was '%s'.", Q_FUNC_INFO,
- int(elements.count()), qPrintable(visualizeNull(line)));
- continue;
- }
- bool ok;
- const int pid = elements.first().mid(6).toInt(&ok);
- if (!ok) {
- qDebug("%s: Expected number in %s. Line was '%s'.", Q_FUNC_INFO,
- qPrintable(elements.first()), qPrintable(visualizeNull(line)));
- continue;
- }
- QString command = elements.at(1);
- command.replace(QLatin1Char('\0'), QLatin1Char(' '));
- if (command.isEmpty()) {
- const QString &statString = elements.at(2);
- const int openParenPos = statString.indexOf(QLatin1Char('('));
- const int closedParenPos = statString.indexOf(QLatin1Char(')'), openParenPos);
- if (openParenPos == -1 || closedParenPos == -1)
- continue;
- command = QLatin1Char('[')
- + statString.mid(openParenPos + 1, closedParenPos - openParenPos - 1)
- + QLatin1Char(']');
- }
-
- ProcessInfo process;
- process.processId = pid;
- process.commandLine = command;
- process.executable = elements.at(3);
- processes.append(process);
- }
-
- return Utils::sorted(std::move(processes));
- }
-};
-
-
// LinuxDevicePrivate
class ShellThreadHandler;
@@ -382,6 +309,9 @@ public:
Environment getEnvironment();
void invalidateEnvironmentCache();
+ void checkOsType();
+ void queryOsType(std::function<RunResult(const CommandLine &)> run);
+
LinuxDevice *q = nullptr;
QThread m_shellThread;
ShellThreadHandler *m_handler = nullptr;
@@ -411,10 +341,7 @@ Environment LinuxDevicePrivate::getEnvironment()
return m_environmentCache.value();
QtcProcess getEnvProc;
- getEnvProc.setCommand({FilePath("env").onDevice(q->rootPath()), {}});
- Environment inEnv;
- inEnv.setCombineWithDeviceEnvironment(false);
- getEnvProc.setEnvironment(inEnv);
+ getEnvProc.setCommand({q->filePath("env"), {}});
getEnvProc.runBlocking();
const QString remoteOutput = getEnvProc.cleanedStdOut();
@@ -437,10 +364,8 @@ Environment LinuxDeviceFileAccess::deviceEnvironment() const
class SshProcessInterfacePrivate : public QObject
{
- Q_OBJECT
-
public:
- SshProcessInterfacePrivate(SshProcessInterface *sshInterface, LinuxDevicePrivate *devicePrivate);
+ SshProcessInterfacePrivate(SshProcessInterface *sshInterface, const IDevice::ConstPtr &device);
void start();
@@ -464,46 +389,32 @@ public:
IDevice::ConstPtr m_device;
std::unique_ptr<SshConnectionHandle> m_connectionHandle;
QtcProcess m_process;
- LinuxDevicePrivate *m_devicePrivate = nullptr;
QString m_socketFilePath;
SshParameters m_sshParameters;
+ IDevice::ConstPtr m_linkDevice;
+
bool m_connecting = false;
bool m_killed = false;
ProcessResultData m_result;
+
+ QByteArray m_output;
+ QByteArray m_error;
+ bool m_pidParsed = false;
+ bool m_useConnectionSharing = false;
};
-SshProcessInterface::SshProcessInterface(const LinuxDevice *linuxDevice)
- : d(new SshProcessInterfacePrivate(this, linuxDevice->d))
-{
-}
+SshProcessInterface::SshProcessInterface(const IDevice::ConstPtr &device)
+ : d(new SshProcessInterfacePrivate(this, device))
+{}
SshProcessInterface::~SshProcessInterface()
{
+ killIfRunning();
delete d;
}
-void SshProcessInterface::handleStarted(qint64 processId)
-{
- emitStarted(processId);
-}
-
-void SshProcessInterface::handleDone(const ProcessResultData &resultData)
-{
- emit done(resultData);
-}
-
-void SshProcessInterface::handleReadyReadStandardOutput(const QByteArray &outputData)
-{
- emit readyRead(outputData, {});
-}
-
-void SshProcessInterface::handleReadyReadStandardError(const QByteArray &errorData)
-{
- emit readyRead({}, errorData);
-}
-
void SshProcessInterface::emitStarted(qint64 processId)
{
d->m_processId = processId;
@@ -569,17 +480,7 @@ void SshProcessInterface::sendControlSignal(ControlSignal controlSignal)
handleSendControlSignal(controlSignal);
}
-LinuxProcessInterface::LinuxProcessInterface(const LinuxDevice *linuxDevice)
- : SshProcessInterface(linuxDevice)
-{
-}
-
-LinuxProcessInterface::~LinuxProcessInterface()
-{
- killIfRunning();
-}
-
-void LinuxProcessInterface::handleSendControlSignal(ControlSignal controlSignal)
+void SshProcessInterface::handleSendControlSignal(ControlSignal controlSignal)
{
QTC_ASSERT(controlSignal != ControlSignal::KickOff, return);
QTC_ASSERT(controlSignal != ControlSignal::CloseWriteChannel, return);
@@ -592,75 +493,58 @@ void LinuxProcessInterface::handleSendControlSignal(ControlSignal controlSignal)
runInShell(command);
}
-QString LinuxProcessInterface::fullCommandLine(const CommandLine &commandLine) const
+void SshProcessInterfacePrivate::handleStarted()
{
- CommandLine cmd;
-
- if (!commandLine.isEmpty()) {
- const QStringList rcFilesToSource = {"/etc/profile", "$HOME/.profile"};
- for (const QString &filePath : rcFilesToSource) {
- cmd.addArgs({"test", "-f", filePath});
- cmd.addArgs("&&", CommandLine::Raw);
- cmd.addArgs({".", filePath});
- cmd.addArgs(";", CommandLine::Raw);
- }
- }
-
- if (!m_setup.m_workingDirectory.isEmpty()) {
- cmd.addArgs({"cd", m_setup.m_workingDirectory.path()});
- cmd.addArgs("&&", CommandLine::Raw);
- }
-
- if (m_setup.m_terminalMode == TerminalMode::Off)
- cmd.addArgs(QString("echo ") + s_pidMarker + "$$" + s_pidMarker + " && ", CommandLine::Raw);
-
- const Environment &env = m_setup.m_environment;
- for (auto it = env.constBegin(); it != env.constEnd(); ++it)
- cmd.addArgs(env.key(it) + "='" + env.expandedValueForKey(env.key(it)) + '\'', CommandLine::Raw);
-
- if (m_setup.m_terminalMode == TerminalMode::Off)
- cmd.addArg("exec");
-
- if (!commandLine.isEmpty())
- cmd.addCommandLineAsArgs(commandLine, CommandLine::Raw);
- return cmd.arguments();
-}
+ const qint64 processId = m_process.usesTerminal() ? m_process.processId() : 0;
-void LinuxProcessInterface::handleStarted(qint64 processId)
-{
// Don't emit started() when terminal is off,
// it's being done later inside handleReadyReadStandardOutput().
- if (m_setup.m_terminalMode == TerminalMode::Off)
+ if (q->m_setup.m_terminalMode == TerminalMode::Off && !q->m_setup.m_ptyData)
return;
m_pidParsed = true;
- emitStarted(processId);
+ q->emitStarted(processId);
}
-void LinuxProcessInterface::handleDone(const ProcessResultData &resultData)
+void SshProcessInterfacePrivate::handleDone()
{
- ProcessResultData finalData = resultData;
+ if (m_connectionHandle) // TODO: should it disconnect from signals first?
+ m_connectionHandle.release()->deleteLater();
+
+ ProcessResultData finalData = m_process.resultData();
if (!m_pidParsed) {
finalData.m_error = QProcess::FailedToStart;
finalData.m_errorString = Utils::joinStrings({finalData.m_errorString,
QString::fromLocal8Bit(m_error)}, '\n');
}
- emit done(finalData);
+ emit q->done(finalData);
}
-void LinuxProcessInterface::handleReadyReadStandardOutput(const QByteArray &outputData)
+void SshProcessInterfacePrivate::handleReadyReadStandardOutput()
{
+ // By default this forwards readyRead immediately, but only buffers the
+ // output in case the start signal is not emitted yet.
+ // In case the pid can be parsed now, the delayed started() is
+ // emitted, and any previously buffered output emitted now.
+ const QByteArray outputData = m_process.readAllRawStandardOutput();
+
if (m_pidParsed) {
- emit readyRead(outputData, {});
+ emit q->readyRead(outputData, {});
return;
}
m_output.append(outputData);
static const QByteArray endMarker = s_pidMarker + '\n';
- const int endMarkerOffset = m_output.indexOf(endMarker);
- if (endMarkerOffset == -1)
- return;
+ int endMarkerLength = endMarker.length();
+ int endMarkerOffset = m_output.indexOf(endMarker);
+ if (endMarkerOffset == -1) {
+ static const QByteArray endMarker = s_pidMarker + "\r\n";
+ endMarkerOffset = m_output.indexOf(endMarker);
+ endMarkerLength = endMarker.length();
+ if (endMarkerOffset == -1)
+ return;
+ }
const int startMarkerOffset = m_output.indexOf(s_pidMarker);
if (startMarkerOffset == endMarkerOffset) // Only theoretically possible.
return;
@@ -670,33 +554,36 @@ void LinuxProcessInterface::handleReadyReadStandardOutput(const QByteArray &outp
const qint64 processId = pidString.toLongLong();
// We don't want to show output from e.g. /etc/profile.
- m_output = m_output.mid(endMarkerOffset + endMarker.length());
+ m_output = m_output.mid(endMarkerOffset + endMarkerLength);
- emitStarted(processId);
+ q->emitStarted(processId);
if (!m_output.isEmpty() || !m_error.isEmpty())
- emit readyRead(m_output, m_error);
+ emit q->readyRead(m_output, m_error);
m_output.clear();
m_error.clear();
}
-void LinuxProcessInterface::handleReadyReadStandardError(const QByteArray &errorData)
+void SshProcessInterfacePrivate::handleReadyReadStandardError()
{
+ // By default forwards readyRead immediately, but buffers it in
+ // case the start signal is not emitted yet.
+ const QByteArray errorData = m_process.readAllRawStandardError();
+
if (m_pidParsed) {
- emit readyRead({}, errorData);
+ emit q->readyRead({}, errorData);
return;
}
m_error.append(errorData);
}
SshProcessInterfacePrivate::SshProcessInterfacePrivate(SshProcessInterface *sshInterface,
- LinuxDevicePrivate *devicePrivate)
+ const IDevice::ConstPtr &device)
: QObject(sshInterface)
, q(sshInterface)
- , m_device(devicePrivate->q->sharedFromThis())
+ , m_device(device)
, m_process(this)
- , m_devicePrivate(devicePrivate)
{
connect(&m_process, &QtcProcess::started, this, &SshProcessInterfacePrivate::handleStarted);
connect(&m_process, &QtcProcess::done, this, &SshProcessInterfacePrivate::handleDone);
@@ -710,19 +597,26 @@ void SshProcessInterfacePrivate::start()
{
clearForStart();
- m_sshParameters = m_devicePrivate->q->sshParameters();
+ const Id linkDeviceId = Id::fromSetting(m_device->extraData(Constants::LinkDevice));
+ m_linkDevice = DeviceManager::instance()->find(linkDeviceId);
+ m_useConnectionSharing = !m_linkDevice && SshSettings::connectionSharingEnabled();
+
+ m_sshParameters = m_device->sshParameters();
// TODO: Do we really need it for master process?
m_sshParameters.x11DisplayName
= q->m_setup.m_extraData.value("Ssh.X11ForwardToDisplay").toString();
- if (SshSettings::connectionSharingEnabled()) {
+ if (m_useConnectionSharing) {
m_connecting = true;
- m_connectionHandle.reset(new SshConnectionHandle(m_devicePrivate->q->sharedFromThis()));
+ m_connectionHandle.reset(new SshConnectionHandle(m_device));
m_connectionHandle->setParent(this);
connect(m_connectionHandle.get(), &SshConnectionHandle::connected,
this, &SshProcessInterfacePrivate::handleConnected);
connect(m_connectionHandle.get(), &SshConnectionHandle::disconnected,
this, &SshProcessInterfacePrivate::handleDisconnected);
- m_devicePrivate->attachToSharedConnection(m_connectionHandle.get(), m_sshParameters);
+ auto linuxDevice = m_device.dynamicCast<const LinuxDevice>();
+ QTC_ASSERT(linuxDevice, handleDone(); return);
+ linuxDevice->connectionAccess()
+ ->attachToSharedConnection(m_connectionHandle.get(), m_sshParameters);
} else {
doStart();
}
@@ -749,34 +643,6 @@ void SshProcessInterfacePrivate::handleDisconnected(const ProcessResultData &res
emit q->done(resultData); // TODO: don't emit done() on process finished afterwards
}
-void SshProcessInterfacePrivate::handleStarted()
-{
- const qint64 processId = m_process.usesTerminal() ? m_process.processId() : 0;
- // By default emits started signal, Linux impl doesn't emit it when terminal is off.
- q->handleStarted(processId);
-}
-
-void SshProcessInterfacePrivate::handleDone()
-{
- if (m_connectionHandle) // TODO: should it disconnect from signals first?
- m_connectionHandle.release()->deleteLater();
- q->handleDone(m_process.resultData());
-}
-
-void SshProcessInterfacePrivate::handleReadyReadStandardOutput()
-{
- // By default emits signal. LinuxProcessImpl does custom parsing for processId
- // and emits delayed start() - only when terminal is off.
- q->handleReadyReadStandardOutput(m_process.readAllRawStandardOutput());
-}
-
-void SshProcessInterfacePrivate::handleReadyReadStandardError()
-{
- // By default emits signal. LinuxProcessImpl buffers the error channel until
- // it emits delayed start() - only when terminal is off.
- q->handleReadyReadStandardError(m_process.readAllRawStandardError());
-}
-
void SshProcessInterfacePrivate::clearForStart()
{
m_result = {};
@@ -787,8 +653,11 @@ void SshProcessInterfacePrivate::doStart()
m_process.setProcessImpl(q->m_setup.m_processImpl);
m_process.setProcessMode(q->m_setup.m_processMode);
m_process.setTerminalMode(q->m_setup.m_terminalMode);
+ m_process.setPtyData(q->m_setup.m_ptyData);
m_process.setReaperTimeout(q->m_setup.m_reaperTimeout);
m_process.setWriteData(q->m_setup.m_writeData);
+ m_process.setCreateConsoleOnWindows(q->m_setup.m_createConsoleOnWindows);
+
// TODO: what about other fields from m_setup?
SshParameters::setupSshEnvironment(&m_process);
if (!m_sshParameters.x11DisplayName.isEmpty()) {
@@ -798,32 +667,127 @@ void SshProcessInterfacePrivate::doStart()
env.set("DISPLAY", m_sshParameters.x11DisplayName);
m_process.setControlEnvironment(env);
}
+ m_process.setExtraData(q->m_setup.m_extraData);
m_process.setCommand(fullLocalCommandLine());
m_process.start();
}
-CommandLine SshProcessInterfacePrivate::fullLocalCommandLine() const
+static CommandLine getCommandLine(
+ const FilePath sshBinary,
+ const CommandLine commandLine0,
+ const FilePath &workingDirectory,
+ const Environment &env,
+ const QStringList &options,
+ bool useX,
+ bool useTerminal,
+ bool usePidMarker,
+ bool sourceProfile)
{
- CommandLine cmd{SshSettings::sshFilePath()};
+ CommandLine cmd{sshBinary};
- if (!m_sshParameters.x11DisplayName.isEmpty())
+ if (useX)
cmd.addArg("-X");
- if (q->m_setup.m_terminalMode != TerminalMode::Off)
+ if (useTerminal)
cmd.addArg("-tt");
cmd.addArg("-q");
- QStringList options = m_sshParameters.connectionOptions(SshSettings::sshFilePath());
- if (!m_socketFilePath.isEmpty())
- options << "-o" << ("ControlPath=" + m_socketFilePath);
- options << m_sshParameters.host();
cmd.addArgs(options);
- CommandLine remoteWithLocalPath = q->m_setup.m_commandLine;
- FilePath executable = FilePath::fromParts({}, {}, remoteWithLocalPath.executable().path());
- remoteWithLocalPath.setExecutable(executable);
+ CommandLine commandLine = commandLine0;
+ FilePath executable = FilePath::fromParts({}, {}, commandLine.executable().path());
+ commandLine.setExecutable(executable);
+
+ CommandLine inner;
+
+ if (!commandLine.isEmpty() && sourceProfile) {
+ const QStringList rcFilesToSource = {"/etc/profile", "$HOME/.profile"};
+ for (const QString &filePath : rcFilesToSource) {
+ inner.addArgs({"test", "-f", filePath});
+ inner.addArgs("&&", CommandLine::Raw);
+ inner.addArgs({".", filePath});
+ inner.addArgs(";", CommandLine::Raw);
+ }
+ }
+
+ if (!workingDirectory.isEmpty()) {
+ inner.addArgs({"cd", workingDirectory.path()});
+ inner.addArgs("&&", CommandLine::Raw);
+ }
+
+ if (usePidMarker)
+ inner.addArgs(QString("echo ") + s_pidMarker + "$$" + s_pidMarker + " && ", CommandLine::Raw);
+
+ env.forEachEntry([&](const QString &key, const QString &value, bool) {
+ inner.addArgs(key + "='" + env.expandVariables(value) + '\'', CommandLine::Raw);
+ });
+
+ if (!useTerminal && !commandLine.isEmpty())
+ inner.addArg("exec");
+
+ if (!commandLine.isEmpty())
+ inner.addCommandLineAsArgs(commandLine, CommandLine::Raw);
+
+ cmd.addArg(inner.arguments());
+
+ return cmd;
+}
+
+CommandLine SshProcessInterfacePrivate::fullLocalCommandLine() const
+{
+ const FilePath sshBinary = SshSettings::sshFilePath();
+ const bool useTerminal = q->m_setup.m_terminalMode != TerminalMode::Off || q->m_setup.m_ptyData;
+ const bool usePidMarker = !useTerminal;
+ const bool sourceProfile = m_device->extraData(Constants::SourceProfile).toBool();
+ const bool useX = !m_sshParameters.x11DisplayName.isEmpty();
+
+ CommandLine cmd;
+ if (m_linkDevice) {
+ QStringList farOptions = m_sshParameters.connectionOptions("ssh");
+ farOptions << m_sshParameters.host();
+
+ const SshParameters nearParameters = m_linkDevice->sshParameters();
+ QStringList nearOptions = nearParameters.connectionOptions(sshBinary);
+// if (!m_socketFilePath.isEmpty())
+// options << "-o" << ("ControlPath=" + m_socketFilePath);
+ nearOptions << nearParameters.host();
+
+ cmd = getCommandLine("ssh",
+ q->m_setup.m_commandLine,
+ {},
+ {},
+ farOptions,
+ false,
+ false,
+ false,
+ false);
+
+ cmd = getCommandLine(sshBinary,
+ cmd,
+ {},
+ {},
+ nearOptions,
+ false,
+ false,
+ usePidMarker,
+ false);
+ } else {
+ QStringList options = m_sshParameters.connectionOptions(sshBinary);
+ if (!m_socketFilePath.isEmpty())
+ options << "-o" << ("ControlPath=" + m_socketFilePath);
+ options << m_sshParameters.host();
+
+ cmd = getCommandLine(sshBinary,
+ q->m_setup.m_commandLine,
+ q->m_setup.m_workingDirectory,
+ q->m_setup.m_environment,
+ options,
+ useX,
+ useTerminal,
+ usePidMarker,
+ sourceProfile);
+ }
- cmd.addArg(q->fullCommandLine(remoteWithLocalPath));
return cmd;
}
@@ -995,9 +959,16 @@ LinuxDevice::LinuxDevice()
{
setFileAccess(&d->m_fileAccess);
setDisplayType(Tr::tr("Remote Linux"));
- setDefaultDisplayName(Tr::tr("Remote Linux Device"));
setOsType(OsTypeLinux);
+ setupId(IDevice::ManuallyAdded, Utils::Id());
+ setType(Constants::GenericLinuxOsType);
+ setMachineType(IDevice::Hardware);
+ setFreePorts(PortList::fromString(QLatin1String("10000-10100")));
+ SshParameters sshParams;
+ sshParams.timeout = 10;
+ setSshParameters(sshParams);
+
addDeviceAction({Tr::tr("Deploy Public Key..."), [](const IDevice::Ptr &device, QWidget *parent) {
if (auto d = PublicKeyDeploymentDialog::createDialog(device, parent)) {
d->exec();
@@ -1024,16 +995,11 @@ LinuxDevice::LinuxDevice()
d->m_terminals.removeOne(proc);
});
- // We recreate the same way that QtcProcess uses to create the actual environment.
- const Environment finalEnv = (!env.hasChanges() && env.combineWithDeviceEnvironment())
- ? d->getEnvironment()
- : env;
// If we will not set any environment variables, we can leave out the shell executable
// as the "ssh ..." call will automatically launch the default shell if there are
// no arguments. But if we will set environment variables, we need to explicitly
// specify the shell executable.
- const QString shell = finalEnv.hasChanges() ? finalEnv.value_or("SHELL", "/bin/sh")
- : QString();
+ const QString shell = env.hasChanges() ? env.value_or("SHELL", "/bin/sh") : QString();
proc->setCommand({filePath(shell), {}});
proc->setTerminalMode(TerminalMode::On);
@@ -1047,6 +1013,12 @@ LinuxDevice::LinuxDevice()
}});
}
+void LinuxDevice::_setOsType(Utils::OsType osType)
+{
+ qCDebug(linuxDeviceLog) << "Setting OS type to" << osType << "for" << displayName();
+ IDevice::setOsType(osType);
+}
+
LinuxDevice::~LinuxDevice()
{
delete d;
@@ -1088,7 +1060,7 @@ PortsGatheringMethod LinuxDevice::portsGatheringMethod() const
DeviceProcessList *LinuxDevice::createProcessListModel(QObject *parent) const
{
- return new LinuxDeviceProcessList(sharedFromThis(), parent);
+ return new ProcessList(sharedFromThis(), parent);
}
DeviceTester *LinuxDevice::createDeviceTester() const
@@ -1127,7 +1099,7 @@ bool LinuxDevice::handlesFile(const FilePath &filePath) const
ProcessInterface *LinuxDevice::createProcessInterface() const
{
- return new LinuxProcessInterface(this);
+ return new SshProcessInterface(sharedFromThis());
}
LinuxDevicePrivate::LinuxDevicePrivate(LinuxDevice *parent)
@@ -1153,6 +1125,23 @@ LinuxDevicePrivate::~LinuxDevicePrivate()
QMetaObject::invokeMethod(&m_shellThread, closeShell, Qt::BlockingQueuedConnection);
}
+void LinuxDevicePrivate::queryOsType(std::function<RunResult(const CommandLine &)> runInShell)
+{
+ const RunResult result = runInShell({"uname", {"-s"}, OsType::OsTypeLinux});
+ if (result.exitCode != 0)
+ q->_setOsType(OsTypeOtherUnix);
+ const QString osName = QString::fromUtf8(result.stdOut).trimmed();
+ if (osName == "Darwin")
+ q->_setOsType(OsTypeMac);
+ if (osName == "Linux")
+ q->_setOsType(OsTypeLinux);
+}
+
+void LinuxDevicePrivate::checkOsType()
+{
+ queryOsType([this](const CommandLine &cmd) { return runInShell(cmd); });
+}
+
// Call me with shell mutex locked
bool LinuxDevicePrivate::setupShell()
{
@@ -1166,6 +1155,10 @@ bool LinuxDevicePrivate::setupShell()
QMetaObject::invokeMethod(m_handler, [this, sshParameters] {
return m_handler->start(sshParameters);
}, Qt::BlockingQueuedConnection, &ok);
+
+ if (ok) {
+ queryOsType([this](const CommandLine &cmd) { return m_handler->runInShell(cmd); });
+ }
return ok;
}
@@ -1222,10 +1215,9 @@ class SshTransferInterface : public FileTransferInterface
Q_OBJECT
protected:
- SshTransferInterface(const FileTransferSetupData &setup, LinuxDevicePrivate *devicePrivate)
+ SshTransferInterface(const FileTransferSetupData &setup, const IDevice::ConstPtr &device)
: FileTransferInterface(setup)
- , m_device(devicePrivate->q->sharedFromThis())
- , m_devicePrivate(devicePrivate)
+ , m_device(device)
, m_process(this)
{
m_direction = m_setup.m_files.isEmpty() ? FileTransferDirection::Invalid
@@ -1270,7 +1262,7 @@ protected:
}
QString host() const { return m_sshParameters.host(); }
- QString userAtHost() const { return m_sshParameters.userName() + '@' + m_sshParameters.host(); }
+ QString userAtHost() const { return m_sshParameters.userAtHost(); }
QtcProcess &process() { return m_process; }
FileTransferDirection direction() const { return m_direction; }
@@ -1282,7 +1274,11 @@ private:
void start() final
{
m_sshParameters = displayless(m_device->sshParameters());
- if (SshSettings::connectionSharingEnabled()) {
+ const Id linkDeviceId = Id::fromSetting(m_device->extraData(Constants::LinkDevice));
+ const auto linkDevice = DeviceManager::instance()->find(linkDeviceId);
+ const bool useConnectionSharing = !linkDevice && SshSettings::connectionSharingEnabled();
+
+ if (useConnectionSharing) {
m_connecting = true;
m_connectionHandle.reset(new SshConnectionHandle(m_device));
m_connectionHandle->setParent(this);
@@ -1290,7 +1286,10 @@ private:
this, &SshTransferInterface::handleConnected);
connect(m_connectionHandle.get(), &SshConnectionHandle::disconnected,
this, &SshTransferInterface::handleDisconnected);
- m_devicePrivate->attachToSharedConnection(m_connectionHandle.get(), m_sshParameters);
+ auto linuxDevice = m_device.dynamicCast<const LinuxDevice>();
+ QTC_ASSERT(linuxDevice, startFailed("No Linux device"); return);
+ linuxDevice->connectionAccess()
+ ->attachToSharedConnection(m_connectionHandle.get(), m_sshParameters);
} else {
startImpl();
}
@@ -1318,7 +1317,6 @@ private:
}
IDevice::ConstPtr m_device;
- LinuxDevicePrivate *m_devicePrivate = nullptr;
SshParameters m_sshParameters;
FileTransferDirection m_direction = FileTransferDirection::Invalid; // helper
@@ -1333,8 +1331,9 @@ private:
class SftpTransferImpl : public SshTransferInterface
{
public:
- SftpTransferImpl(const FileTransferSetupData &setup, LinuxDevicePrivate *devicePrivate)
- : SshTransferInterface(setup, devicePrivate) { }
+ SftpTransferImpl(const FileTransferSetupData &setup, const IDevice::ConstPtr &device)
+ : SshTransferInterface(setup, device)
+ {}
private:
void startImpl() final
@@ -1390,8 +1389,8 @@ private:
class RsyncTransferImpl : public SshTransferInterface
{
public:
- RsyncTransferImpl(const FileTransferSetupData &setup, LinuxDevicePrivate *devicePrivate)
- : SshTransferInterface(setup, devicePrivate)
+ RsyncTransferImpl(const FileTransferSetupData &setup, const IDevice::ConstPtr &device)
+ : SshTransferInterface(setup, device)
{ }
private:
@@ -1483,7 +1482,7 @@ private:
class GenericTransferImpl : public FileTransferInterface
{
public:
- GenericTransferImpl(const FileTransferSetupData &setup, LinuxDevicePrivate *)
+ GenericTransferImpl(const FileTransferSetupData &setup)
: FileTransferInterface(setup)
{}
@@ -1525,8 +1524,9 @@ private:
.arg(m_currentIndex)
.arg(m_fileCount)
.arg(source.toUserOutput(), target.toUserOutput()));
- if (!source.copyFile(target)) {
- result.m_errorString = Tr::tr("Failed.");
+ expected_str<void> copyResult = source.copyFile(target);
+ if (!copyResult) {
+ result.m_errorString = Tr::tr("Failed: %1").arg(copyResult.error());
result.m_exitCode = -1; // Random pick
emit done(result);
return;
@@ -1545,14 +1545,24 @@ FileTransferInterface *LinuxDevice::createFileTransferInterface(
const FileTransferSetupData &setup) const
{
switch (setup.m_method) {
- case FileTransferMethod::Sftp: return new SftpTransferImpl(setup, d);
- case FileTransferMethod::Rsync: return new RsyncTransferImpl(setup, d);
- case FileTransferMethod::GenericCopy: return new GenericTransferImpl(setup, d);
+ case FileTransferMethod::Sftp: return new SftpTransferImpl(setup, sharedFromThis());
+ case FileTransferMethod::Rsync: return new RsyncTransferImpl(setup, sharedFromThis());
+ case FileTransferMethod::GenericCopy: return new GenericTransferImpl(setup);
}
QTC_CHECK(false);
return {};
}
+LinuxDevicePrivate *LinuxDevice::connectionAccess() const
+{
+ return d;
+}
+
+void LinuxDevice::checkOsType()
+{
+ d->checkOsType();
+}
+
namespace Internal {
// Factory
@@ -1563,6 +1573,7 @@ LinuxDeviceFactory::LinuxDeviceFactory()
setDisplayName(Tr::tr("Remote Linux Device"));
setIcon(QIcon());
setConstructionFunction(&LinuxDevice::create);
+ setQuickCreationAllowed(true);
setCreator([] {
GenericLinuxDeviceConfigurationWizard wizard(Core::ICore::dialogParent());
if (wizard.exec() != QDialog::Accepted)
diff --git a/src/plugins/remotelinux/linuxdevice.h b/src/plugins/remotelinux/linuxdevice.h
index ac65d6e89c9..d9dad9ba75f 100644
--- a/src/plugins/remotelinux/linuxdevice.h
+++ b/src/plugins/remotelinux/linuxdevice.h
@@ -41,12 +41,16 @@ public:
ProjectExplorer::FileTransferInterface *createFileTransferInterface(
const ProjectExplorer::FileTransferSetupData &setup) const override;
+ class LinuxDevicePrivate *connectionAccess() const;
+ void checkOsType() override;
+
protected:
LinuxDevice();
+ void _setOsType(Utils::OsType osType);
+
class LinuxDevicePrivate *d;
- friend class SshProcessInterface;
- friend class SshTransferInterface;
+ friend class LinuxDevicePrivate;
};
namespace Internal {
diff --git a/src/plugins/remotelinux/linuxprocessinterface.h b/src/plugins/remotelinux/linuxprocessinterface.h
index 949c5934200..e2a8e82fc8c 100644
--- a/src/plugins/remotelinux/linuxprocessinterface.h
+++ b/src/plugins/remotelinux/linuxprocessinterface.h
@@ -5,32 +5,35 @@
#include "remotelinux_export.h"
-#include "sshprocessinterface.h"
+#include "linuxdevice.h"
+
+#include <utils/processinterface.h>
namespace RemoteLinux {
-class LinuxDevice;
class SshProcessInterfacePrivate;
-class REMOTELINUX_EXPORT LinuxProcessInterface : public SshProcessInterface
+class REMOTELINUX_EXPORT SshProcessInterface : public Utils::ProcessInterface
{
public:
- LinuxProcessInterface(const LinuxDevice *linuxDevice);
- ~LinuxProcessInterface();
+ explicit SshProcessInterface(const ProjectExplorer::IDevice::ConstPtr &device);
+ ~SshProcessInterface();
-private:
- void handleSendControlSignal(Utils::ControlSignal controlSignal) override;
+protected:
+ void emitStarted(qint64 processId);
+ void killIfRunning();
+ qint64 processId() const;
+ bool runInShell(const Utils::CommandLine &command, const QByteArray &data = {});
- void handleStarted(qint64 processId) final;
- void handleDone(const Utils::ProcessResultData &resultData) final;
- void handleReadyReadStandardOutput(const QByteArray &outputData) final;
- void handleReadyReadStandardError(const QByteArray &errorData) final;
+private:
+ virtual void handleSendControlSignal(Utils::ControlSignal controlSignal);
- QString fullCommandLine(const Utils::CommandLine &commandLine) const final;
+ void start() final;
+ qint64 write(const QByteArray &data) final;
+ void sendControlSignal(Utils::ControlSignal controlSignal) final;
- QByteArray m_output;
- QByteArray m_error;
- bool m_pidParsed = false;
+ friend class SshProcessInterfacePrivate;
+ SshProcessInterfacePrivate *d = nullptr;
};
} // namespace RemoteLinux
diff --git a/src/plugins/remotelinux/makeinstallstep.cpp b/src/plugins/remotelinux/makeinstallstep.cpp
index ea7b6364586..79c42bdf107 100644
--- a/src/plugins/remotelinux/makeinstallstep.cpp
+++ b/src/plugins/remotelinux/makeinstallstep.cpp
@@ -172,12 +172,10 @@ bool MakeInstallStep::init()
const MakeInstallCommand cmd = buildSystem()->makeInstallCommand(rootDir);
if (cmd.environment.hasChanges()) {
Environment env = processParameters()->environment();
- for (auto it = cmd.environment.constBegin(); it != cmd.environment.constEnd(); ++it) {
- if (cmd.environment.isEnabled(it)) {
- const QString key = cmd.environment.key(it);
- env.set(key, cmd.environment.expandedValueForKey(key));
- }
- }
+ cmd.environment.forEachEntry([&](const QString &key, const QString &value, bool enabled) {
+ if (enabled)
+ env.set(key, cmd.environment.expandVariables(value));
+ });
processParameters()->setEnvironment(env);
}
m_noInstallTarget = false;
@@ -251,11 +249,8 @@ void MakeInstallStep::updateArgsFromAspect()
void MakeInstallStep::updateFullCommandLine()
{
- // FIXME: Only executable?
- static_cast<StringAspect *>(aspect(FullCommandLineAspectId))->setValue(
- QDir::toNativeSeparators(
- ProcessArgs::quoteArg(makeExecutable().toString()))
- + ' ' + userArguments());
+ CommandLine cmd{makeExecutable(), userArguments(), CommandLine::Raw};
+ static_cast<StringAspect *>(aspect(FullCommandLineAspectId))->setValue(cmd.toUserOutput());
}
void MakeInstallStep::updateFromCustomCommandLineAspect()
@@ -263,7 +258,7 @@ void MakeInstallStep::updateFromCustomCommandLineAspect()
const StringAspect * const aspect = customCommandLineAspect();
if (!aspect->isChecked())
return;
- const QStringList tokens = ProcessArgs::splitArgs(aspect->value());
+ const QStringList tokens = ProcessArgs::splitArgs(aspect->value(), HostOsInfo::hostOs());
setMakeCommand(tokens.isEmpty() ? FilePath() : FilePath::fromString(tokens.first()));
setUserArguments(ProcessArgs::joinArgs(tokens.mid(1)));
}
@@ -283,4 +278,12 @@ bool MakeInstallStep::fromMap(const QVariantMap &map)
return true;
}
-} // namespace RemoteLinux
+// Factory
+
+MakeInstallStepFactory::MakeInstallStepFactory()
+{
+ registerStep<MakeInstallStep>(Constants::MakeInstallStepId);
+ setDisplayName(Tr::tr("Install into temporary host directory"));
+}
+
+} // RemoteLinux
diff --git a/src/plugins/remotelinux/makeinstallstep.h b/src/plugins/remotelinux/makeinstallstep.h
index ec2fc3985a2..012a9924cf6 100644
--- a/src/plugins/remotelinux/makeinstallstep.h
+++ b/src/plugins/remotelinux/makeinstallstep.h
@@ -41,4 +41,11 @@ private:
bool m_isCmakeProject = false;
};
+class REMOTELINUX_EXPORT MakeInstallStepFactory
+ : public ProjectExplorer::BuildStepFactory
+{
+public:
+ MakeInstallStepFactory();
+};
+
} // namespace RemoteLinux
diff --git a/src/plugins/remotelinux/remotelinux.qbs b/src/plugins/remotelinux/remotelinux.qbs
index 7881cab47de..03a6afad10f 100644
--- a/src/plugins/remotelinux/remotelinux.qbs
+++ b/src/plugins/remotelinux/remotelinux.qbs
@@ -19,8 +19,6 @@ Project {
"deploymenttimeinfo.h",
"customcommanddeploystep.cpp",
"customcommanddeploystep.h",
- "genericdirectuploadservice.cpp",
- "genericdirectuploadservice.h",
"genericdirectuploadstep.cpp",
"genericdirectuploadstep.h",
"genericlinuxdeviceconfigurationwidget.cpp",
@@ -62,7 +60,6 @@ Project {
"rsyncdeploystep.h",
"sshkeycreationdialog.cpp",
"sshkeycreationdialog.h",
- "sshprocessinterface.h",
"tarpackagecreationstep.cpp",
"tarpackagecreationstep.h",
"tarpackagedeploystep.cpp",
@@ -70,9 +67,7 @@ Project {
"images/embeddedtarget.png",
]
- Group {
- name: "Tests"
- condition: qtc.testsEnabled
+ QtcTestFiles {
files: [
"filesystemaccess_test.cpp",
"filesystemaccess_test.h",
diff --git a/src/plugins/remotelinux/remotelinux_constants.h b/src/plugins/remotelinux/remotelinux_constants.h
index 31a97588281..40cc1d1a8b1 100644
--- a/src/plugins/remotelinux/remotelinux_constants.h
+++ b/src/plugins/remotelinux/remotelinux_constants.h
@@ -19,7 +19,9 @@ const char RsyncDeployStepId[] = "RemoteLinux.RsyncDeployStep";
const char CustomCommandDeployStepId[] = "RemoteLinux.GenericRemoteLinuxCustomCommandDeploymentStep";
const char KillAppStepId[] = "RemoteLinux.KillAppStep";
-const char SupportsRSync[] = "RemoteLinux.SupportsRSync";
+const char SupportsRSync[] = "RemoteLinux.SupportsRSync";
+const char SourceProfile[] = "RemoteLinux.SourceProfile";
+const char LinkDevice[] = "RemoteLinux.LinkDevice";
const char RunConfigId[] = "RemoteLinuxRunConfiguration:";
const char CustomRunConfigId[] = "RemoteLinux.CustomRunConfig";
diff --git a/src/plugins/remotelinux/remotelinuxrunconfiguration.cpp b/src/plugins/remotelinux/remotelinuxrunconfiguration.cpp
index 3e28f24af49..06128ad2ffd 100644
--- a/src/plugins/remotelinux/remotelinuxrunconfiguration.cpp
+++ b/src/plugins/remotelinux/remotelinuxrunconfiguration.cpp
@@ -10,6 +10,7 @@
#include <projectexplorer/buildsystem.h>
#include <projectexplorer/buildtargetinfo.h>
#include <projectexplorer/deploymentdata.h>
+#include <projectexplorer/devicesupport/idevice.h>
#include <projectexplorer/kitinformation.h>
#include <projectexplorer/project.h>
#include <projectexplorer/runconfigurationaspects.h>
@@ -58,18 +59,16 @@ RemoteLinuxRunConfiguration::RemoteLinuxRunConfiguration(Target *target, Id id)
envAspect, &EnvironmentAspect::environmentChanged);
setUpdater([this, target, exeAspect, symbolsAspect, libAspect] {
- BuildTargetInfo bti = buildTargetInfo();
+ const IDeviceConstPtr buildDevice = BuildDeviceKitAspect::device(target->kit());
+ const IDeviceConstPtr runDevice = DeviceKitAspect::device(target->kit());
+ QTC_ASSERT(buildDevice, return);
+ QTC_ASSERT(runDevice, return);
+ const BuildTargetInfo bti = buildTargetInfo();
const FilePath localExecutable = bti.targetFilePath;
- DeployableFile depFile = target->deploymentData().deployableForLocalFile(localExecutable);
+ const DeployableFile depFile = target->deploymentData().deployableForLocalFile(localExecutable);
- if (depFile.localFilePath().needsDevice()) // a full remote build
- exeAspect->setExecutable(depFile.localFilePath());
- else
- exeAspect->setExecutable(FilePath::fromString(depFile.remoteFilePath()));
+ exeAspect->setExecutable(runDevice->filePath(depFile.remoteFilePath()));
symbolsAspect->setFilePath(localExecutable);
-
- const IDeviceConstPtr buildDevice = BuildDeviceKitAspect::device(target->kit());
- const IDeviceConstPtr runDevice = DeviceKitAspect::device(target->kit());
libAspect->setEnabled(buildDevice == runDevice);
});
diff --git a/src/plugins/remotelinux/rsyncdeploystep.cpp b/src/plugins/remotelinux/rsyncdeploystep.cpp
index b20eaf90499..1dd46cbc286 100644
--- a/src/plugins/remotelinux/rsyncdeploystep.cpp
+++ b/src/plugins/remotelinux/rsyncdeploystep.cpp
@@ -25,39 +25,53 @@ using namespace Utils::Tasking;
namespace RemoteLinux {
-class RsyncDeployService : public AbstractRemoteLinuxDeployService
-{
-public:
- void setDeployableFiles(const QList<DeployableFile> &files);
- void setIgnoreMissingFiles(bool ignore) { m_ignoreMissingFiles = ignore; }
- void setFlags(const QString &flags) { m_flags = flags; }
-
-private:
- bool isDeploymentNecessary() const final;
- Group deployRecipe() final;
- TaskItem mkdirTask();
- TaskItem transferTask();
-
- mutable FilesToTransfer m_files;
- bool m_ignoreMissingFiles = false;
- QString m_flags;
-};
-
-void RsyncDeployService::setDeployableFiles(const QList<DeployableFile> &files)
+// RsyncDeployStep
+
+RsyncDeployStep::RsyncDeployStep(BuildStepList *bsl, Id id)
+ : AbstractRemoteLinuxDeployStep(bsl, id)
{
- m_files.clear();
- for (const DeployableFile &f : files)
- m_files.append({f.localFilePath(), deviceConfiguration()->filePath(f.remoteFilePath())});
+ auto flags = addAspect<StringAspect>();
+ flags->setDisplayStyle(StringAspect::LineEditDisplay);
+ flags->setSettingsKey("RemoteLinux.RsyncDeployStep.Flags");
+ flags->setLabelText(Tr::tr("Flags:"));
+ flags->setValue(FileTransferSetupData::defaultRsyncFlags());
+
+ auto ignoreMissingFiles = addAspect<BoolAspect>();
+ ignoreMissingFiles->setSettingsKey("RemoteLinux.RsyncDeployStep.IgnoreMissingFiles");
+ ignoreMissingFiles->setLabel(Tr::tr("Ignore missing files:"),
+ BoolAspect::LabelPlacement::InExtraLabel);
+ ignoreMissingFiles->setValue(false);
+
+ setInternalInitializer([this, ignoreMissingFiles, flags] {
+ if (BuildDeviceKitAspect::device(kit()) == DeviceKitAspect::device(kit())) {
+ // rsync transfer on the same device currently not implemented
+ // and typically not wanted.
+ return CheckResult::failure(
+ Tr::tr("rsync is only supported for transfers between different devices."));
+ }
+ m_ignoreMissingFiles = ignoreMissingFiles->value();
+ m_flags = flags->value();
+ return isDeploymentPossible();
+ });
+
+ setRunPreparer([this] {
+ const QList<DeployableFile> files = target()->deploymentData().allFiles();
+ m_files.clear();
+ for (const DeployableFile &f : files)
+ m_files.append({f.localFilePath(), deviceConfiguration()->filePath(f.remoteFilePath())});
+ });
}
-bool RsyncDeployService::isDeploymentNecessary() const
+RsyncDeployStep::~RsyncDeployStep() = default;
+
+bool RsyncDeployStep::isDeploymentNecessary() const
{
if (m_ignoreMissingFiles)
Utils::erase(m_files, [](const FileToTransfer &file) { return !file.m_source.exists(); });
return !m_files.empty();
}
-TaskItem RsyncDeployService::mkdirTask()
+TaskItem RsyncDeployStep::mkdirTask()
{
const auto setupHandler = [this](QtcProcess &process) {
QStringList remoteDirs;
@@ -68,7 +82,7 @@ TaskItem RsyncDeployService::mkdirTask()
process.setCommand({deviceConfiguration()->filePath("mkdir"),
QStringList("-p") + remoteDirs});
connect(&process, &QtcProcess::readyReadStandardError, this, [this, proc = &process] {
- emit stdErrData(QString::fromLocal8Bit(proc->readAllRawStandardError()));
+ handleStdErrData(QString::fromLocal8Bit(proc->readAllRawStandardError()));
});
};
const auto errorHandler = [this](const QtcProcess &process) {
@@ -79,79 +93,40 @@ TaskItem RsyncDeployService::mkdirTask()
finalMessage += '\n';
finalMessage += stdErr;
}
- emit errorMessage(Tr::tr("Deploy via rsync: failed to create remote directories:")
- + '\n' + finalMessage);
+ addErrorMessage(Tr::tr("Deploy via rsync: failed to create remote directories:")
+ + '\n' + finalMessage);
};
return Process(setupHandler, {}, errorHandler);
}
-TaskItem RsyncDeployService::transferTask()
+TaskItem RsyncDeployStep::transferTask()
{
const auto setupHandler = [this](FileTransfer &transfer) {
transfer.setTransferMethod(FileTransferMethod::Rsync);
transfer.setRsyncFlags(m_flags);
transfer.setFilesToTransfer(m_files);
connect(&transfer, &FileTransfer::progress,
- this, &AbstractRemoteLinuxDeployService::stdOutData);
+ this, &AbstractRemoteLinuxDeployStep::handleStdOutData);
};
const auto errorHandler = [this](const FileTransfer &transfer) {
const ProcessResultData result = transfer.resultData();
if (result.m_error == QProcess::FailedToStart) {
- emit errorMessage(Tr::tr("rsync failed to start: %1").arg(result.m_errorString));
+ addErrorMessage(Tr::tr("rsync failed to start: %1").arg(result.m_errorString));
} else if (result.m_exitStatus == QProcess::CrashExit) {
- emit errorMessage(Tr::tr("rsync crashed."));
+ addErrorMessage(Tr::tr("rsync crashed."));
} else if (result.m_exitCode != 0) {
- emit errorMessage(Tr::tr("rsync failed with exit code %1.").arg(result.m_exitCode)
- + "\n" + result.m_errorString);
+ addErrorMessage(Tr::tr("rsync failed with exit code %1.").arg(result.m_exitCode)
+ + "\n" + result.m_errorString);
}
};
return Transfer(setupHandler, {}, errorHandler);
}
-Group RsyncDeployService::deployRecipe()
+Group RsyncDeployStep::deployRecipe()
{
return Group { mkdirTask(), transferTask() };
}
-// RsyncDeployStep
-
-RsyncDeployStep::RsyncDeployStep(BuildStepList *bsl, Id id)
- : AbstractRemoteLinuxDeployStep(bsl, id)
-{
- auto service = new RsyncDeployService;
- setDeployService(service);
-
- auto flags = addAspect<StringAspect>();
- flags->setDisplayStyle(StringAspect::LineEditDisplay);
- flags->setSettingsKey("RemoteLinux.RsyncDeployStep.Flags");
- flags->setLabelText(Tr::tr("Flags:"));
- flags->setValue(FileTransferSetupData::defaultRsyncFlags());
-
- auto ignoreMissingFiles = addAspect<BoolAspect>();
- ignoreMissingFiles->setSettingsKey("RemoteLinux.RsyncDeployStep.IgnoreMissingFiles");
- ignoreMissingFiles->setLabel(Tr::tr("Ignore missing files:"),
- BoolAspect::LabelPlacement::InExtraLabel);
- ignoreMissingFiles->setValue(false);
-
- setInternalInitializer([this, service, flags, ignoreMissingFiles] {
- if (BuildDeviceKitAspect::device(kit()) == DeviceKitAspect::device(kit())) {
- // rsync transfer on the same device currently not implemented
- // and typically not wanted.
- return CheckResult::failure(
- Tr::tr("rsync is only supported for transfers between different devices."));
- }
- service->setIgnoreMissingFiles(ignoreMissingFiles->value());
- service->setFlags(flags->value());
- return service->isDeploymentPossible();
- });
-
- setRunPreparer([this, service] {
- service->setDeployableFiles(target()->deploymentData().allFiles());
- });
-}
-
-RsyncDeployStep::~RsyncDeployStep() = default;
-
Utils::Id RsyncDeployStep::stepId()
{
return Constants::RsyncDeployStepId;
@@ -162,4 +137,12 @@ QString RsyncDeployStep::displayName()
return Tr::tr("Deploy files via rsync");
}
+// Factory
+
+RsyncDeployStepFactory::RsyncDeployStepFactory()
+{
+ registerStep<RsyncDeployStep>(Constants::RsyncDeployStepId);
+ setDisplayName(Tr::tr("Deploy files via rsync"));
+}
+
} // RemoteLinux
diff --git a/src/plugins/remotelinux/rsyncdeploystep.h b/src/plugins/remotelinux/rsyncdeploystep.h
index a0f87905311..eb6ac41d6d4 100644
--- a/src/plugins/remotelinux/rsyncdeploystep.h
+++ b/src/plugins/remotelinux/rsyncdeploystep.h
@@ -7,6 +7,10 @@
#include "abstractremotelinuxdeploystep.h"
+#include <projectexplorer/devicesupport/filetransfer.h>
+
+#include <utils/tasktree.h>
+
namespace RemoteLinux {
class REMOTELINUX_EXPORT RsyncDeployStep : public AbstractRemoteLinuxDeployStep
@@ -17,6 +21,23 @@ public:
static Utils::Id stepId();
static QString displayName();
+
+private:
+ bool isDeploymentNecessary() const final;
+ Utils::Tasking::Group deployRecipe() final;
+ Utils::Tasking::TaskItem mkdirTask();
+ Utils::Tasking::TaskItem transferTask();
+
+ mutable ProjectExplorer::FilesToTransfer m_files;
+ bool m_ignoreMissingFiles = false;
+ QString m_flags;
+};
+
+class REMOTELINUX_EXPORT RsyncDeployStepFactory
+ : public ProjectExplorer::BuildStepFactory
+{
+public:
+ RsyncDeployStepFactory();
};
} // namespace RemoteLinux
diff --git a/src/plugins/remotelinux/sshprocessinterface.h b/src/plugins/remotelinux/sshprocessinterface.h
deleted file mode 100644
index 9d67d6a4e67..00000000000
--- a/src/plugins/remotelinux/sshprocessinterface.h
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright (C) 2022 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-
-#pragma once
-
-#include "remotelinux_export.h"
-
-#include <utils/processinterface.h>
-
-namespace RemoteLinux {
-
-class LinuxDevice;
-class SshProcessInterfacePrivate;
-
-class REMOTELINUX_EXPORT SshProcessInterface : public Utils::ProcessInterface
-{
-public:
- SshProcessInterface(const LinuxDevice *linuxDevice);
- ~SshProcessInterface();
-
-protected:
- void emitStarted(qint64 processId);
- // To be called from leaf destructor.
- // Can't call it from SshProcessInterface destructor as it calls virtual method.
- void killIfRunning();
- qint64 processId() const;
- bool runInShell(const Utils::CommandLine &command, const QByteArray &data = {});
-
-private:
- virtual void handleStarted(qint64 processId);
- virtual void handleDone(const Utils::ProcessResultData &resultData);
- virtual void handleReadyReadStandardOutput(const QByteArray &outputData);
- virtual void handleReadyReadStandardError(const QByteArray &errorData);
- virtual void handleSendControlSignal(Utils::ControlSignal controlSignal) = 0;
-
- virtual QString fullCommandLine(const Utils::CommandLine &commandLine) const = 0;
-
- void start() final;
- qint64 write(const QByteArray &data) final;
- void sendControlSignal(Utils::ControlSignal controlSignal) final;
-
- friend class SshProcessInterfacePrivate;
- SshProcessInterfacePrivate *d = nullptr;
-};
-
-} // namespace RemoteLinux
diff --git a/src/plugins/remotelinux/tarpackagecreationstep.cpp b/src/plugins/remotelinux/tarpackagecreationstep.cpp
index 0d53d36ff55..80aec8053de 100644
--- a/src/plugins/remotelinux/tarpackagecreationstep.cpp
+++ b/src/plugins/remotelinux/tarpackagecreationstep.cpp
@@ -13,8 +13,8 @@
#include <projectexplorer/project.h>
#include <projectexplorer/target.h>
+#include <utils/asynctask.h>
#include <utils/futuresynchronizer.h>
-#include <utils/runextensions.h>
#include <QDateTime>
#include <QDir>
@@ -74,9 +74,9 @@ private:
bool isPackagingNeeded() const;
void deployFinished(bool success);
void addNeededDeploymentFiles(const DeployableFile &deployable, const Kit *kit);
- void doPackage(QFutureInterface<bool> &fi, const Utils::FilePath &tarFilePath,
+ void doPackage(QPromise<bool> &promise, const Utils::FilePath &tarFilePath,
bool ignoreMissingFiles);
- bool appendFile(QFutureInterface<bool> &fi, QFile &tarFile, const QFileInfo &fileInfo,
+ bool appendFile(QPromise<bool> &promise, QFile &tarFile, const QFileInfo &fileInfo,
const QString &remoteFilePath, const Utils::FilePath &tarFilePath,
bool ignoreMissingFiles);
@@ -167,7 +167,7 @@ void TarPackageCreationStep::doRun()
connect(BuildManager::instance(), &BuildManager::buildQueueFinished,
this, &TarPackageCreationStep::deployFinished);
});
- auto future = Utils::runAsync(&TarPackageCreationStep::doPackage, this,
+ auto future = Utils::asyncRun(&TarPackageCreationStep::doPackage, this,
m_tarFilePath, m_ignoreMissingFilesAspect->value());
watcher->setFuture(future);
m_synchronizer.addFuture(future);
@@ -271,7 +271,7 @@ void TarPackageCreationStep::addNeededDeploymentFiles(
}
}
-void TarPackageCreationStep::doPackage(QFutureInterface<bool> &fi, const FilePath &tarFilePath,
+void TarPackageCreationStep::doPackage(QPromise<bool> &promise, const FilePath &tarFilePath,
bool ignoreMissingFiles)
{
// TODO: Optimization: Only package changed files
@@ -280,7 +280,7 @@ void TarPackageCreationStep::doPackage(QFutureInterface<bool> &fi, const FilePat
if (!tarFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
raiseError(Tr::tr("Error: tar file %1 cannot be opened (%2).")
.arg(tarFilePath.toUserOutput(), tarFile.errorString()));
- fi.reportResult(false);
+ promise.addResult(false);
return;
}
@@ -291,10 +291,10 @@ void TarPackageCreationStep::doPackage(QFutureInterface<bool> &fi, const FilePat
continue;
}
QFileInfo fileInfo = d.localFilePath().toFileInfo();
- if (!appendFile(fi, tarFile, fileInfo,
+ if (!appendFile(promise, tarFile, fileInfo,
d.remoteDirectory() + QLatin1Char('/') + fileInfo.fileName(),
tarFilePath, ignoreMissingFiles)) {
- fi.reportResult(false);
+ promise.addResult(false);
return;
}
}
@@ -303,10 +303,10 @@ void TarPackageCreationStep::doPackage(QFutureInterface<bool> &fi, const FilePat
if (tarFile.write(eofIndicator) != eofIndicator.length()) {
raiseError(Tr::tr("Error writing tar file \"%1\": %2.")
.arg(QDir::toNativeSeparators(tarFile.fileName()), tarFile.errorString()));
- fi.reportResult(false);
+ promise.addResult(false);
return;
}
- fi.reportResult(true);
+ promise.addResult(true);
}
static bool setFilePath(TarFileHeader &header, const QByteArray &filePath)
@@ -388,7 +388,7 @@ static bool writeHeader(QFile &tarFile, const QFileInfo &fileInfo, const QString
return true;
}
-bool TarPackageCreationStep::appendFile(QFutureInterface<bool> &fi,
+bool TarPackageCreationStep::appendFile(QPromise<bool> &promise,
QFile &tarFile,
const QFileInfo &fileInfo,
const QString &remoteFilePath,
@@ -406,7 +406,7 @@ bool TarPackageCreationStep::appendFile(QFutureInterface<bool> &fi,
for (const QString &fileName : files) {
const QString thisLocalFilePath = dir.path() + QLatin1Char('/') + fileName;
const QString thisRemoteFilePath = remoteFilePath + QLatin1Char('/') + fileName;
- if (!appendFile(fi, tarFile, QFileInfo(thisLocalFilePath), thisRemoteFilePath,
+ if (!appendFile(promise, tarFile, QFileInfo(thisLocalFilePath), thisRemoteFilePath,
tarFilePath, ignoreMissingFiles)) {
return false;
}
@@ -437,7 +437,7 @@ bool TarPackageCreationStep::appendFile(QFutureInterface<bool> &fi,
while (!file.atEnd() && file.error() == QFile::NoError && tarFile.error() == QFile::NoError) {
const QByteArray data = file.read(chunkSize);
tarFile.write(data);
- if (fi.isCanceled())
+ if (promise.isCanceled())
return false;
}
if (file.error() != QFile::NoError) {
diff --git a/src/plugins/remotelinux/tarpackagedeploystep.cpp b/src/plugins/remotelinux/tarpackagedeploystep.cpp
index ee276666ac8..9f6022e8160 100644
--- a/src/plugins/remotelinux/tarpackagedeploystep.cpp
+++ b/src/plugins/remotelinux/tarpackagedeploystep.cpp
@@ -21,10 +21,35 @@ using namespace Utils::Tasking;
namespace RemoteLinux::Internal {
-class TarPackageDeployService : public AbstractRemoteLinuxDeployService
+// TarPackageDeployStep
+
+class TarPackageDeployStep : public AbstractRemoteLinuxDeployStep
{
public:
- void setPackageFilePath(const FilePath &filePath);
+ TarPackageDeployStep(BuildStepList *bsl, Id id)
+ : AbstractRemoteLinuxDeployStep(bsl, id)
+ {
+ setWidgetExpandedByDefault(false);
+
+ setInternalInitializer([this] {
+ const BuildStep *tarCreationStep = nullptr;
+
+ for (BuildStep *step : deployConfiguration()->stepList()->steps()) {
+ if (step == this)
+ break;
+ if (step->id() == Constants::TarPackageCreationStepId) {
+ tarCreationStep = step;
+ break;
+ }
+ }
+ if (!tarCreationStep)
+ return CheckResult::failure(Tr::tr("No tarball creation step found."));
+
+ m_packageFilePath =
+ FilePath::fromVariant(tarCreationStep->data(Constants::TarPackageFilePathId));
+ return isDeploymentPossible();
+ });
+ }
private:
QString remoteFilePath() const;
@@ -36,42 +61,36 @@ private:
FilePath m_packageFilePath;
};
-void TarPackageDeployService::setPackageFilePath(const FilePath &filePath)
-{
- m_packageFilePath = filePath;
-}
-
-QString TarPackageDeployService::remoteFilePath() const
+QString TarPackageDeployStep::remoteFilePath() const
{
return QLatin1String("/tmp/") + m_packageFilePath.fileName();
}
-bool TarPackageDeployService::isDeploymentNecessary() const
+bool TarPackageDeployStep::isDeploymentNecessary() const
{
return hasLocalFileChanged(DeployableFile(m_packageFilePath, {}));
}
-TaskItem TarPackageDeployService::uploadTask()
+TaskItem TarPackageDeployStep::uploadTask()
{
const auto setupHandler = [this](FileTransfer &transfer) {
const FilesToTransfer files {{m_packageFilePath,
deviceConfiguration()->filePath(remoteFilePath())}};
transfer.setFilesToTransfer(files);
- connect(&transfer, &FileTransfer::progress,
- this, &TarPackageDeployService::progressMessage);
- emit progressMessage(Tr::tr("Uploading package to device..."));
+ connect(&transfer, &FileTransfer::progress, this, &TarPackageDeployStep::addProgressMessage);
+ addProgressMessage(Tr::tr("Uploading package to device..."));
};
const auto doneHandler = [this](const FileTransfer &) {
- emit progressMessage(Tr::tr("Successfully uploaded package file."));
+ addProgressMessage(Tr::tr("Successfully uploaded package file."));
};
const auto errorHandler = [this](const FileTransfer &transfer) {
const ProcessResultData result = transfer.resultData();
- emit errorMessage(result.m_errorString);
+ addErrorMessage(result.m_errorString);
};
return Transfer(setupHandler, doneHandler, errorHandler);
}
-TaskItem TarPackageDeployService::installTask()
+TaskItem TarPackageDeployStep::installTask()
{
const auto setupHandler = [this](QtcProcess &process) {
const QString cmdLine = QLatin1String("cd / && tar xvf ") + remoteFilePath()
@@ -79,63 +98,28 @@ TaskItem TarPackageDeployService::installTask()
process.setCommand({deviceConfiguration()->filePath("/bin/sh"), {"-c", cmdLine}});
QtcProcess *proc = &process;
connect(proc, &QtcProcess::readyReadStandardOutput, this, [this, proc] {
- emit stdOutData(proc->readAllStandardOutput());
+ handleStdOutData(proc->readAllStandardOutput());
});
connect(proc, &QtcProcess::readyReadStandardError, this, [this, proc] {
- emit stdErrData(proc->readAllStandardError());
+ handleStdErrData(proc->readAllStandardError());
});
- emit progressMessage(Tr::tr("Installing package to device..."));
+ addProgressMessage(Tr::tr("Installing package to device..."));
};
const auto doneHandler = [this](const QtcProcess &) {
saveDeploymentTimeStamp(DeployableFile(m_packageFilePath, {}), {});
- emit progressMessage(Tr::tr("Successfully installed package file."));
+ addProgressMessage(Tr::tr("Successfully installed package file."));
};
const auto errorHandler = [this](const QtcProcess &process) {
- emit errorMessage(Tr::tr("Installing package failed.") + process.errorString());
+ addErrorMessage(Tr::tr("Installing package failed.") + process.errorString());
};
return Process(setupHandler, doneHandler, errorHandler);
}
-Group TarPackageDeployService::deployRecipe()
+Group TarPackageDeployStep::deployRecipe()
{
return Group { uploadTask(), installTask() };
}
-// TarPackageDeployStep
-
-class TarPackageDeployStep : public AbstractRemoteLinuxDeployStep
-{
-public:
- TarPackageDeployStep(BuildStepList *bsl, Id id)
- : AbstractRemoteLinuxDeployStep(bsl, id)
- {
- auto service = new TarPackageDeployService;
- setDeployService(service);
-
- setWidgetExpandedByDefault(false);
-
- setInternalInitializer([this, service] {
- const BuildStep *tarCreationStep = nullptr;
-
- for (BuildStep *step : deployConfiguration()->stepList()->steps()) {
- if (step == this)
- break;
- if (step->id() == Constants::TarPackageCreationStepId) {
- tarCreationStep = step;
- break;
- }
- }
- if (!tarCreationStep)
- return CheckResult::failure(Tr::tr("No tarball creation step found."));
-
- const FilePath tarFile =
- FilePath::fromVariant(tarCreationStep->data(Constants::TarPackageFilePathId));
- service->setPackageFilePath(tarFile);
- return service->isDeploymentPossible();
- });
- }
-};
-
// TarPackageDeployStepFactory
diff --git a/src/plugins/scxmleditor/scxmleditordocument.cpp b/src/plugins/scxmleditor/scxmleditordocument.cpp
index 9c84910b2dd..e10c8759084 100644
--- a/src/plugins/scxmleditor/scxmleditordocument.cpp
+++ b/src/plugins/scxmleditor/scxmleditordocument.cpp
@@ -6,7 +6,7 @@
#include "scxmleditorconstants.h"
#include <projectexplorer/projectexplorerconstants.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <qtsupport/qtkitinformation.h>
#include <utils/fileutils.h>
#include <utils/qtcassert.h>
diff --git a/src/plugins/silversearcher/findinfilessilversearcher.cpp b/src/plugins/silversearcher/findinfilessilversearcher.cpp
index b2887cd0a91..6e08a301d1a 100644
--- a/src/plugins/silversearcher/findinfilessilversearcher.cpp
+++ b/src/plugins/silversearcher/findinfilessilversearcher.cpp
@@ -4,14 +4,13 @@
#include "findinfilessilversearcher.h"
#include <aggregation/aggregate.h>
-#include <coreplugin/progressmanager/progressmanager.h>
#include <texteditor/findinfiles.h>
#include <utils/algorithm.h>
+#include <utils/asynctask.h>
#include <utils/environment.h>
#include <utils/fileutils.h>
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
-#include <utils/runextensions.h>
#include "silversearcheroutputparser.h"
#include "silversearchertr.h"
@@ -28,8 +27,6 @@ using namespace Utils;
namespace {
const QLatin1String silverSearcherName("Silver Searcher");
-using FutureInterfaceType = QFutureInterface<FileSearchResultList>;
-
const QString metacharacters = "+()^$.{}[]|\\";
const QString SearchOptionsString = "SearchOptionsString";
@@ -76,9 +73,8 @@ bool isSilverSearcherAvailable()
return false;
}
-void runSilverSeacher(FutureInterfaceType &fi, FileFindParameters parameters)
+void runSilverSeacher(QPromise<FileSearchResultList> &promise, FileFindParameters parameters)
{
- ProgressTimer progress(fi, 5);
const FilePath directory = FilePath::fromUserInput(parameters.additionalParameters.toString());
QStringList arguments = {"--parallel", "--ackmate"};
@@ -126,9 +122,9 @@ void runSilverSeacher(FutureInterfaceType &fi, FileFindParameters parameters)
SilverSearcher::SilverSearcherOutputParser parser(process.cleanedStdOut(), regexp);
FileSearchResultList items = parser.parse();
if (!items.isEmpty())
- fi.reportResult(items);
+ promise.addResult(items);
} else {
- fi.reportCanceled();
+ promise.future().cancel();
}
}
@@ -196,7 +192,7 @@ void FindInFilesSilverSearcher::writeSettings(QSettings *settings) const
QFuture<FileSearchResultList> FindInFilesSilverSearcher::executeSearch(
const FileFindParameters &parameters, BaseFileFind * /*baseFileFind*/)
{
- return Utils::runAsync(runSilverSeacher, parameters);
+ return Utils::asyncRun(runSilverSeacher, parameters);
}
IEditor *FindInFilesSilverSearcher::openEditor(const SearchResultItem & /*item*/,
diff --git a/src/plugins/silversearcher/silversearcher.qbs b/src/plugins/silversearcher/silversearcher.qbs
index f099858f6e3..8cf2cd15090 100644
--- a/src/plugins/silversearcher/silversearcher.qbs
+++ b/src/plugins/silversearcher/silversearcher.qbs
@@ -13,9 +13,7 @@ QtcPlugin {
"silversearcherplugin.cpp", "silversearcherplugin.h",
]
- Group {
- name: "Tests"
- condition: qtc.testsEnabled
+ QtcTestFiles {
files: [
"outputparser_test.cpp",
"outputparser_test.h",
diff --git a/src/plugins/squish/squishfilehandler.cpp b/src/plugins/squish/squishfilehandler.cpp
index 956f9488227..918120a4063 100644
--- a/src/plugins/squish/squishfilehandler.cpp
+++ b/src/plugins/squish/squishfilehandler.cpp
@@ -15,8 +15,10 @@
#include <coreplugin/documentmanager.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/icore.h>
+
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/session.h>
+
#include <utils/algorithm.h>
#include <utils/aspects.h>
#include <utils/layoutbuilder.h>
@@ -93,8 +95,7 @@ SquishFileHandler::SquishFileHandler(QObject *parent)
: QObject(parent)
{
m_instance = this;
- auto sessionManager = ProjectExplorer::SessionManager::instance();
- connect(sessionManager, &ProjectExplorer::SessionManager::sessionLoaded,
+ connect(ProjectExplorer::SessionManager::instance(), &ProjectExplorer::SessionManager::sessionLoaded,
this, &SquishFileHandler::onSessionLoaded);
}
diff --git a/src/plugins/studiowelcome/stylemodel.cpp b/src/plugins/studiowelcome/stylemodel.cpp
index d93fc5fded2..1198052994e 100644
--- a/src/plugins/studiowelcome/stylemodel.cpp
+++ b/src/plugins/studiowelcome/stylemodel.cpp
@@ -3,8 +3,8 @@
#include "stylemodel.h"
-#include "utils/algorithm.h"
-#include "utils/qtcassert.h"
+#include <utils/algorithm.h>
+#include <utils/qtcassert.h>
#include <QRegularExpression>
diff --git a/src/plugins/studiowelcome/wizardhandler.cpp b/src/plugins/studiowelcome/wizardhandler.cpp
index ee20518e604..d6d750d39eb 100644
--- a/src/plugins/studiowelcome/wizardhandler.cpp
+++ b/src/plugins/studiowelcome/wizardhandler.cpp
@@ -11,8 +11,8 @@
#include <projectexplorer/jsonwizard/jsonprojectpage.h>
-#include "utils/wizard.h"
#include <utils/qtcassert.h>
+#include <utils/wizard.h>
using namespace StudioWelcome;
diff --git a/src/plugins/subversion/subversionclient.cpp b/src/plugins/subversion/subversionclient.cpp
index f496054da67..3602e1f4b83 100644
--- a/src/plugins/subversion/subversionclient.cpp
+++ b/src/plugins/subversion/subversionclient.cpp
@@ -203,7 +203,7 @@ SubversionDiffEditorController::SubversionDiffEditorController(IDocument *docume
process.setCommand(command);
};
const auto onDiffDone = [diffInputStorage](const QtcProcess &process) {
- *diffInputStorage.activeStorage() = process.cleanedStdOut();
+ *diffInputStorage = process.cleanedStdOut();
};
const Group root {
diff --git a/src/plugins/terminal/CMakeLists.txt b/src/plugins/terminal/CMakeLists.txt
new file mode 100644
index 00000000000..005a3aaca56
--- /dev/null
+++ b/src/plugins/terminal/CMakeLists.txt
@@ -0,0 +1,23 @@
+
+add_qtc_plugin(Terminal
+ PLUGIN_DEPENDS Core ProjectExplorer
+ DEPENDS libvterm
+ SOURCES
+ celliterator.cpp celliterator.h
+ glyphcache.cpp glyphcache.h
+ keys.cpp keys.h
+ scrollback.cpp scrollback.h
+ shellintegration.cpp shellintegration.h
+ shellmodel.cpp shellmodel.h
+ terminal.qrc
+ terminalcommands.cpp terminalcommands.h
+ terminalpane.cpp terminalpane.h
+ terminalplugin.cpp terminalplugin.h
+ terminalprocessimpl.cpp terminalprocessimpl.h
+ terminalsearch.cpp terminalsearch.h
+ terminalsettings.cpp terminalsettings.h
+ terminalsettingspage.cpp terminalsettingspage.h
+ terminalsurface.cpp terminalsurface.h
+ terminaltr.h
+ terminalwidget.cpp terminalwidget.h
+)
diff --git a/src/plugins/terminal/Terminal.json.in b/src/plugins/terminal/Terminal.json.in
new file mode 100644
index 00000000000..3715f77fa31
--- /dev/null
+++ b/src/plugins/terminal/Terminal.json.in
@@ -0,0 +1,19 @@
+{
+ \"Name\" : \"Terminal\",
+ \"Version\" : \"$$QTCREATOR_VERSION\",
+ \"CompatVersion\" : \"$$QTCREATOR_COMPAT_VERSION\",
+ \"DisabledByDefault\" : true,
+ \"Vendor\" : \"The Qt Company Ltd\",
+ \"Copyright\" : \"(C) $$QTCREATOR_COPYRIGHT_YEAR The Qt Company Ltd\",
+ \"License\" : [ \"Commercial Usage\",
+ \"\",
+ \"Licensees holding valid Qt Commercial licenses may use this plugin in accordance with the Qt Commercial License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and The Qt Company.\",
+ \"\",
+ \"GNU General Public License Usage\",
+ \"\",
+ \"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html.\"
+ ],
+ \"Description\" : \"Terminal window.\",
+ \"Url\" : \"http://www.qt.io\",
+ $$dependencyList
+}
diff --git a/src/plugins/terminal/celliterator.cpp b/src/plugins/terminal/celliterator.cpp
new file mode 100644
index 00000000000..26f347f4fda
--- /dev/null
+++ b/src/plugins/terminal/celliterator.cpp
@@ -0,0 +1,99 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "celliterator.h"
+
+#include "terminalsurface.h"
+
+#include <stdexcept>
+
+namespace Terminal::Internal {
+
+CellIterator::CellIterator(const TerminalSurface *surface, QPoint pos)
+ : m_state(State::INSIDE)
+ , m_surface(surface)
+{
+ m_pos = (pos.x()) + (pos.y() * surface->liveSize().width());
+ m_maxpos = surface->fullSize().width() * (surface->fullSize().height()) - 1;
+ updateChar();
+}
+
+CellIterator::CellIterator(const TerminalSurface *surface, int pos)
+ : m_state(State::INSIDE)
+ , m_surface(surface)
+{
+ m_maxpos = surface->fullSize().width() * (surface->fullSize().height()) - 1;
+ m_pos = qMin(m_maxpos + 1, pos);
+ if (m_pos == 0) {
+ m_state = State::BEGIN;
+ } else if (m_pos == m_maxpos + 1) {
+ m_state = State::END;
+ }
+ updateChar();
+}
+
+CellIterator::CellIterator(const TerminalSurface *surface, State state)
+ : m_state(state)
+ , m_surface(surface)
+ , m_pos()
+{
+ m_maxpos = surface->fullSize().width() * (surface->fullSize().height()) - 1;
+ if (state == State::END) {
+ m_pos = m_maxpos + 1;
+ }
+}
+
+QPoint CellIterator::gridPos() const
+{
+ return m_surface->posToGrid(m_pos);
+}
+
+bool CellIterator::updateChar()
+{
+ QPoint cell = m_surface->posToGrid(m_pos);
+ m_char = m_surface->fetchCharAt(cell.x(), cell.y());
+ return m_char != 0;
+}
+
+CellIterator &CellIterator::operator-=(int n)
+{
+ if (n == 0)
+ return *this;
+
+ if (m_pos - n < 0)
+ throw new std::runtime_error("-= n too big!");
+
+ m_pos -= n;
+
+ while (!updateChar() && m_pos > 0 && m_skipZeros)
+ m_pos--;
+
+ m_state = State::INSIDE;
+
+ if (m_pos == 0) {
+ m_state = State::BEGIN;
+ }
+
+ return *this;
+}
+
+CellIterator &CellIterator::operator+=(int n)
+{
+ if (n == 0)
+ return *this;
+
+ if (m_pos + n < m_maxpos + 1) {
+ m_state = State::INSIDE;
+ m_pos += n;
+ while (!updateChar() && m_pos < (m_maxpos + 1) && m_skipZeros)
+ m_pos++;
+
+ if (m_pos == m_maxpos + 1)
+ m_state = State::END;
+ } else {
+ *this = m_surface->end();
+ }
+ return *this;
+}
+
+} // namespace Terminal::Internal
diff --git a/src/plugins/terminal/celliterator.h b/src/plugins/terminal/celliterator.h
new file mode 100644
index 00000000000..e5e7847edc2
--- /dev/null
+++ b/src/plugins/terminal/celliterator.h
@@ -0,0 +1,97 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <string>
+
+#include <QPoint>
+
+namespace Terminal::Internal {
+
+class TerminalSurface;
+
+class CellIterator
+{
+public:
+ using iterator_category = std::bidirectional_iterator_tag;
+ using difference_type = std::ptrdiff_t;
+ using value_type = std::u32string::value_type;
+ using pointer = std::u32string::value_type *;
+ // We need to return copies for std::reverse_iterator to work
+ using reference = std::u32string::value_type;
+
+ enum class State { BEGIN, INSIDE, END } m_state{};
+
+public:
+ CellIterator(const TerminalSurface *surface, QPoint pos);
+ CellIterator(const TerminalSurface *surface, int pos);
+ CellIterator(const TerminalSurface *surface, State state);
+
+public:
+ QPoint gridPos() const;
+
+public:
+ CellIterator &operator-=(int n);
+ CellIterator &operator+=(int n);
+
+ reference operator*() const { return m_char; }
+ pointer operator->() { return &m_char; }
+
+ CellIterator &operator++() { return *this += 1; }
+ CellIterator operator++(int)
+ {
+ CellIterator tmp = *this;
+ ++(*this);
+ return tmp;
+ }
+
+ CellIterator &operator--() { return *this -= 1; }
+ CellIterator operator--(int)
+ {
+ CellIterator tmp = *this;
+ --(*this);
+ return tmp;
+ }
+
+ bool operator!=(const CellIterator &other) const
+ {
+ if (other.m_state != m_state)
+ return true;
+
+ if (other.m_pos != m_pos)
+ return true;
+
+ return false;
+ }
+
+ bool operator==(const CellIterator &other) const { return !operator!=(other); }
+
+ CellIterator operator-(int n) const
+ {
+ CellIterator result = *this;
+ result -= n;
+ return result;
+ }
+
+ CellIterator operator+(int n) const
+ {
+ CellIterator result = *this;
+ result += n;
+ return result;
+ }
+
+ int position() const { return m_pos; }
+ void setSkipZeros(bool skipZeros) { m_skipZeros = skipZeros; }
+
+private:
+ bool updateChar();
+
+ const TerminalSurface *m_surface{nullptr};
+ int m_pos{-1};
+ int m_maxpos{-1};
+ bool m_skipZeros{false};
+ mutable std::u32string::value_type m_char;
+};
+
+} // namespace Terminal::Internal
diff --git a/src/plugins/terminal/glyphcache.cpp b/src/plugins/terminal/glyphcache.cpp
new file mode 100644
index 00000000000..72a0fd7b9d1
--- /dev/null
+++ b/src/plugins/terminal/glyphcache.cpp
@@ -0,0 +1,48 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "glyphcache.h"
+
+#include <QTextLayout>
+
+namespace Terminal::Internal {
+
+size_t qHash(const GlyphCacheKey &key, size_t seed = 0)
+{
+ return qHash(key.font, seed) ^ qHash(key.text, seed);
+}
+
+GlyphCache &GlyphCache::instance()
+{
+ static GlyphCache cache(5000);
+ return cache;
+}
+
+const QGlyphRun *GlyphCache::get(const QFont &font, const QString &text)
+{
+ GlyphCacheKey key{font, text};
+ if (auto *run = object(key))
+ return run;
+
+ QTextLayout layout;
+
+ layout.setText(text);
+ layout.setFont(font);
+
+ layout.beginLayout();
+ layout.createLine().setNumColumns(std::numeric_limits<int>::max());
+ layout.endLayout();
+
+ if (layout.lineCount() > 0) {
+ const auto &line = layout.lineAt(0);
+ const auto runs = line.glyphRuns();
+ if (!runs.isEmpty()) {
+ QGlyphRun *run = new QGlyphRun(layout.lineAt(0).glyphRuns().first());
+ insert(key, run);
+ return run;
+ }
+ }
+ return nullptr;
+}
+
+} // namespace Terminal::Internal
diff --git a/src/plugins/terminal/glyphcache.h b/src/plugins/terminal/glyphcache.h
new file mode 100644
index 00000000000..60701098f5f
--- /dev/null
+++ b/src/plugins/terminal/glyphcache.h
@@ -0,0 +1,34 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <QCache>
+#include <QFont>
+#include <QGlyphRun>
+#include <QString>
+
+namespace Terminal::Internal {
+
+struct GlyphCacheKey
+{
+ QFont font;
+ QString text;
+
+ bool operator==(const GlyphCacheKey &other) const
+ {
+ return font == other.font && text == other.text;
+ }
+};
+
+class GlyphCache : public QCache<GlyphCacheKey, QGlyphRun>
+{
+public:
+ using QCache::QCache;
+
+ static GlyphCache &instance();
+
+ const QGlyphRun *get(const QFont &font, const QString &text);
+};
+
+} // namespace Terminal::Internal
diff --git a/src/plugins/terminal/images/settingscategory_terminal.png b/src/plugins/terminal/images/settingscategory_terminal.png
new file mode 100644
index 00000000000..6e8c7167787
--- /dev/null
+++ b/src/plugins/terminal/images/settingscategory_terminal.png
Binary files differ
diff --git a/src/plugins/terminal/images/[email protected] b/src/plugins/terminal/images/[email protected]
new file mode 100644
index 00000000000..71e292d8bc1
--- /dev/null
+++ b/src/plugins/terminal/images/[email protected]
Binary files differ
diff --git a/src/plugins/terminal/images/terminal.png b/src/plugins/terminal/images/terminal.png
new file mode 100644
index 00000000000..0a1dd311ec8
--- /dev/null
+++ b/src/plugins/terminal/images/terminal.png
Binary files differ
diff --git a/src/plugins/terminal/images/[email protected] b/src/plugins/terminal/images/[email protected]
new file mode 100644
index 00000000000..da36b36721c
--- /dev/null
+++ b/src/plugins/terminal/images/[email protected]
Binary files differ
diff --git a/src/plugins/terminal/keys.cpp b/src/plugins/terminal/keys.cpp
new file mode 100644
index 00000000000..f6a7a91b13d
--- /dev/null
+++ b/src/plugins/terminal/keys.cpp
@@ -0,0 +1,83 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "keys.h"
+
+namespace Terminal::Internal {
+
+VTermModifier qtModifierToVTerm(Qt::KeyboardModifiers mod)
+{
+ int ret = VTERM_MOD_NONE;
+
+ if (mod & Qt::ShiftModifier)
+ ret |= VTERM_MOD_SHIFT;
+
+ if (mod & Qt::AltModifier)
+ ret |= VTERM_MOD_ALT;
+
+#ifdef Q_OS_DARWIN
+ if (mod & Qt::MetaModifier)
+ ret |= VTERM_MOD_CTRL;
+#else
+ if (mod & Qt::ControlModifier)
+ ret |= VTERM_MOD_CTRL;
+#endif
+
+ return static_cast<VTermModifier>(ret);
+}
+
+VTermKey qtKeyToVTerm(Qt::Key key, bool keypad)
+{
+ if (key >= Qt::Key_F1 && key <= Qt::Key_F35)
+ return static_cast<VTermKey>(VTERM_KEY_FUNCTION_0 + key - Qt::Key_F1 + 1);
+
+ switch (key) {
+ case Qt::Key_Return:
+ return VTERM_KEY_ENTER;
+ case Qt::Key_Tab:
+ return VTERM_KEY_TAB;
+ case Qt::Key_Backspace:
+ return VTERM_KEY_BACKSPACE;
+ case Qt::Key_Escape:
+ return VTERM_KEY_ESCAPE;
+ case Qt::Key_Up:
+ return VTERM_KEY_UP;
+ case Qt::Key_Down:
+ return VTERM_KEY_DOWN;
+ case Qt::Key_Left:
+ return VTERM_KEY_LEFT;
+ case Qt::Key_Right:
+ return VTERM_KEY_RIGHT;
+ case Qt::Key_Insert:
+ return VTERM_KEY_INS;
+ case Qt::Key_Delete:
+ return VTERM_KEY_DEL;
+ case Qt::Key_Home:
+ return VTERM_KEY_HOME;
+ case Qt::Key_End:
+ return VTERM_KEY_END;
+ case Qt::Key_PageUp:
+ return VTERM_KEY_PAGEUP;
+ case Qt::Key_PageDown:
+ return VTERM_KEY_PAGEDOWN;
+ case Qt::Key_multiply:
+ return keypad ? VTERM_KEY_KP_MULT : VTERM_KEY_NONE;
+ case Qt::Key_Plus:
+ return keypad ? VTERM_KEY_KP_PLUS : VTERM_KEY_NONE;
+ case Qt::Key_Comma:
+ return keypad ? VTERM_KEY_KP_COMMA : VTERM_KEY_NONE;
+ case Qt::Key_Minus:
+ return keypad ? VTERM_KEY_KP_MINUS : VTERM_KEY_NONE;
+ case Qt::Key_Period:
+ return keypad ? VTERM_KEY_KP_PERIOD : VTERM_KEY_NONE;
+ case Qt::Key_Slash:
+ return keypad ? VTERM_KEY_KP_DIVIDE : VTERM_KEY_NONE;
+ case Qt::Key_Enter:
+ return keypad ? VTERM_KEY_KP_ENTER : VTERM_KEY_NONE;
+ case Qt::Key_Equal:
+ return keypad ? VTERM_KEY_KP_EQUAL : VTERM_KEY_NONE;
+ default:
+ return VTERM_KEY_NONE;
+ }
+}
+} // namespace Terminal::Internal
diff --git a/src/plugins/terminal/keys.h b/src/plugins/terminal/keys.h
new file mode 100644
index 00000000000..f3df9330013
--- /dev/null
+++ b/src/plugins/terminal/keys.h
@@ -0,0 +1,15 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <vterm_keycodes.h>
+
+#include <QKeyEvent>
+
+namespace Terminal::Internal {
+
+VTermKey qtKeyToVTerm(Qt::Key key, bool keypad);
+VTermModifier qtModifierToVTerm(Qt::KeyboardModifiers mod);
+
+} // namespace Terminal::Internal
diff --git a/src/plugins/terminal/scrollback.cpp b/src/plugins/terminal/scrollback.cpp
new file mode 100644
index 00000000000..e22d5fa2436
--- /dev/null
+++ b/src/plugins/terminal/scrollback.cpp
@@ -0,0 +1,61 @@
+// Copyright (c) 2020, Justin Bronder
+// Copied and modified from: https://github.com/jsbronder/sff
+// SPDX-License-Identifier: BSD-3-Clause
+
+#include "scrollback.h"
+
+#include <cassert>
+#include <cstring>
+#include <future>
+
+namespace Terminal::Internal {
+
+Scrollback::Line::Line(int cols, const VTermScreenCell *cells)
+ : m_cols(cols)
+ , m_cells(std::make_unique<VTermScreenCell[]>(cols))
+{
+ memcpy(m_cells.get(), cells, cols * sizeof(cells[0]));
+}
+
+const VTermScreenCell *Scrollback::Line::cell(int i) const
+{
+ assert(i >= 0 && i < m_cols);
+ return &m_cells[i];
+}
+
+Scrollback::Scrollback(size_t capacity)
+ : m_capacity(capacity)
+{}
+
+void Scrollback::emplace(int cols, const VTermScreenCell *cells)
+{
+ m_deque.emplace_front(cols, cells);
+ while (m_deque.size() > m_capacity) {
+ m_deque.pop_back();
+ }
+}
+
+void Scrollback::popto(int cols, VTermScreenCell *cells)
+{
+ const Line &sbl = m_deque.front();
+
+ int ncells = cols;
+ if (ncells > sbl.cols())
+ ncells = sbl.cols();
+
+ memcpy(cells, sbl.cells(), sizeof(cells[0]) * ncells);
+ for (size_t i = ncells; i < static_cast<size_t>(cols); ++i) {
+ cells[i].chars[0] = '\0';
+ cells[i].width = 1;
+ cells[i].bg = cells[ncells - 1].bg;
+ }
+
+ m_deque.pop_front();
+}
+
+void Scrollback::clear()
+{
+ m_deque.clear();
+}
+
+} // namespace Terminal::Internal
diff --git a/src/plugins/terminal/scrollback.h b/src/plugins/terminal/scrollback.h
new file mode 100644
index 00000000000..9ca71eec615
--- /dev/null
+++ b/src/plugins/terminal/scrollback.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2020, Justin Bronder
+// Copied and modified from: https://github.com/jsbronder/sff
+// SPDX-License-Identifier: BSD-3-Clause
+
+#pragma once
+
+#include <vterm.h>
+
+#include <deque>
+#include <future>
+#include <memory>
+
+#include <QFont>
+#include <QTextLayout>
+
+namespace Terminal::Internal {
+
+class Scrollback
+{
+public:
+ class Line
+ {
+ public:
+ Line(int cols, const VTermScreenCell *cells);
+ Line(Line &&other) = default;
+ Line() = delete;
+
+ int cols() const { return m_cols; };
+ const VTermScreenCell *cell(int i) const;
+ const VTermScreenCell *cells() const { return &m_cells[0]; };
+
+ private:
+ int m_cols;
+ std::unique_ptr<VTermScreenCell[]> m_cells;
+ };
+
+public:
+ Scrollback(size_t capacity);
+ Scrollback() = delete;
+
+ int capacity() const { return static_cast<int>(m_capacity); };
+ int size() const { return static_cast<int>(m_deque.size()); };
+
+ const Line &line(size_t index) const { return m_deque.at(index); };
+ const std::deque<Line> &lines() const { return m_deque; };
+
+ void emplace(int cols, const VTermScreenCell *cells);
+ void popto(int cols, VTermScreenCell *cells);
+
+ void clear();
+
+private:
+ size_t m_capacity;
+ std::deque<Line> m_deque;
+};
+
+} // namespace Terminal::Internal
diff --git a/src/plugins/terminal/shellintegration.cpp b/src/plugins/terminal/shellintegration.cpp
new file mode 100644
index 00000000000..fd4c1364696
--- /dev/null
+++ b/src/plugins/terminal/shellintegration.cpp
@@ -0,0 +1,143 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "shellintegration.h"
+
+#include <utils/environment.h>
+#include <utils/filepath.h>
+#include <utils/stringutils.h>
+
+#include <QLoggingCategory>
+
+Q_LOGGING_CATEGORY(integrationLog, "qtc.terminal.shellintegration", QtWarningMsg)
+
+using namespace Utils;
+
+namespace Terminal {
+
+struct FileToCopy
+{
+ FilePath source;
+ QString destName;
+};
+
+// clang-format off
+struct
+{
+ struct
+ {
+ FilePath rcFile{":/terminal/shellintegrations/shellintegration-bash.sh"};
+ } bash;
+ struct
+ {
+ QList<FileToCopy> files{
+ {":/terminal/shellintegrations/shellintegration-env.zsh", ".zshenv"},
+ {":/terminal/shellintegrations/shellintegration-login.zsh", ".zlogin"},
+ {":/terminal/shellintegrations/shellintegration-profile.zsh", ".zprofile"},
+ {":/terminal/shellintegrations/shellintegration-rc.zsh", ".zshrc"}
+ };
+ } zsh;
+ struct
+ {
+ FilePath script{":/terminal/shellintegrations/shellintegration.ps1"};
+ } pwsh;
+
+} filesToCopy;
+// clang-format on
+
+bool ShellIntegration::canIntegrate(const Utils::CommandLine &cmdLine)
+{
+ if (cmdLine.executable().needsDevice())
+ return false; // TODO: Allow integration for remote shells
+
+ if (!cmdLine.arguments().isEmpty())
+ return false;
+
+ if (cmdLine.executable().baseName() == "bash")
+ return true;
+
+ if (cmdLine.executable().baseName() == "zsh")
+ return true;
+
+ if (cmdLine.executable().baseName() == "pwsh"
+ || cmdLine.executable().baseName() == "powershell") {
+ return true;
+ }
+
+ return false;
+}
+
+void ShellIntegration::onOsc(int cmd, const VTermStringFragment &fragment)
+{
+ QString d = QString::fromLocal8Bit(fragment.str, fragment.len);
+ const auto [command, data] = Utils::splitAtFirst(d, ';');
+
+ if (cmd == 1337) {
+ const auto [key, value] = Utils::splitAtFirst(command, '=');
+ if (key == QStringView(u"CurrentDir"))
+ emit currentDirChanged(FilePath::fromUserInput(value.toString()).path());
+
+ } else if (cmd == 7) {
+ emit currentDirChanged(FilePath::fromUserInput(d).path());
+ } else if (cmd == 133) {
+ qCDebug(integrationLog) << "OSC 133:" << data;
+ } else if (cmd == 633 && command.length() == 1) {
+ if (command[0] == 'E') {
+ CommandLine cmdLine = CommandLine::fromUserInput(data.toString());
+ emit commandChanged(cmdLine);
+ } else if (command[0] == 'D') {
+ emit commandChanged({});
+ } else if (command[0] == 'P') {
+ const auto [key, value] = Utils::splitAtFirst(data, '=');
+ if (key == QStringView(u"Cwd"))
+ emit currentDirChanged(value.toString());
+ }
+ }
+}
+
+void ShellIntegration::prepareProcess(Utils::QtcProcess &process)
+{
+ Environment env = process.environment().hasChanges() ? process.environment()
+ : Environment::systemEnvironment();
+ CommandLine cmd = process.commandLine();
+
+ if (!canIntegrate(cmd))
+ return;
+
+ env.set("VSCODE_INJECTION", "1");
+
+ if (cmd.executable().baseName() == "bash") {
+ const FilePath rcPath = filesToCopy.bash.rcFile;
+ const FilePath tmpRc = FilePath::fromUserInput(
+ m_tempDir.filePath(filesToCopy.bash.rcFile.fileName()));
+ rcPath.copyFile(tmpRc);
+
+ cmd.addArgs({"--init-file", tmpRc.nativePath()});
+ } else if (cmd.executable().baseName() == "zsh") {
+ for (const FileToCopy &file : filesToCopy.zsh.files) {
+ const auto copyResult = file.source.copyFile(
+ FilePath::fromUserInput(m_tempDir.filePath(file.destName)));
+ QTC_ASSERT_EXPECTED(copyResult, return);
+ }
+
+ const Utils::FilePath originalZdotDir = FilePath::fromUserInput(
+ env.value_or("ZDOTDIR", QDir::homePath()));
+
+ env.set("ZDOTDIR", m_tempDir.path());
+ env.set("USER_ZDOTDIR", originalZdotDir.nativePath());
+ } else if (cmd.executable().baseName() == "pwsh"
+ || cmd.executable().baseName() == "powershell") {
+ const FilePath rcPath = filesToCopy.pwsh.script;
+ const FilePath tmpRc = FilePath::fromUserInput(
+ m_tempDir.filePath(filesToCopy.pwsh.script.fileName()));
+ rcPath.copyFile(tmpRc);
+
+ cmd.addArgs(QString("-noexit -command try { . \"%1\" } catch {}{1}").arg(tmpRc.nativePath()),
+ CommandLine::Raw);
+ }
+
+ process.setCommand(cmd);
+ process.setEnvironment(env);
+}
+
+} // namespace Terminal
diff --git a/src/plugins/terminal/shellintegration.h b/src/plugins/terminal/shellintegration.h
new file mode 100644
index 00000000000..264b5a4d67a
--- /dev/null
+++ b/src/plugins/terminal/shellintegration.h
@@ -0,0 +1,34 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH
+// Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <utils/commandline.h>
+#include <utils/qtcprocess.h>
+
+#include <vterm.h>
+
+#include <QTemporaryDir>
+
+namespace Terminal {
+
+class ShellIntegration : public QObject
+{
+ Q_OBJECT
+public:
+ static bool canIntegrate(const Utils::CommandLine &cmdLine);
+
+ void onOsc(int cmd, const VTermStringFragment &fragment);
+
+ void prepareProcess(Utils::QtcProcess &process);
+
+signals:
+ void commandChanged(const Utils::CommandLine &command);
+ void currentDirChanged(const QString &dir);
+
+private:
+ QTemporaryDir m_tempDir;
+};
+
+} // namespace Terminal
diff --git a/src/plugins/terminal/shellintegrations/shellintegration-bash.sh b/src/plugins/terminal/shellintegrations/shellintegration-bash.sh
new file mode 100755
index 00000000000..7db188be08e
--- /dev/null
+++ b/src/plugins/terminal/shellintegrations/shellintegration-bash.sh
@@ -0,0 +1,252 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# SPDX-License-Identifier: MIT
+
+
+# Prevent the script recursing when setting up
+if [[ -n "$VSCODE_SHELL_INTEGRATION" ]]; then
+ builtin return
+fi
+
+VSCODE_SHELL_INTEGRATION=1
+
+# Run relevant rc/profile only if shell integration has been injected, not when run manually
+if [ "$VSCODE_INJECTION" == "1" ]; then
+ if [ -z "$VSCODE_SHELL_LOGIN" ]; then
+ if [ -r ~/.bashrc ]; then
+ . ~/.bashrc
+ fi
+ else
+ # Imitate -l because --init-file doesn't support it:
+ # run the first of these files that exists
+ if [ -r /etc/profile ]; then
+ . /etc/profile
+ fi
+ # exceute the first that exists
+ if [ -r ~/.bash_profile ]; then
+ . ~/.bash_profile
+ elif [ -r ~/.bash_login ]; then
+ . ~/.bash_login
+ elif [ -r ~/.profile ]; then
+ . ~/.profile
+ fi
+ builtin unset VSCODE_SHELL_LOGIN
+
+ # Apply any explicit path prefix (see #99878)
+ if [ -n "$VSCODE_PATH_PREFIX" ]; then
+ export PATH=$VSCODE_PATH_PREFIX$PATH
+ builtin unset VSCODE_PATH_PREFIX
+ fi
+ fi
+ builtin unset VSCODE_INJECTION
+fi
+
+if [ -z "$VSCODE_SHELL_INTEGRATION" ]; then
+ builtin return
+fi
+
+__vsc_get_trap() {
+ # 'trap -p DEBUG' outputs a shell command like `trap -- '…shellcode…' DEBUG`.
+ # The terms are quoted literals, but are not guaranteed to be on a single line.
+ # (Consider a trap like $'echo foo\necho \'bar\'').
+ # To parse, we splice those terms into an expression capturing them into an array.
+ # This preserves the quoting of those terms: when we `eval` that expression, they are preserved exactly.
+ # This is different than simply exploding the string, which would split everything on IFS, oblivious to quoting.
+ builtin local -a terms
+ builtin eval "terms=( $(trap -p "${1:-DEBUG}") )"
+ # |________________________|
+ # |
+ # \-------------------*--------------------/
+ # terms=( trap -- '…arbitrary shellcode…' DEBUG )
+ # |____||__| |_____________________| |_____|
+ # | | | |
+ # 0 1 2 3
+ # |
+ # \--------*----/
+ builtin printf '%s' "${terms[2]:-}"
+}
+
+# The property (P) and command (E) codes embed values which require escaping.
+# Backslashes are doubled. Non-alphanumeric characters are converted to escaped hex.
+__vsc_escape_value() {
+ # Process text byte by byte, not by codepoint.
+ builtin local LC_ALL=C str="${1}" i byte token out=''
+
+ for (( i=0; i < "${#str}"; ++i )); do
+ byte="${str:$i:1}"
+
+ # Escape backslashes and semi-colons
+ if [ "$byte" = "\\" ]; then
+ token="\\\\"
+ elif [ "$byte" = ";" ]; then
+ token="\\x3b"
+ else
+ token="$byte"
+ fi
+
+ out+="$token"
+ done
+
+ builtin printf '%s\n' "${out}"
+}
+
+# Send the IsWindows property if the environment looks like Windows
+if [[ "$(uname -s)" =~ ^CYGWIN*|MINGW*|MSYS* ]]; then
+ builtin printf '\e]633;P;IsWindows=True\a'
+fi
+
+# Allow verifying $BASH_COMMAND doesn't have aliases resolved via history when the right HISTCONTROL
+# configuration is used
+if [[ "$HISTCONTROL" =~ .*(erasedups|ignoreboth|ignoredups).* ]]; then
+ __vsc_history_verify=0
+else
+ __vsc_history_verify=1
+fi
+
+__vsc_initialized=0
+__vsc_original_PS1="$PS1"
+__vsc_original_PS2="$PS2"
+__vsc_custom_PS1=""
+__vsc_custom_PS2=""
+__vsc_in_command_execution="1"
+__vsc_current_command=""
+
+__vsc_prompt_start() {
+ builtin printf '\e]633;A\a'
+}
+
+__vsc_prompt_end() {
+ builtin printf '\e]633;B\a'
+}
+
+__vsc_update_cwd() {
+ builtin printf '\e]633;P;Cwd=%s\a' "$(__vsc_escape_value "$PWD")"
+}
+
+__vsc_command_output_start() {
+ builtin printf '\e]633;C\a'
+ builtin printf '\e]633;E;%s\a' "$(__vsc_escape_value "${__vsc_current_command}")"
+}
+
+__vsc_continuation_start() {
+ builtin printf '\e]633;F\a'
+}
+
+__vsc_continuation_end() {
+ builtin printf '\e]633;G\a'
+}
+
+__vsc_command_complete() {
+ if [ "$__vsc_current_command" = "" ]; then
+ builtin printf '\e]633;D\a'
+ else
+ builtin printf '\e]633;D;%s\a' "$__vsc_status"
+ fi
+ __vsc_update_cwd
+}
+__vsc_update_prompt() {
+ # in command execution
+ if [ "$__vsc_in_command_execution" = "1" ]; then
+ # Wrap the prompt if it is not yet wrapped, if the PS1 changed this this was last set it
+ # means the user re-exported the PS1 so we should re-wrap it
+ if [[ "$__vsc_custom_PS1" == "" || "$__vsc_custom_PS1" != "$PS1" ]]; then
+ __vsc_original_PS1=$PS1
+ __vsc_custom_PS1="\[$(__vsc_prompt_start)\]$__vsc_original_PS1\[$(__vsc_prompt_end)\]"
+ PS1="$__vsc_custom_PS1"
+ fi
+ if [[ "$__vsc_custom_PS2" == "" || "$__vsc_custom_PS2" != "$PS2" ]]; then
+ __vsc_original_PS2=$PS2
+ __vsc_custom_PS2="\[$(__vsc_continuation_start)\]$__vsc_original_PS2\[$(__vsc_continuation_end)\]"
+ PS2="$__vsc_custom_PS2"
+ fi
+ __vsc_in_command_execution="0"
+ fi
+}
+
+__vsc_precmd() {
+ __vsc_command_complete "$__vsc_status"
+ __vsc_current_command=""
+ __vsc_update_prompt
+}
+
+__vsc_preexec() {
+ __vsc_initialized=1
+ if [[ ! "$BASH_COMMAND" =~ ^__vsc_prompt* ]]; then
+ # Use history if it's available to verify the command as BASH_COMMAND comes in with aliases
+ # resolved
+ if [ "$__vsc_history_verify" = "1" ]; then
+ __vsc_current_command="$(builtin history 1 | sed 's/ *[0-9]* *//')"
+ else
+ __vsc_current_command=$BASH_COMMAND
+ fi
+ else
+ __vsc_current_command=""
+ fi
+ __vsc_command_output_start
+}
+
+# Debug trapping/preexec inspired by starship (ISC)
+if [[ -n "${bash_preexec_imported:-}" ]]; then
+ __vsc_preexec_only() {
+ if [ "$__vsc_in_command_execution" = "0" ]; then
+ __vsc_in_command_execution="1"
+ __vsc_preexec
+ fi
+ }
+ precmd_functions+=(__vsc_prompt_cmd)
+ preexec_functions+=(__vsc_preexec_only)
+else
+ __vsc_dbg_trap="$(__vsc_get_trap DEBUG)"
+
+ if [[ -z "$__vsc_dbg_trap" ]]; then
+ __vsc_preexec_only() {
+ if [ "$__vsc_in_command_execution" = "0" ]; then
+ __vsc_in_command_execution="1"
+ __vsc_preexec
+ fi
+ }
+ trap '__vsc_preexec_only "$_"' DEBUG
+ elif [[ "$__vsc_dbg_trap" != '__vsc_preexec "$_"' && "$__vsc_dbg_trap" != '__vsc_preexec_all "$_"' ]]; then
+ __vsc_preexec_all() {
+ if [ "$__vsc_in_command_execution" = "0" ]; then
+ __vsc_in_command_execution="1"
+ builtin eval "${__vsc_dbg_trap}"
+ __vsc_preexec
+ fi
+ }
+ trap '__vsc_preexec_all "$_"' DEBUG
+ fi
+fi
+
+__vsc_update_prompt
+
+__vsc_restore_exit_code() {
+ return "$1"
+}
+
+__vsc_prompt_cmd_original() {
+ __vsc_status="$?"
+ __vsc_restore_exit_code "${__vsc_status}"
+ # Evaluate the original PROMPT_COMMAND similarly to how bash would normally
+ # See https://unix.stackexchange.com/a/672843 for technique
+ for cmd in "${__vsc_original_prompt_command[@]}"; do
+ eval "${cmd:-}"
+ done
+ __vsc_precmd
+}
+
+__vsc_prompt_cmd() {
+ __vsc_status="$?"
+ __vsc_precmd
+}
+
+# PROMPT_COMMAND arrays and strings seem to be handled the same (handling only the first entry of
+# the array?)
+__vsc_original_prompt_command=$PROMPT_COMMAND
+
+if [[ -z "${bash_preexec_imported:-}" ]]; then
+ if [[ -n "$__vsc_original_prompt_command" && "$__vsc_original_prompt_command" != "__vsc_prompt_cmd" ]]; then
+ PROMPT_COMMAND=__vsc_prompt_cmd_original
+ else
+ PROMPT_COMMAND=__vsc_prompt_cmd
+ fi
+fi
diff --git a/src/plugins/terminal/shellintegrations/shellintegration-env.zsh b/src/plugins/terminal/shellintegrations/shellintegration-env.zsh
new file mode 100644
index 00000000000..3c890539aeb
--- /dev/null
+++ b/src/plugins/terminal/shellintegrations/shellintegration-env.zsh
@@ -0,0 +1,15 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# SPDX-License-Identifier: MIT
+
+if [[ -f $USER_ZDOTDIR/.zshenv ]]; then
+ VSCODE_ZDOTDIR=$ZDOTDIR
+ ZDOTDIR=$USER_ZDOTDIR
+
+ # prevent recursion
+ if [[ $USER_ZDOTDIR != $VSCODE_ZDOTDIR ]]; then
+ . $USER_ZDOTDIR/.zshenv
+ fi
+
+ USER_ZDOTDIR=$ZDOTDIR
+ ZDOTDIR=$VSCODE_ZDOTDIR
+fi
diff --git a/src/plugins/terminal/shellintegrations/shellintegration-login.zsh b/src/plugins/terminal/shellintegrations/shellintegration-login.zsh
new file mode 100644
index 00000000000..37ff5439790
--- /dev/null
+++ b/src/plugins/terminal/shellintegrations/shellintegration-login.zsh
@@ -0,0 +1,7 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# SPDX-License-Identifier: MIT
+
+ZDOTDIR=$USER_ZDOTDIR
+if [[ $options[norcs] = off && -o "login" && -f $ZDOTDIR/.zlogin ]]; then
+ . $ZDOTDIR/.zlogin
+fi
diff --git a/src/plugins/terminal/shellintegrations/shellintegration-profile.zsh b/src/plugins/terminal/shellintegrations/shellintegration-profile.zsh
new file mode 100644
index 00000000000..724e1f28790
--- /dev/null
+++ b/src/plugins/terminal/shellintegrations/shellintegration-profile.zsh
@@ -0,0 +1,15 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# SPDX-License-Identifier: MIT
+
+if [[ $options[norcs] = off && -o "login" && -f $USER_ZDOTDIR/.zprofile ]]; then
+ VSCODE_ZDOTDIR=$ZDOTDIR
+ ZDOTDIR=$USER_ZDOTDIR
+ . $USER_ZDOTDIR/.zprofile
+ ZDOTDIR=$VSCODE_ZDOTDIR
+
+ # Apply any explicit path prefix (see #99878)
+ if (( ${+VSCODE_PATH_PREFIX} )); then
+ export PATH=$VSCODE_PATH_PREFIX$PATH
+ fi
+ builtin unset VSCODE_PATH_PREFIX
+fi
diff --git a/src/plugins/terminal/shellintegrations/shellintegration-rc.zsh b/src/plugins/terminal/shellintegrations/shellintegration-rc.zsh
new file mode 100644
index 00000000000..df4109131a9
--- /dev/null
+++ b/src/plugins/terminal/shellintegrations/shellintegration-rc.zsh
@@ -0,0 +1,160 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# SPDX-License-Identifier: MIT
+
+builtin autoload -Uz add-zsh-hook
+
+# Prevent the script recursing when setting up
+if [ -n "$VSCODE_SHELL_INTEGRATION" ]; then
+ ZDOTDIR=$USER_ZDOTDIR
+ builtin return
+fi
+
+# This variable allows the shell to both detect that VS Code's shell integration is enabled as well
+# as disable it by unsetting the variable.
+VSCODE_SHELL_INTEGRATION=1
+
+# By default, zsh will set the $HISTFILE to the $ZDOTDIR location automatically. In the case of the
+# shell integration being injected, this means that the terminal will use a different history file
+# to other terminals. To fix this issue, set $HISTFILE back to the default location before ~/.zshrc
+# is called as that may depend upon the value.
+if [[ "$VSCODE_INJECTION" == "1" ]]; then
+ HISTFILE=$USER_ZDOTDIR/.zsh_history
+fi
+
+# Only fix up ZDOTDIR if shell integration was injected (not manually installed) and has not been called yet
+if [[ "$VSCODE_INJECTION" == "1" ]]; then
+ if [[ $options[norcs] = off && -f $USER_ZDOTDIR/.zshrc ]]; then
+ VSCODE_ZDOTDIR=$ZDOTDIR
+ ZDOTDIR=$USER_ZDOTDIR
+ # A user's custom HISTFILE location might be set when their .zshrc file is sourced below
+ . $USER_ZDOTDIR/.zshrc
+ fi
+fi
+
+# Shell integration was disabled by the shell, exit without warning assuming either the shell has
+# explicitly disabled shell integration as it's incompatible or it implements the protocol.
+if [ -z "$VSCODE_SHELL_INTEGRATION" ]; then
+ builtin return
+fi
+
+# The property (P) and command (E) codes embed values which require escaping.
+# Backslashes are doubled. Non-alphanumeric characters are converted to escaped hex.
+__vsc_escape_value() {
+ builtin emulate -L zsh
+
+ # Process text byte by byte, not by codepoint.
+ builtin local LC_ALL=C str="$1" i byte token out=''
+
+ for (( i = 0; i < ${#str}; ++i )); do
+ byte="${str:$i:1}"
+
+ # Escape backslashes and semi-colons
+ if [ "$byte" = "\\" ]; then
+ token="\\\\"
+ elif [ "$byte" = ";" ]; then
+ token="\\x3b"
+ else
+ token="$byte"
+ fi
+
+ out+="$token"
+ done
+
+ builtin print -r "$out"
+}
+
+__vsc_in_command_execution="1"
+__vsc_current_command=""
+
+__vsc_prompt_start() {
+ builtin printf '\e]633;A\a'
+}
+
+__vsc_prompt_end() {
+ builtin printf '\e]633;B\a'
+}
+
+__vsc_update_cwd() {
+ builtin printf '\e]633;P;Cwd=%s\a' "$(__vsc_escape_value "${PWD}")"
+}
+
+__vsc_command_output_start() {
+ builtin printf '\e]633;C\a'
+ builtin printf '\e]633;E;%s\a' "${__vsc_current_command}"
+}
+
+__vsc_continuation_start() {
+ builtin printf '\e]633;F\a'
+}
+
+__vsc_continuation_end() {
+ builtin printf '\e]633;G\a'
+}
+
+__vsc_right_prompt_start() {
+ builtin printf '\e]633;H\a'
+}
+
+__vsc_right_prompt_end() {
+ builtin printf '\e]633;I\a'
+}
+
+__vsc_command_complete() {
+ if [[ "$__vsc_current_command" == "" ]]; then
+ builtin printf '\e]633;D\a'
+ else
+ builtin printf '\e]633;D;%s\a' "$__vsc_status"
+ fi
+ __vsc_update_cwd
+}
+
+if [[ -o NOUNSET ]]; then
+ if [ -z "${RPROMPT-}" ]; then
+ RPROMPT=""
+ fi
+fi
+__vsc_update_prompt() {
+ __vsc_prior_prompt="$PS1"
+ __vsc_prior_prompt2="$PS2"
+ __vsc_in_command_execution=""
+ PS1="%{$(__vsc_prompt_start)%}$PS1%{$(__vsc_prompt_end)%}"
+ PS2="%{$(__vsc_continuation_start)%}$PS2%{$(__vsc_continuation_end)%}"
+ if [ -n "$RPROMPT" ]; then
+ __vsc_prior_rprompt="$RPROMPT"
+ RPROMPT="%{$(__vsc_right_prompt_start)%}$RPROMPT%{$(__vsc_right_prompt_end)%}"
+ fi
+}
+
+__vsc_precmd() {
+ local __vsc_status="$?"
+ if [ -z "${__vsc_in_command_execution-}" ]; then
+ # not in command execution
+ __vsc_command_output_start
+ fi
+
+ __vsc_command_complete "$__vsc_status"
+ __vsc_current_command=""
+
+ # in command execution
+ if [ -n "$__vsc_in_command_execution" ]; then
+ # non null
+ __vsc_update_prompt
+ fi
+}
+
+__vsc_preexec() {
+ PS1="$__vsc_prior_prompt"
+ PS2="$__vsc_prior_prompt2"
+ if [ -n "$RPROMPT" ]; then
+ RPROMPT="$__vsc_prior_rprompt"
+ fi
+ __vsc_in_command_execution="1"
+ __vsc_current_command=$2
+ __vsc_command_output_start
+}
+add-zsh-hook precmd __vsc_precmd
+add-zsh-hook preexec __vsc_preexec
+
+if [[ $options[login] = off && $USER_ZDOTDIR != $VSCODE_ZDOTDIR ]]; then
+ ZDOTDIR=$USER_ZDOTDIR
+fi
diff --git a/src/plugins/terminal/shellintegrations/shellintegration.fish b/src/plugins/terminal/shellintegrations/shellintegration.fish
new file mode 100644
index 00000000000..7495bab3f40
--- /dev/null
+++ b/src/plugins/terminal/shellintegrations/shellintegration.fish
@@ -0,0 +1,122 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# SPDX-License-Identifier: MIT
+#
+# Visual Studio Code terminal integration for fish
+#
+# Manual installation:
+#
+# (1) Add the following to the end of `$__fish_config_dir/config.fish`:
+#
+# string match -q "$TERM_PROGRAM" "vscode"
+# and . (code --locate-shell-integration-path fish)
+#
+# (2) Restart fish.
+
+# Don't run in scripts, other terminals, or more than once per session.
+status is-interactive
+and string match --quiet "$TERM_PROGRAM" "vscode"
+and ! set --query VSCODE_SHELL_INTEGRATION
+or exit
+
+set --global VSCODE_SHELL_INTEGRATION 1
+
+# Apply any explicit path prefix (see #99878)
+if status --is-login; and set -q VSCODE_PATH_PREFIX
+ fish_add_path -p $VSCODE_PATH_PREFIX
+end
+set -e VSCODE_PATH_PREFIX
+
+# Helper function
+function __vsc_esc -d "Emit escape sequences for VS Code shell integration"
+ builtin printf "\e]633;%s\a" (string join ";" $argv)
+end
+
+# Sent right before executing an interactive command.
+# Marks the beginning of command output.
+function __vsc_cmd_executed --on-event fish_preexec
+ __vsc_esc C
+ __vsc_esc E (__vsc_escape_value "$argv")
+
+ # Creates a marker to indicate a command was run.
+ set --global _vsc_has_cmd
+end
+
+
+# Escape a value for use in the 'P' ("Property") or 'E' ("Command Line") sequences.
+# Backslashes are doubled and non-alphanumeric characters are hex encoded.
+function __vsc_escape_value
+ # Escape backslashes and semi-colons
+ echo $argv \
+ | string replace --all '\\' '\\\\' \
+ | string replace --all ';' '\\x3b' \
+ ;
+end
+
+# Sent right after an interactive command has finished executing.
+# Marks the end of command output.
+function __vsc_cmd_finished --on-event fish_postexec
+ __vsc_esc D $status
+end
+
+# Sent when a command line is cleared or reset, but no command was run.
+# Marks the cleared line with neither success nor failure.
+function __vsc_cmd_clear --on-event fish_cancel
+ __vsc_esc D
+end
+
+# Sent whenever a new fish prompt is about to be displayed.
+# Updates the current working directory.
+function __vsc_update_cwd --on-event fish_prompt
+ __vsc_esc P Cwd=(__vsc_escape_value "$PWD")
+
+ # If a command marker exists, remove it.
+ # Otherwise, the commandline is empty and no command was run.
+ if set --query _vsc_has_cmd
+ set --erase _vsc_has_cmd
+ else
+ __vsc_cmd_clear
+ end
+end
+
+# Sent at the start of the prompt.
+# Marks the beginning of the prompt (and, implicitly, a new line).
+function __vsc_fish_prompt_start
+ __vsc_esc A
+end
+
+# Sent at the end of the prompt.
+# Marks the beginning of the user's command input.
+function __vsc_fish_cmd_start
+ __vsc_esc B
+end
+
+function __vsc_fish_has_mode_prompt -d "Returns true if fish_mode_prompt is defined and not empty"
+ functions fish_mode_prompt | string match -rvq '^ *(#|function |end$|$)'
+end
+
+# Preserve the user's existing prompt, to wrap in our escape sequences.
+functions --copy fish_prompt __vsc_fish_prompt
+
+# Preserve and wrap fish_mode_prompt (which appears to the left of the regular
+# prompt), but only if it's not defined as an empty function (which is the
+# officially documented way to disable that feature).
+if __vsc_fish_has_mode_prompt
+ functions --copy fish_mode_prompt __vsc_fish_mode_prompt
+
+ function fish_mode_prompt
+ __vsc_fish_prompt_start
+ __vsc_fish_mode_prompt
+ end
+
+ function fish_prompt
+ __vsc_fish_prompt
+ __vsc_fish_cmd_start
+ end
+else
+ # No fish_mode_prompt, so put everything in fish_prompt.
+ function fish_prompt
+ __vsc_fish_prompt_start
+ __vsc_fish_prompt
+ __vsc_fish_cmd_start
+ end
+end
diff --git a/src/plugins/terminal/shellintegrations/shellintegration.ps1 b/src/plugins/terminal/shellintegrations/shellintegration.ps1
new file mode 100644
index 00000000000..4fd978a8844
--- /dev/null
+++ b/src/plugins/terminal/shellintegrations/shellintegration.ps1
@@ -0,0 +1,158 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# SPDX-License-Identifier: MIT
+
+# Prevent installing more than once per session
+if (Test-Path variable:global:__VSCodeOriginalPrompt) {
+ return;
+}
+
+# Disable shell integration when the language mode is restricted
+if ($ExecutionContext.SessionState.LanguageMode -ne "FullLanguage") {
+ return;
+}
+
+$Global:__VSCodeOriginalPrompt = $function:Prompt
+
+$Global:__LastHistoryId = -1
+
+function Global:__VSCode-Escape-Value([string]$value) {
+ # NOTE: In PowerShell v6.1+, this can be written `$value -replace '…', { … }` instead of `[regex]::Replace`.
+ # Replace any non-alphanumeric characters.
+ [regex]::Replace($value, '[\\\n;]', { param($match)
+ # Encode the (ascii) matches as `\x<hex>`
+ -Join (
+ [System.Text.Encoding]::UTF8.GetBytes($match.Value) | ForEach-Object { '\x{0:x2}' -f $_ }
+ )
+ })
+}
+
+function Global:Prompt() {
+ $FakeCode = [int]!$global:?
+ # NOTE: We disable strict mode for the scope of this function because it unhelpfully throws an
+ # error when $LastHistoryEntry is null, and is not otherwise useful.
+ Set-StrictMode -Off
+ $LastHistoryEntry = Get-History -Count 1
+ # Skip finishing the command if the first command has not yet started
+ if ($Global:__LastHistoryId -ne -1) {
+ if ($LastHistoryEntry.Id -eq $Global:__LastHistoryId) {
+ # Don't provide a command line or exit code if there was no history entry (eg. ctrl+c, enter on no command)
+ $Result = "$([char]0x1b)]633;E`a"
+ $Result += "$([char]0x1b)]633;D`a"
+ } else {
+ # Command finished command line
+ # OSC 633 ; A ; <CommandLine?> ST
+ $Result = "$([char]0x1b)]633;E;"
+ # Sanitize the command line to ensure it can get transferred to the terminal and can be parsed
+ # correctly. This isn't entirely safe but good for most cases, it's important for the Pt parameter
+ # to only be composed of _printable_ characters as per the spec.
+ if ($LastHistoryEntry.CommandLine) {
+ $CommandLine = $LastHistoryEntry.CommandLine
+ } else {
+ $CommandLine = ""
+ }
+ $Result += $(__VSCode-Escape-Value $CommandLine)
+ $Result += "`a"
+ # Command finished exit code
+ # OSC 633 ; D [; <ExitCode>] ST
+ $Result += "$([char]0x1b)]633;D;$FakeCode`a"
+ }
+ }
+ # Prompt started
+ # OSC 633 ; A ST
+ $Result += "$([char]0x1b)]633;A`a"
+ # Current working directory
+ # OSC 633 ; <Property>=<Value> ST
+ $Result += if($pwd.Provider.Name -eq 'FileSystem'){"$([char]0x1b)]633;P;Cwd=$(__VSCode-Escape-Value $pwd.ProviderPath)`a"}
+ # Before running the original prompt, put $? back to what it was:
+ if ($FakeCode -ne 0) {
+ Write-Error "failure" -ea ignore
+ }
+ # Run the original prompt
+ $Result += $Global:__VSCodeOriginalPrompt.Invoke()
+ # Write command started
+ $Result += "$([char]0x1b)]633;B`a"
+ $Global:__LastHistoryId = $LastHistoryEntry.Id
+ return $Result
+}
+
+# Only send the command executed sequence when PSReadLine is loaded, if not shell integration should
+# still work thanks to the command line sequence
+if (Get-Module -Name PSReadLine) {
+ $__VSCodeOriginalPSConsoleHostReadLine = $function:PSConsoleHostReadLine
+ function Global:PSConsoleHostReadLine {
+ $tmp = $__VSCodeOriginalPSConsoleHostReadLine.Invoke()
+ # Write command executed sequence directly to Console to avoid the new line from Write-Host
+ [Console]::Write("$([char]0x1b)]633;C`a")
+ $tmp
+ }
+}
+
+# Set IsWindows property
+[Console]::Write("$([char]0x1b)]633;P;IsWindows=$($IsWindows)`a")
+
+# Set always on key handlers which map to default VS Code keybindings
+function Set-MappedKeyHandler {
+ param ([string[]] $Chord, [string[]]$Sequence)
+ try {
+ $Handler = Get-PSReadLineKeyHandler -Chord $Chord | Select-Object -First 1
+ } catch [System.Management.Automation.ParameterBindingException] {
+ # PowerShell 5.1 ships with PSReadLine 2.0.0 which does not have -Chord,
+ # so we check what's bound and filter it.
+ $Handler = Get-PSReadLineKeyHandler -Bound | Where-Object -FilterScript { $_.Key -eq $Chord } | Select-Object -First 1
+ }
+ if ($Handler) {
+ Set-PSReadLineKeyHandler -Chord $Sequence -Function $Handler.Function
+ }
+}
+
+function Set-MappedKeyHandlers {
+ Set-MappedKeyHandler -Chord Ctrl+Spacebar -Sequence 'F12,a'
+ Set-MappedKeyHandler -Chord Alt+Spacebar -Sequence 'F12,b'
+ Set-MappedKeyHandler -Chord Shift+Enter -Sequence 'F12,c'
+ Set-MappedKeyHandler -Chord Shift+End -Sequence 'F12,d'
+
+ # Conditionally enable suggestions
+ if ($env:VSCODE_SUGGEST -eq '1') {
+ Remove-Item Env:VSCODE_SUGGEST
+
+ # VS Code send completions request (may override Ctrl+Spacebar)
+ Set-PSReadLineKeyHandler -Chord 'F12,e' -ScriptBlock {
+ Send-Completions
+ }
+
+ # Suggest trigger characters
+ Set-PSReadLineKeyHandler -Chord "-" -ScriptBlock {
+ [Microsoft.PowerShell.PSConsoleReadLine]::Insert("-")
+ Send-Completions
+ }
+ }
+}
+
+function Send-Completions {
+ $commandLine = ""
+ $cursorIndex = 0
+ # TODO: Since fuzzy matching exists, should completions be provided only for character after the
+ # last space and then filter on the client side? That would let you trigger ctrl+space
+ # anywhere on a word and have full completions available
+ [Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$commandLine, [ref]$cursorIndex)
+ $completionPrefix = $commandLine
+
+ # Get completions
+ $result = "`e]633;Completions"
+ if ($completionPrefix.Length -gt 0) {
+ # Get and send completions
+ $completions = TabExpansion2 -inputScript $completionPrefix -cursorColumn $cursorIndex
+ if ($null -ne $completions.CompletionMatches) {
+ $result += ";$($completions.ReplacementIndex);$($completions.ReplacementLength);$($cursorIndex);"
+ $result += $completions.CompletionMatches | ConvertTo-Json -Compress
+ }
+ }
+ $result += "`a"
+
+ Write-Host -NoNewLine $result
+}
+
+# Register key handlers if PSReadLine is available
+if (Get-Module -Name PSReadLine) {
+ Set-MappedKeyHandlers
+}
diff --git a/src/plugins/terminal/shellmodel.cpp b/src/plugins/terminal/shellmodel.cpp
new file mode 100644
index 00000000000..b4cf53a1e99
--- /dev/null
+++ b/src/plugins/terminal/shellmodel.cpp
@@ -0,0 +1,110 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "shellmodel.h"
+
+#include <utils/algorithm.h>
+#include <utils/environment.h>
+#include <utils/filepath.h>
+
+#include <QFileIconProvider>
+#include <QStandardPaths>
+
+namespace Terminal::Internal {
+
+using namespace Utils;
+
+FilePaths availableShells()
+{
+ if (Utils::HostOsInfo::isWindowsHost()) {
+ FilePaths shells;
+
+ FilePath comspec = FilePath::fromUserInput(qtcEnvironmentVariable("COMSPEC"));
+ shells << comspec;
+
+ if (comspec.fileName() != "cmd.exe") {
+ FilePath cmd = FilePath::fromUserInput(QStandardPaths::findExecutable("cmd.exe"));
+ shells << cmd;
+ }
+
+ FilePath powershell = FilePath::fromUserInput(
+ QStandardPaths::findExecutable("powershell.exe"));
+ if (powershell.exists())
+ shells << powershell;
+
+ FilePath bash = FilePath::fromUserInput(QStandardPaths::findExecutable("bash.exe"));
+ if (bash.exists())
+ shells << bash;
+
+ FilePath git_bash = FilePath::fromUserInput(QStandardPaths::findExecutable("git.exe"));
+ if (git_bash.exists())
+ shells << git_bash.parentDir().parentDir().pathAppended("usr/bin/bash.exe");
+
+ FilePath msys2_bash = FilePath::fromUserInput(QStandardPaths::findExecutable("msys2.exe"));
+ if (msys2_bash.exists())
+ shells << msys2_bash.parentDir().pathAppended("usr/bin/bash.exe");
+
+ return shells;
+ } else {
+ FilePath shellsFile = FilePath::fromString("/etc/shells");
+ const auto shellFileContent = shellsFile.fileContents();
+ QTC_ASSERT_EXPECTED(shellFileContent, return {});
+
+ QString shellFileContentString = QString::fromUtf8(*shellFileContent);
+
+ // Filter out comments ...
+ const QStringList lines
+ = Utils::filtered(shellFileContentString.split('\n', Qt::SkipEmptyParts),
+ [](const QString &line) { return !line.trimmed().startsWith('#'); });
+
+ // Convert lines to file paths ...
+ const FilePaths shells = Utils::transform(lines, [](const QString &line) {
+ return FilePath::fromUserInput(line.trimmed());
+ });
+
+ // ... and filter out non-existing shells.
+ return Utils::filtered(shells, [](const FilePath &shell) { return shell.exists(); });
+ }
+}
+
+struct ShellModelPrivate
+{
+ QList<ShellModelItem> localShells;
+};
+
+ShellModel::ShellModel(QObject *parent)
+ : QObject(parent)
+ , d(new ShellModelPrivate())
+{
+ QFileIconProvider iconProvider;
+
+ const FilePaths shells = availableShells();
+ for (const FilePath &shell : shells) {
+ ShellModelItem item;
+ item.icon = iconProvider.icon(shell.toFileInfo());
+ item.name = shell.toUserOutput();
+ item.openParameters.shellCommand = {shell, {}};
+ d->localShells << item;
+ }
+}
+
+ShellModel::~ShellModel() = default;
+
+QList<ShellModelItem> ShellModel::local() const
+{
+ return d->localShells;
+}
+
+QList<ShellModelItem> ShellModel::remote() const
+{
+ const auto deviceCmds = Utils::Terminal::Hooks::instance().getTerminalCommandsForDevicesHook()();
+
+ const QList<ShellModelItem> deviceItems = Utils::transform(
+ deviceCmds, [](const Utils::Terminal::NameAndCommandLine &item) -> ShellModelItem {
+ return ShellModelItem{item.name, {}, {item.commandLine, std::nullopt, std::nullopt}};
+ });
+
+ return deviceItems;
+}
+
+} // namespace Terminal::Internal
diff --git a/src/plugins/terminal/shellmodel.h b/src/plugins/terminal/shellmodel.h
new file mode 100644
index 00000000000..272f3fcd394
--- /dev/null
+++ b/src/plugins/terminal/shellmodel.h
@@ -0,0 +1,37 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <utils/terminalhooks.h>
+
+#include <QIcon>
+#include <QObject>
+#include <QString>
+
+#include <memory>
+
+namespace Terminal::Internal {
+struct ShellModelPrivate;
+
+struct ShellModelItem
+{
+ QString name;
+ QIcon icon;
+ Utils::Terminal::OpenTerminalParameters openParameters;
+};
+
+class ShellModel : public QObject
+{
+public:
+ ShellModel(QObject *parent = nullptr);
+ ~ShellModel();
+
+ QList<ShellModelItem> local() const;
+ QList<ShellModelItem> remote() const;
+
+private:
+ std::unique_ptr<ShellModelPrivate> d;
+};
+
+} // namespace Terminal::Internal
diff --git a/src/plugins/terminal/terminal.qbs b/src/plugins/terminal/terminal.qbs
new file mode 100644
index 00000000000..231d3630b7b
--- /dev/null
+++ b/src/plugins/terminal/terminal.qbs
@@ -0,0 +1,46 @@
+import qbs 1.0
+
+QtcPlugin {
+ name: "Terminal"
+
+ Depends { name: "Core" }
+ Depends { name: "ProjectExplorer" }
+ Depends { name: "vterm" }
+ Depends { name: "ptyqt" }
+
+ files: [
+ "celliterator.cpp",
+ "celliterator.h",
+ "glyphcache.cpp",
+ "glyphcache.h",
+ "keys.cpp",
+ "keys.h",
+ "scrollback.cpp",
+ "scrollback.h",
+ "shellmodel.cpp",
+ "shellmodel.h",
+ "shellintegration.cpp",
+ "shellintegration.h",
+ "terminal.qrc",
+ "terminalcommands.cpp",
+ "terminalcommands.h",
+ "terminalpane.cpp",
+ "terminalpane.h",
+ "terminalplugin.cpp",
+ "terminalplugin.h",
+ "terminalprocessimpl.cpp",
+ "terminalprocessimpl.h",
+ "terminalsearch.cpp",
+ "terminalsearch.h",
+ "terminalsettings.cpp",
+ "terminalsettings.h",
+ "terminalsettingspage.cpp",
+ "terminalsettingspage.h",
+ "terminalsurface.cpp",
+ "terminalsurface.h",
+ "terminaltr.h",
+ "terminalwidget.cpp",
+ "terminalwidget.h",
+ ]
+}
+
diff --git a/src/plugins/terminal/terminal.qrc b/src/plugins/terminal/terminal.qrc
new file mode 100644
index 00000000000..50fcff4f2f0
--- /dev/null
+++ b/src/plugins/terminal/terminal.qrc
@@ -0,0 +1,15 @@
+<RCC>
+ <qresource prefix="/terminal">
+ <file>images/settingscategory_terminal.png</file>
+ <file>images/[email protected]</file>
+ <file>images/terminal.png</file>
+ <file>images/[email protected]</file>
+ <file>shellintegrations/shellintegration-bash.sh</file>
+ <file>shellintegrations/shellintegration-env.zsh</file>
+ <file>shellintegrations/shellintegration-login.zsh</file>
+ <file>shellintegrations/shellintegration-profile.zsh</file>
+ <file>shellintegrations/shellintegration-rc.zsh</file>
+ <file>shellintegrations/shellintegration.fish</file>
+ <file>shellintegrations/shellintegration.ps1</file>
+ </qresource>
+</RCC>
diff --git a/src/plugins/terminal/terminalcommands.cpp b/src/plugins/terminal/terminalcommands.cpp
new file mode 100644
index 00000000000..2313ac722e4
--- /dev/null
+++ b/src/plugins/terminal/terminalcommands.cpp
@@ -0,0 +1,182 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "terminalcommands.h"
+#include "terminaltr.h"
+
+#include <coreplugin/actionmanager/actionmanager.h>
+#include <coreplugin/coreconstants.h>
+#include <coreplugin/find/textfindconstants.h>
+#include <coreplugin/locator/locatorconstants.h>
+
+#include <utils/hostosinfo.h>
+
+//#include <coreplugin/context.h>
+
+using namespace Core;
+using namespace Utils;
+
+namespace Terminal {
+
+constexpr char COPY[] = "Terminal.Copy";
+constexpr char PASTE[] = "Terminal.Paste";
+constexpr char CLEARSELECTION[] = "Terminal.ClearSelection";
+constexpr char MOVECURSORWORDLEFT[] = "Terminal.MoveCursorWordLeft";
+constexpr char MOVECURSORWORDRIGHT[] = "Terminal.MoveCursorWordRight";
+
+constexpr char NEWTERMINAL[] = "Terminal.NewTerminal";
+constexpr char CLOSETERMINAL[] = "Terminal.CloseTerminal";
+constexpr char NEXTTERMINAL[] = "Terminal.NextTerminal";
+constexpr char PREVTERMINAL[] = "Terminal.PrevTerminal";
+constexpr char MINMAX[] = "Terminal.MinMax";
+
+TerminalCommands &TerminalCommands::instance()
+{
+ static TerminalCommands instance;
+ return instance;
+}
+
+TerminalCommands::TerminalCommands() {}
+
+void TerminalCommands::init(const Core::Context &context)
+{
+ m_context = context;
+ initWidgetActions();
+ initPaneActions();
+ initGlobalCommands();
+}
+
+void TerminalCommands::registerAction(QAction &action,
+ const Utils::Id &id,
+ QList<QKeySequence> shortCuts)
+{
+ Command *cmd = ActionManager::instance()->registerAction(&action, id, m_context);
+ cmd->setKeySequences(shortCuts);
+ m_commands.push_back(cmd);
+}
+
+void TerminalCommands::initWidgetActions()
+{
+ m_widgetActions.copy.setText(Tr::tr("Copy"));
+ m_widgetActions.paste.setText(Tr::tr("Paste"));
+ m_widgetActions.clearSelection.setText(Tr::tr("Clear Selection"));
+ m_widgetActions.clearTerminal.setText(Tr::tr("Clear Terminal"));
+ m_widgetActions.moveCursorWordLeft.setText(Tr::tr("Move Cursor Word Left"));
+ m_widgetActions.moveCursorWordRight.setText(Tr::tr("Move Cursor Word Right"));
+ m_widgetActions.findNext.setText(Tr::tr("Find Next"));
+ m_widgetActions.findPrevious.setText(Tr::tr("Find Previous"));
+
+ registerAction(m_widgetActions.copy,
+ COPY,
+ {QKeySequence(HostOsInfo::isMacHost() ? QLatin1String("Ctrl+C")
+ : QLatin1String("Ctrl+Shift+C"))});
+
+ registerAction(m_widgetActions.paste,
+ PASTE,
+ {QKeySequence(HostOsInfo::isMacHost() ? QLatin1String("Ctrl+V")
+ : QLatin1String("Ctrl+Shift+V"))});
+
+ registerAction(m_widgetActions.clearSelection, CLEARSELECTION);
+
+ registerAction(m_widgetActions.moveCursorWordLeft,
+ MOVECURSORWORDLEFT,
+ {QKeySequence("Alt+Left")});
+
+ registerAction(m_widgetActions.moveCursorWordRight,
+ MOVECURSORWORDRIGHT,
+ {QKeySequence("Alt+Right")});
+}
+
+void TerminalCommands::initPaneActions()
+{
+ m_paneActions.newTerminal.setText(Tr::tr("New Terminal"));
+ m_paneActions.closeTerminal.setText(Tr::tr("Close Terminal"));
+ m_paneActions.nextTerminal.setText(Tr::tr("Next Terminal"));
+ m_paneActions.prevTerminal.setText(Tr::tr("Previous Terminal"));
+ m_paneActions.minMax.setText(Tr::tr("Minimize/Maximize Terminal"));
+
+ registerAction(m_paneActions.newTerminal,
+ NEWTERMINAL,
+ {QKeySequence(HostOsInfo::isMacHost() ? QLatin1String("Ctrl+T")
+ : QLatin1String("Ctrl+Shift+T"))});
+
+ registerAction(m_paneActions.closeTerminal,
+ CLOSETERMINAL,
+ {QKeySequence(HostOsInfo::isMacHost() ? QLatin1String("Ctrl+W")
+ : QLatin1String("Ctrl+Shift+W"))});
+
+ registerAction(m_paneActions.nextTerminal,
+ NEXTTERMINAL,
+ {QKeySequence("ALT+TAB"),
+ QKeySequence(HostOsInfo::isMacHost() ? QLatin1String("Ctrl+Shift+[")
+ : QLatin1String("Ctrl+PgUp"))});
+
+ registerAction(m_paneActions.prevTerminal,
+ PREVTERMINAL,
+ {QKeySequence("ALT+SHIFT+TAB"),
+ QKeySequence(HostOsInfo::isMacHost() ? QLatin1String("Ctrl+Shift+]")
+ : QLatin1String("Ctrl+PgDown"))});
+
+ registerAction(m_paneActions.minMax,
+ MINMAX,
+ {QKeySequence(HostOsInfo::isMacHost() ? QLatin1String("Ctrl+Return")
+ : QLatin1String("Alt+Return"))});
+}
+
+void TerminalCommands::initGlobalCommands()
+{
+ // Global commands we still want to allow
+ m_commands.push_back(ActionManager::command(Constants::ZOOM_IN));
+ m_commands.push_back(ActionManager::command(Constants::ZOOM_OUT));
+ m_commands.push_back(ActionManager::command(Constants::EXIT));
+ m_commands.push_back(ActionManager::command(Constants::OPTIONS));
+}
+
+bool TerminalCommands::triggerAction(QKeyEvent *event)
+{
+ QKeyCombination combination = event->keyCombination();
+
+ // On macOS, the arrow keys include the KeypadModifier, which we don't want.
+ if (HostOsInfo::isMacHost() && combination.keyboardModifiers() & Qt::KeypadModifier)
+ combination = QKeyCombination(combination.keyboardModifiers() & ~Qt::KeypadModifier,
+ combination.key());
+
+ for (const auto &command : TerminalCommands::instance().m_commands) {
+ if (!command->action()->isEnabled())
+ continue;
+
+ for (const auto &shortcut : command->keySequences()) {
+ const auto result = shortcut.matches(QKeySequence(combination));
+ if (result == QKeySequence::ExactMatch) {
+ command->action()->trigger();
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+QAction *TerminalCommands::openSettingsAction()
+{
+ return ActionManager::command("Preferences.Terminal.General")->action();
+}
+
+void TerminalCommands::lazyInitCommand(const Utils::Id &id)
+{
+ Command *cmd = ActionManager::command(id);
+ QTC_ASSERT(cmd, return);
+ m_commands.append(cmd);
+}
+
+void TerminalCommands::lazyInitCommands()
+{
+ static const Utils::Id terminalPaneCmd("QtCreator.Pane.Terminal");
+ lazyInitCommand(terminalPaneCmd);
+ lazyInitCommand(Core::Constants::FIND_IN_DOCUMENT);
+ lazyInitCommand(Core::Constants::FIND_NEXT);
+ lazyInitCommand(Core::Constants::FIND_PREVIOUS);
+ lazyInitCommand(Core::Constants::LOCATE);
+}
+
+} // namespace Terminal
diff --git a/src/plugins/terminal/terminalcommands.h b/src/plugins/terminal/terminalcommands.h
new file mode 100644
index 00000000000..80af894d662
--- /dev/null
+++ b/src/plugins/terminal/terminalcommands.h
@@ -0,0 +1,75 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <utils/id.h>
+
+#include <coreplugin/icontext.h>
+
+#include <QAction>
+#include <QCoreApplication>
+#include <QKeyEvent>
+
+namespace Core {
+class Command;
+class Context;
+} // namespace Core
+
+namespace Terminal {
+
+struct WidgetActions
+{
+ QAction copy;
+ QAction paste;
+ QAction clearSelection;
+ QAction clearTerminal;
+ QAction moveCursorWordLeft;
+ QAction moveCursorWordRight;
+ QAction findNext;
+ QAction findPrevious;
+};
+
+struct PaneActions
+{
+ QAction newTerminal;
+ QAction closeTerminal;
+ QAction nextTerminal;
+ QAction prevTerminal;
+ QAction minMax;
+};
+
+class TerminalCommands
+{
+public:
+ TerminalCommands();
+
+ void init(const Core::Context &context);
+ static TerminalCommands &instance();
+ static WidgetActions &widgetActions() { return instance().m_widgetActions; }
+ static PaneActions &paneActions() { return instance().m_paneActions; }
+
+ static QList<QKeySequence> shortcutsFor(QAction *action);
+
+ static bool triggerAction(QKeyEvent *event);
+
+ static QAction *openSettingsAction();
+
+ void lazyInitCommands();
+
+protected:
+ void initWidgetActions();
+ void initPaneActions();
+ void initGlobalCommands();
+
+ void lazyInitCommand(const Utils::Id &id);
+ void registerAction(QAction &action, const Utils::Id &id, QList<QKeySequence> shortcuts = {});
+
+private:
+ WidgetActions m_widgetActions;
+ PaneActions m_paneActions;
+ QList<Core::Command *> m_commands;
+ Core::Context m_context;
+};
+
+} // namespace Terminal
diff --git a/src/plugins/terminal/terminalpane.cpp b/src/plugins/terminal/terminalpane.cpp
new file mode 100644
index 00000000000..3d110e764ee
--- /dev/null
+++ b/src/plugins/terminal/terminalpane.cpp
@@ -0,0 +1,379 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "terminalpane.h"
+
+#include "shellmodel.h"
+#include "terminalcommands.h"
+#include "terminalsettings.h"
+#include "terminaltr.h"
+#include "terminalwidget.h"
+
+#include <coreplugin/actionmanager/actionmanager.h>
+#include <coreplugin/icontext.h>
+#include <coreplugin/icore.h>
+
+#include <projectexplorer/project.h>
+#include <projectexplorer/projectmanager.h>
+
+#include <utils/algorithm.h>
+#include <utils/environment.h>
+#include <utils/terminalhooks.h>
+#include <utils/utilsicons.h>
+
+#include <QMenu>
+#include <QStandardPaths>
+#include <QToolButton>
+
+namespace Terminal {
+
+using namespace Utils;
+using namespace Utils::Terminal;
+
+TerminalPane::TerminalPane(QObject *parent)
+ : Core::IOutputPane(parent)
+ , m_tabWidget(new QTabWidget)
+{
+ setupContext("Terminal.Pane", m_tabWidget);
+ setZoomButtonsEnabled(true);
+
+ TerminalCommands::instance().init(Core::Context("Terminal.Pane"));
+
+ connect(this, &IOutputPane::zoomInRequested, this, [this] {
+ if (currentTerminal())
+ currentTerminal()->zoomIn();
+ });
+ connect(this, &IOutputPane::zoomOutRequested, this, [this] {
+ if (currentTerminal())
+ currentTerminal()->zoomOut();
+ });
+
+ QAction &newTerminal = TerminalCommands::instance().paneActions().newTerminal;
+ QAction &closeTerminal = TerminalCommands::instance().paneActions().closeTerminal;
+
+ newTerminal.setIcon(
+ Icon({{":/terminal/images/terminal.png", Theme::IconsBaseColor},
+ {":/utils/images/iconoverlay_add_small.png", Theme::IconsRunToolBarColor}})
+ .icon());
+ newTerminal.setToolTip(Tr::tr("Create a new Terminal."));
+
+ connect(&newTerminal, &QAction::triggered, this, [this] { openTerminal({}); });
+
+ closeTerminal.setIcon(
+ Icon({{":/terminal/images/terminal.png", Theme::IconsBaseColor},
+ {":/utils/images/iconoverlay_close_small.png", Theme::IconsStopToolBarColor}})
+ .icon());
+ closeTerminal.setToolTip(Tr::tr("Close the current Terminal."));
+ closeTerminal.setEnabled(false);
+
+ connect(&closeTerminal, &QAction::triggered, this, [this] {
+ removeTab(m_tabWidget->currentIndex());
+ });
+
+ m_newTerminalButton = new QToolButton();
+
+ QMenu *shellMenu = new QMenu(m_newTerminalButton);
+ Internal::ShellModel *shellModel = new Internal::ShellModel(shellMenu);
+ connect(shellMenu, &QMenu::aboutToShow, shellMenu, [shellMenu, shellModel, pane = this] {
+ shellMenu->clear();
+
+ const auto addItems = [shellMenu, pane](const QList<Internal::ShellModelItem> &items) {
+ for (const Internal::ShellModelItem &item : items) {
+ QAction *action = new QAction(item.icon, item.name, shellMenu);
+
+ connect(action, &QAction::triggered, action, [item, pane]() {
+ pane->openTerminal(item.openParameters);
+ });
+
+ shellMenu->addAction(action);
+ }
+ };
+
+ addItems(shellModel->local());
+ shellMenu->addSection(Tr::tr("Devices"));
+ addItems(shellModel->remote());
+ });
+
+ newTerminal.setMenu(shellMenu);
+
+ m_newTerminalButton->setDefaultAction(&newTerminal);
+
+ m_closeTerminalButton = new QToolButton();
+ m_closeTerminalButton->setDefaultAction(&closeTerminal);
+
+ connect(&TerminalCommands::instance().paneActions().nextTerminal,
+ &QAction::triggered,
+ this,
+ [this] {
+ if (canNavigate())
+ goToNext();
+ });
+ connect(&TerminalCommands::instance().paneActions().prevTerminal,
+ &QAction::triggered,
+ this,
+ [this] {
+ if (canPrevious())
+ goToPrev();
+ });
+
+ connect(&TerminalCommands::instance().paneActions().minMax, &QAction::triggered, this, []() {
+ Core::Command *minMaxCommand = Core::ActionManager::command("Coreplugin.OutputPane.minmax");
+ if (minMaxCommand)
+ emit minMaxCommand->action()->triggered();
+ });
+
+ m_openSettingsButton = new QToolButton();
+ m_openSettingsButton->setToolTip(Tr::tr("Open Terminal Settings"));
+ m_openSettingsButton->setIcon(Icons::SETTINGS_TOOLBAR.icon());
+
+ connect(m_openSettingsButton, &QToolButton::clicked, m_openSettingsButton, []() {
+ TerminalCommands::openSettingsAction()->trigger();
+ });
+
+ auto updateEscButton = [this] {
+ m_escSettingButton->setChecked(TerminalSettings::instance().sendEscapeToTerminal.value());
+ static QString escKey = QKeySequence(Qt::Key_Escape).toString(QKeySequence::NativeText);
+ static QString shiftEsc = QKeySequence(QKeyCombination(Qt::ShiftModifier, Qt::Key_Escape))
+ .toString(QKeySequence::NativeText);
+ if (TerminalSettings::instance().sendEscapeToTerminal.value()) {
+ m_escSettingButton->setText(escKey);
+ m_escSettingButton->setToolTip(Tr::tr("Sending ESC to terminal instead of Qt Creator"));
+ } else {
+ m_escSettingButton->setText(shiftEsc);
+ m_escSettingButton->setToolTip(Tr::tr("Press %1 to send ESC to terminal").arg(shiftEsc));
+ }
+ };
+
+ m_escSettingButton = new QToolButton();
+ m_escSettingButton->setCheckable(true);
+
+ updateEscButton();
+
+ connect(m_escSettingButton, &QToolButton::toggled, this, [this] {
+ TerminalSettings::instance().sendEscapeToTerminal.setValue(m_escSettingButton->isChecked());
+ TerminalSettings::instance().apply();
+ TerminalSettings::instance().writeSettings(Core::ICore::settings());
+ });
+
+ connect(&TerminalSettings::instance(), &TerminalSettings::applied, this, updateEscButton);
+}
+
+TerminalPane::~TerminalPane()
+{
+ delete m_tabWidget;
+}
+
+static std::optional<FilePath> startupProjectDirectory()
+{
+ ProjectExplorer::Project *project = ProjectExplorer::ProjectManager::startupProject();
+ if (!project)
+ return std::nullopt;
+
+ return project->projectDirectory();
+}
+
+void TerminalPane::openTerminal(const OpenTerminalParameters &parameters)
+{
+ OpenTerminalParameters parametersCopy{parameters};
+ showPage(0);
+
+ if (!parametersCopy.workingDirectory) {
+ const std::optional<FilePath> projectDir = startupProjectDirectory();
+ if (projectDir) {
+ if (!parametersCopy.shellCommand
+ || parametersCopy.shellCommand->executable().ensureReachable(*projectDir)) {
+ parametersCopy.workingDirectory = *projectDir;
+ }
+ }
+ }
+
+ auto terminalWidget = new TerminalWidget(m_tabWidget, parametersCopy);
+ m_tabWidget->setCurrentIndex(m_tabWidget->addTab(terminalWidget, Tr::tr("Terminal")));
+ setupTerminalWidget(terminalWidget);
+
+ m_tabWidget->currentWidget()->setFocus();
+
+ TerminalCommands::instance().paneActions().closeTerminal.setEnabled(m_tabWidget->count() > 1);
+ emit navigateStateUpdate();
+}
+
+void TerminalPane::addTerminal(TerminalWidget *terminal, const QString &title)
+{
+ showPage(0);
+ m_tabWidget->setCurrentIndex(m_tabWidget->addTab(terminal, title));
+ setupTerminalWidget(terminal);
+
+ TerminalCommands::instance().paneActions().closeTerminal.setEnabled(m_tabWidget->count() > 1);
+ emit navigateStateUpdate();
+}
+
+TerminalWidget *TerminalPane::stoppedTerminalWithId(const Id &identifier) const
+{
+ QTC_ASSERT(m_tabWidget, return nullptr);
+
+ for (int i = 0; i < m_tabWidget->count(); ++i) {
+ auto terminal = qobject_cast<TerminalWidget *>(m_tabWidget->widget(i));
+ if (terminal->processState() == QProcess::NotRunning && terminal->identifier() == identifier)
+ return terminal;
+ }
+
+ return nullptr;
+}
+
+QWidget *TerminalPane::outputWidget(QWidget *parent)
+{
+ if (!m_widgetInitialized) {
+ m_widgetInitialized = true;
+ m_tabWidget->setTabBarAutoHide(false);
+ m_tabWidget->setDocumentMode(true);
+ m_tabWidget->setTabsClosable(true);
+ m_tabWidget->setMovable(true);
+
+ connect(m_tabWidget, &QTabWidget::tabCloseRequested, this, [this](int index) {
+ removeTab(index);
+ });
+
+ connect(m_tabWidget, &QTabWidget::currentChanged, this, [this](int index) {
+ if (auto widget = m_tabWidget->widget(index))
+ widget->setFocus();
+ });
+
+ auto terminalWidget = new TerminalWidget(parent);
+ m_tabWidget->addTab(terminalWidget, Tr::tr("Terminal"));
+ setupTerminalWidget(terminalWidget);
+ }
+
+ return m_tabWidget;
+}
+
+TerminalWidget *TerminalPane::currentTerminal() const
+{
+ QWidget *activeWidget = m_tabWidget->currentWidget();
+ return static_cast<TerminalWidget *>(activeWidget);
+}
+
+void TerminalPane::removeTab(int index)
+{
+ if (m_tabWidget->count() > 1)
+ delete m_tabWidget->widget(index);
+
+ TerminalCommands::instance().paneActions().closeTerminal.setEnabled(m_tabWidget->count() > 1);
+
+ emit navigateStateUpdate();
+}
+
+void TerminalPane::setupTerminalWidget(TerminalWidget *terminal)
+{
+ if (!terminal)
+ return;
+
+ auto setTabText = [this](TerminalWidget *terminal) {
+ auto index = m_tabWidget->indexOf(terminal);
+ const FilePath cwd = terminal->cwd();
+
+ const QString exe = terminal->currentCommand().isEmpty()
+ ? terminal->shellName()
+ : terminal->currentCommand().executable().fileName();
+
+ if (cwd.isEmpty())
+ m_tabWidget->setTabText(index, exe);
+ else
+ m_tabWidget->setTabText(index, exe + " - " + cwd.fileName());
+ };
+
+ connect(terminal, &TerminalWidget::started, this, [setTabText, terminal](qint64 /*pid*/) {
+ setTabText(terminal);
+ });
+
+ connect(terminal, &TerminalWidget::cwdChanged, this, [setTabText, terminal]() {
+ setTabText(terminal);
+ });
+
+ connect(terminal, &TerminalWidget::commandChanged, this, [setTabText, terminal]() {
+ setTabText(terminal);
+ });
+
+ if (!terminal->shellName().isEmpty())
+ setTabText(terminal);
+}
+
+QList<QWidget *> TerminalPane::toolBarWidgets() const
+{
+ QList<QWidget *> widgets = IOutputPane::toolBarWidgets();
+ widgets.prepend(m_newTerminalButton);
+ widgets.prepend(m_closeTerminalButton);
+
+ return widgets << m_openSettingsButton << m_escSettingButton;
+}
+
+QString TerminalPane::displayName() const
+{
+ return Tr::tr("Terminal");
+}
+
+int TerminalPane::priorityInStatusBar() const
+{
+ return 50;
+}
+
+void TerminalPane::clearContents()
+{
+ if (const auto t = currentTerminal())
+ t->clearContents();
+}
+
+void TerminalPane::setFocus()
+{
+ if (const auto t = currentTerminal())
+ t->setFocus();
+}
+
+bool TerminalPane::hasFocus() const
+{
+ if (const auto t = currentTerminal())
+ return t->hasFocus();
+
+ return false;
+}
+
+bool TerminalPane::canFocus() const
+{
+ return true;
+}
+
+bool TerminalPane::canNavigate() const
+{
+ return true;
+}
+
+bool TerminalPane::canNext() const
+{
+ return m_tabWidget->count() > 1;
+}
+
+bool TerminalPane::canPrevious() const
+{
+ return m_tabWidget->count() > 1;
+}
+
+void TerminalPane::goToNext()
+{
+ int nextIndex = m_tabWidget->currentIndex() + 1;
+ if (nextIndex >= m_tabWidget->count())
+ nextIndex = 0;
+
+ m_tabWidget->setCurrentIndex(nextIndex);
+ emit navigateStateUpdate();
+}
+
+void TerminalPane::goToPrev()
+{
+ int prevIndex = m_tabWidget->currentIndex() - 1;
+ if (prevIndex < 0)
+ prevIndex = m_tabWidget->count() - 1;
+
+ m_tabWidget->setCurrentIndex(prevIndex);
+ emit navigateStateUpdate();
+}
+
+} // namespace Terminal
diff --git a/src/plugins/terminal/terminalpane.h b/src/plugins/terminal/terminalpane.h
new file mode 100644
index 00000000000..5d93dcb4d9a
--- /dev/null
+++ b/src/plugins/terminal/terminalpane.h
@@ -0,0 +1,61 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <coreplugin/ioutputpane.h>
+
+#include <utils/terminalhooks.h>
+
+#include <QAction>
+#include <QTabWidget>
+#include <QToolButton>
+
+namespace Terminal {
+
+class TerminalWidget;
+
+class TerminalPane : public Core::IOutputPane
+{
+ Q_OBJECT
+public:
+ TerminalPane(QObject *parent = nullptr);
+ ~TerminalPane() override;
+
+ QWidget *outputWidget(QWidget *parent) override;
+ QList<QWidget *> toolBarWidgets() const override;
+ QString displayName() const override;
+ int priorityInStatusBar() const override;
+ void clearContents() override;
+ void setFocus() override;
+ bool hasFocus() const override;
+ bool canFocus() const override;
+ bool canNavigate() const override;
+ bool canNext() const override;
+ bool canPrevious() const override;
+ void goToNext() override;
+ void goToPrev() override;
+
+ void openTerminal(const Utils::Terminal::OpenTerminalParameters &parameters);
+ void addTerminal(TerminalWidget *terminal, const QString &title);
+
+ TerminalWidget *stoppedTerminalWithId(const Utils::Id &identifier) const;
+
+private:
+ TerminalWidget *currentTerminal() const;
+
+ void removeTab(int index);
+ void setupTerminalWidget(TerminalWidget *terminal);
+
+private:
+ QTabWidget *m_tabWidget{nullptr};
+
+ QToolButton *m_newTerminalButton{nullptr};
+ QToolButton *m_closeTerminalButton{nullptr};
+ QToolButton *m_openSettingsButton{nullptr};
+ QToolButton *m_escSettingButton{nullptr};
+
+ bool m_widgetInitialized{false};
+};
+
+} // namespace Terminal
diff --git a/src/plugins/terminal/terminalplugin.cpp b/src/plugins/terminal/terminalplugin.cpp
new file mode 100644
index 00000000000..9062e417f0f
--- /dev/null
+++ b/src/plugins/terminal/terminalplugin.cpp
@@ -0,0 +1,84 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "terminalplugin.h"
+
+#include "terminalpane.h"
+#include "terminalprocessimpl.h"
+#include "terminalsettings.h"
+#include "terminalsettingspage.h"
+#include "terminalcommands.h"
+
+#include <coreplugin/actionmanager/actioncontainer.h>
+#include <coreplugin/actionmanager/actionmanager.h>
+#include <coreplugin/coreconstants.h>
+#include <coreplugin/icore.h>
+#include <coreplugin/imode.h>
+#include <coreplugin/modemanager.h>
+
+#include <extensionsystem/pluginmanager.h>
+
+#include <QAction>
+#include <QDebug>
+#include <QMenu>
+#include <QMessageBox>
+#include <QPushButton>
+
+namespace Terminal {
+namespace Internal {
+
+TerminalPlugin::TerminalPlugin() {}
+
+TerminalPlugin::~TerminalPlugin()
+{
+ ExtensionSystem::PluginManager::instance()->removeObject(m_terminalPane);
+ delete m_terminalPane;
+ m_terminalPane = nullptr;
+}
+
+bool TerminalPlugin::delayedInitialize()
+{
+ TerminalCommands::instance().lazyInitCommands();
+ return true;
+}
+
+void TerminalPlugin::extensionsInitialized()
+{
+ TerminalSettingsPage::instance().init();
+ TerminalSettings::instance().readSettings(Core::ICore::settings());
+
+ m_terminalPane = new TerminalPane();
+ ExtensionSystem::PluginManager::instance()->addObject(m_terminalPane);
+
+ auto enable = [this] {
+ Utils::Terminal::Hooks::instance()
+ .addCallbackSet("Internal",
+ {[this](const Utils::Terminal::OpenTerminalParameters &p) {
+ m_terminalPane->openTerminal(p);
+ },
+ [this] { return new TerminalProcessImpl(m_terminalPane); }});
+ };
+
+ auto disable = [] { Utils::Terminal::Hooks::instance().removeCallbackSet("Internal"); };
+
+ static bool isEnabled = false;
+ auto settingsChanged = [enable, disable] {
+ if (isEnabled != TerminalSettings::instance().enableTerminal.value()) {
+ isEnabled = TerminalSettings::instance().enableTerminal.value();
+ if (isEnabled)
+ enable();
+ else
+ disable();
+ }
+ };
+
+ QObject::connect(&TerminalSettings::instance(),
+ &Utils::AspectContainer::applied,
+ this,
+ settingsChanged);
+
+ settingsChanged();
+}
+
+} // namespace Internal
+} // namespace Terminal
diff --git a/src/plugins/terminal/terminalplugin.h b/src/plugins/terminal/terminalplugin.h
new file mode 100644
index 00000000000..2bfbd9f22e0
--- /dev/null
+++ b/src/plugins/terminal/terminalplugin.h
@@ -0,0 +1,30 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <extensionsystem/iplugin.h>
+
+namespace Terminal {
+
+class TerminalPane;
+namespace Internal {
+
+class TerminalPlugin : public ExtensionSystem::IPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Terminal.json")
+
+public:
+ TerminalPlugin();
+ ~TerminalPlugin() override;
+
+ void extensionsInitialized() override;
+ bool delayedInitialize() override;
+
+private:
+ TerminalPane *m_terminalPane{nullptr};
+};
+
+} // namespace Internal
+} // namespace Terminal
diff --git a/src/plugins/terminal/terminalprocessimpl.cpp b/src/plugins/terminal/terminalprocessimpl.cpp
new file mode 100644
index 00000000000..2397af50d17
--- /dev/null
+++ b/src/plugins/terminal/terminalprocessimpl.cpp
@@ -0,0 +1,68 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "terminalprocessimpl.h"
+#include "terminalwidget.h"
+
+#include <QCoreApplication>
+#include <QLocalServer>
+#include <QLocalSocket>
+#include <QLoggingCategory>
+#include <QTemporaryFile>
+#include <QTimer>
+
+Q_LOGGING_CATEGORY(terminalProcessLog, "qtc.terminal.stubprocess", QtDebugMsg)
+
+using namespace Utils;
+using namespace Utils::Terminal;
+
+namespace Terminal {
+
+class ProcessStubCreator : public StubCreator
+{
+public:
+ ProcessStubCreator(TerminalProcessImpl *interface, TerminalPane *terminalPane)
+ : m_terminalPane(terminalPane)
+ , m_process(interface)
+ {}
+
+ void startStubProcess(const CommandLine &cmd, const ProcessSetupData &setup) override
+ {
+ const Id id = Id::fromString(setup.m_commandLine.executable().toUserOutput());
+
+ TerminalWidget *terminal = m_terminalPane->stoppedTerminalWithId(id);
+
+ const OpenTerminalParameters openParameters{cmd,
+ std::nullopt,
+ std::nullopt,
+ ExitBehavior::Keep,
+ id};
+
+ if (!terminal) {
+ terminal = new TerminalWidget(nullptr, openParameters);
+
+ terminal->setShellName(setup.m_commandLine.executable().fileName());
+ m_terminalPane->addTerminal(terminal, "App");
+ } else {
+ terminal->restart(openParameters);
+ }
+
+ connect(terminal, &TerminalWidget::destroyed, m_process, [process = m_process] {
+ if (process->inferiorProcessId())
+ process->emitFinished(-1, QProcess::CrashExit);
+ });
+ }
+
+ TerminalPane *m_terminalPane;
+ TerminalProcessImpl *m_process;
+};
+
+TerminalProcessImpl::TerminalProcessImpl(TerminalPane *terminalPane)
+ : TerminalInterface(false)
+{
+ auto creator = new ProcessStubCreator(this, terminalPane);
+ creator->moveToThread(qApp->thread());
+ setStubCreator(creator);
+}
+
+} // namespace Terminal
diff --git a/src/plugins/terminal/terminalprocessimpl.h b/src/plugins/terminal/terminalprocessimpl.h
new file mode 100644
index 00000000000..f77f418281c
--- /dev/null
+++ b/src/plugins/terminal/terminalprocessimpl.h
@@ -0,0 +1,18 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "terminalpane.h"
+
+#include <utils/terminalinterface.h>
+
+namespace Terminal {
+
+class TerminalProcessImpl : public Utils::TerminalInterface
+{
+public:
+ TerminalProcessImpl(TerminalPane *terminalPane);
+};
+
+} // namespace Terminal
diff --git a/src/plugins/terminal/terminalsearch.cpp b/src/plugins/terminal/terminalsearch.cpp
new file mode 100644
index 00000000000..b69587298b7
--- /dev/null
+++ b/src/plugins/terminal/terminalsearch.cpp
@@ -0,0 +1,283 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "terminalsearch.h"
+#include "terminalcommands.h"
+
+#include <QElapsedTimer>
+#include <QLoggingCategory>
+#include <QRegularExpression>
+
+#include <chrono>
+
+Q_LOGGING_CATEGORY(terminalSearchLog, "qtc.terminal.search", QtWarningMsg)
+
+using namespace std::chrono_literals;
+
+namespace Terminal {
+
+using namespace Terminal::Internal;
+
+constexpr std::chrono::milliseconds debounceInterval = 100ms;
+
+TerminalSearch::TerminalSearch(TerminalSurface *surface)
+ : m_surface(surface)
+{
+ m_debounceTimer.setInterval(debounceInterval);
+ m_debounceTimer.setSingleShot(true);
+
+ connect(surface, &TerminalSurface::invalidated, this, &TerminalSearch::updateHits);
+ connect(&m_debounceTimer, &QTimer::timeout, this, &TerminalSearch::debouncedUpdateHits);
+
+ connect(&TerminalCommands::widgetActions().findNext,
+ &QAction::triggered,
+ this,
+ &TerminalSearch::nextHit);
+ connect(&TerminalCommands::widgetActions().findPrevious,
+ &QAction::triggered,
+ this,
+ &TerminalSearch::previousHit);
+}
+
+void TerminalSearch::setCurrentSelection(std::optional<SearchHitWithText> selection)
+{
+ m_currentSelection = selection;
+}
+
+void TerminalSearch::setSearchString(const QString &searchString, Core::FindFlags findFlags)
+{
+ if (m_currentSearchString != searchString || m_findFlags != findFlags) {
+ m_currentSearchString = searchString;
+ m_findFlags = findFlags;
+ updateHits();
+ }
+}
+
+void TerminalSearch::nextHit()
+{
+ if (m_hits.isEmpty())
+ return;
+
+ m_currentHit = (m_currentHit + 1) % m_hits.size();
+ emit currentHitChanged();
+}
+
+void TerminalSearch::previousHit()
+{
+ if (m_hits.isEmpty())
+ return;
+
+ m_currentHit = (m_currentHit - 1 + m_hits.size()) % m_hits.size();
+ emit currentHitChanged();
+}
+
+void TerminalSearch::updateHits()
+{
+ if (!m_hits.isEmpty()) {
+ m_hits.clear();
+ m_currentHit = -1;
+ emit hitsChanged();
+ emit currentHitChanged();
+ }
+
+ m_debounceTimer.start();
+}
+
+bool isSpace(char32_t a, char32_t b)
+{
+ if (a == std::numeric_limits<char32_t>::max())
+ return std::isspace(b);
+ else if (b == std::numeric_limits<char32_t>::max())
+ return std::isspace(a);
+
+ return false;
+}
+
+QList<SearchHit> TerminalSearch::search()
+{
+ QList<SearchHit> hits;
+
+ std::function<bool(char32_t, char32_t)> compare;
+
+ if (m_findFlags.testFlag(Core::FindFlag::FindCaseSensitively))
+ compare = [](char32_t a, char32_t b) {
+ return std::tolower(a) == std::tolower(b) || isSpace(a, b);
+ };
+ else
+ compare = [](char32_t a, char32_t b) { return a == b || isSpace(a, b); };
+
+ if (!m_currentSearchString.isEmpty()) {
+ const QList<uint> asUcs4 = m_currentSearchString.toUcs4();
+ std::u32string searchString(asUcs4.begin(), asUcs4.end());
+
+ if (m_findFlags.testFlag(Core::FindFlag::FindWholeWords)) {
+ searchString.push_back(std::numeric_limits<char32_t>::max());
+ searchString.insert(searchString.begin(), std::numeric_limits<char32_t>::max());
+ }
+
+ Internal::CellIterator it = m_surface->begin();
+ while (it != m_surface->end()) {
+ it = std::search(it, m_surface->end(), searchString.begin(), searchString.end(), compare);
+
+ if (it != m_surface->end()) {
+ auto hit = SearchHit{it.position(),
+ static_cast<int>(it.position() + searchString.size())};
+ if (m_findFlags.testFlag(Core::FindFlag::FindWholeWords)) {
+ hit.start++;
+ hit.end--;
+ }
+ hits << hit;
+ it += m_currentSearchString.size();
+ }
+ }
+ }
+ return hits;
+}
+
+QList<SearchHit> TerminalSearch::searchRegex()
+{
+ QList<SearchHit> hits;
+
+ QString allText;
+ allText.reserve(1000);
+
+ // Contains offsets at which there are characters > 2 bytes
+ QList<int> adjustTable;
+
+ for (auto it = m_surface->begin(); it != m_surface->end(); ++it) {
+ auto chs = QChar::fromUcs4(*it);
+ if (chs.size() > 1)
+ adjustTable << (allText.size());
+ allText += chs;
+ }
+
+ QRegularExpression re(m_currentSearchString,
+ m_findFlags.testFlag(Core::FindFlag::FindCaseSensitively)
+ ? QRegularExpression::NoPatternOption
+ : QRegularExpression::CaseInsensitiveOption);
+
+ QRegularExpressionMatchIterator it = re.globalMatch(allText);
+ int adjust = 0;
+ auto itAdjust = adjustTable.begin();
+ while (it.hasNext()) {
+ QRegularExpressionMatch match = it.next();
+ int s = match.capturedStart();
+ int e = match.capturedEnd();
+
+ // Update 'adjust' to account for characters > 2 bytes
+ if (itAdjust != adjustTable.end()) {
+ while (s > *itAdjust && itAdjust != adjustTable.end()) {
+ adjust++;
+ itAdjust++;
+ }
+ s -= adjust;
+ while (e > *itAdjust && itAdjust != adjustTable.end()) {
+ adjust++;
+ itAdjust++;
+ }
+ e -= adjust;
+ }
+ hits << SearchHit{s, e};
+ }
+
+ return hits;
+}
+
+void TerminalSearch::debouncedUpdateHits()
+{
+ QElapsedTimer t;
+ t.start();
+
+ m_currentHit = -1;
+
+ const bool regex = m_findFlags.testFlag(Core::FindFlag::FindRegularExpression);
+
+ QList<SearchHit> hits = regex ? searchRegex() : search();
+
+ if (hits != m_hits) {
+ m_currentHit = -1;
+ if (m_currentSelection)
+ m_currentHit = hits.indexOf(*m_currentSelection);
+
+ if (m_currentHit == -1 && !hits.isEmpty())
+ m_currentHit = 0;
+
+ m_hits = hits;
+ emit hitsChanged();
+ emit currentHitChanged();
+ emit changed();
+ }
+ if (!m_currentSearchString.isEmpty())
+ qCDebug(terminalSearchLog) << "Search took" << t.elapsed() << "ms";
+}
+
+Core::FindFlags TerminalSearch::supportedFindFlags() const
+{
+ return Core::FindFlag::FindCaseSensitively | Core::FindFlag::FindBackward
+ | Core::FindFlag::FindRegularExpression | Core::FindFlag::FindWholeWords;
+}
+
+void TerminalSearch::resetIncrementalSearch()
+{
+ m_currentSelection.reset();
+}
+
+void TerminalSearch::clearHighlights()
+{
+ setSearchString("", {});
+}
+
+QString TerminalSearch::currentFindString() const
+{
+ if (m_currentSelection)
+ return m_currentSelection->text;
+ else
+ return m_currentSearchString;
+}
+
+QString TerminalSearch::completedFindString() const
+{
+ return {};
+}
+
+Core::IFindSupport::Result TerminalSearch::findIncremental(const QString &txt,
+ Core::FindFlags findFlags)
+{
+ if (txt == m_currentSearchString) {
+ if (m_debounceTimer.isActive())
+ return Result::NotYetFound;
+ else if (m_hits.isEmpty())
+ return Result::NotFound;
+ else
+ return Result::Found;
+ }
+
+ setSearchString(txt, findFlags);
+ return Result::NotYetFound;
+}
+
+Core::IFindSupport::Result TerminalSearch::findStep(const QString &txt, Core::FindFlags findFlags)
+{
+ if (txt == m_currentSearchString) {
+ if (m_debounceTimer.isActive())
+ return Result::NotYetFound;
+ else if (m_hits.isEmpty())
+ return Result::NotFound;
+
+ if (findFlags.testFlag(Core::FindFlag::FindBackward))
+ previousHit();
+ else
+ nextHit();
+
+ return Result::Found;
+ }
+
+ return findIncremental(txt, findFlags);
+}
+
+void TerminalSearch::highlightAll(const QString &txt, Core::FindFlags findFlags)
+{
+ setSearchString(txt, findFlags);
+}
+
+} // namespace Terminal
diff --git a/src/plugins/terminal/terminalsearch.h b/src/plugins/terminal/terminalsearch.h
new file mode 100644
index 00000000000..29af50fa0da
--- /dev/null
+++ b/src/plugins/terminal/terminalsearch.h
@@ -0,0 +1,82 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "terminalsurface.h"
+
+#include <coreplugin/find/ifindsupport.h>
+#include <coreplugin/find/textfindconstants.h>
+
+#include <QTimer>
+
+namespace Terminal {
+
+struct SearchHit
+{
+ int start{-1};
+ int end{-1};
+
+ bool operator!=(const SearchHit &other) const
+ {
+ return start != other.start || end != other.end;
+ }
+ bool operator==(const SearchHit &other) const { return !operator!=(other); }
+};
+
+struct SearchHitWithText : SearchHit
+{
+ QString text;
+};
+
+class TerminalSearch : public Core::IFindSupport
+{
+ Q_OBJECT
+public:
+ TerminalSearch(Internal::TerminalSurface *surface);
+
+ void setCurrentSelection(std::optional<SearchHitWithText> selection);
+ void setSearchString(const QString &searchString, Core::FindFlags findFlags);
+ void nextHit();
+ void previousHit();
+
+ QList<SearchHit> hits() const { return m_hits; }
+ SearchHit currentHit() const
+ {
+ return m_currentHit >= 0 ? m_hits.at(m_currentHit) : SearchHit{};
+ }
+
+public:
+ bool supportsReplace() const override { return false; }
+ Core::FindFlags supportedFindFlags() const override;
+ void resetIncrementalSearch() override;
+ void clearHighlights() override;
+ QString currentFindString() const override;
+ QString completedFindString() const override;
+ Result findIncremental(const QString &txt, Core::FindFlags findFlags) override;
+ Result findStep(const QString &txt, Core::FindFlags findFlags) override;
+
+ void highlightAll(const QString &, Core::FindFlags) override;
+
+signals:
+ void hitsChanged();
+ void currentHitChanged();
+
+protected:
+ void updateHits();
+ void debouncedUpdateHits();
+ QList<SearchHit> search();
+ QList<SearchHit> searchRegex();
+
+private:
+ std::optional<SearchHitWithText> m_currentSelection;
+ QString m_currentSearchString;
+ Core::FindFlags m_findFlags;
+ Internal::TerminalSurface *m_surface;
+
+ int m_currentHit{-1};
+ QList<SearchHit> m_hits;
+ QTimer m_debounceTimer;
+};
+
+} // namespace Terminal
diff --git a/src/plugins/terminal/terminalsettings.cpp b/src/plugins/terminal/terminalsettings.cpp
new file mode 100644
index 00000000000..1f548a65fae
--- /dev/null
+++ b/src/plugins/terminal/terminalsettings.cpp
@@ -0,0 +1,177 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "terminalsettings.h"
+
+#include "terminaltr.h"
+
+#include <utils/environment.h>
+#include <utils/hostosinfo.h>
+#include <utils/theme/theme.h>
+
+using namespace Utils;
+
+namespace Terminal {
+
+static QString defaultFontFamily()
+{
+ if (HostOsInfo::isMacHost())
+ return QLatin1String("Menlo");
+
+ if (Utils::HostOsInfo::isAnyUnixHost())
+ return QLatin1String("Monospace");
+
+ return QLatin1String("Consolas");
+}
+
+static int defaultFontSize()
+{
+ if (Utils::HostOsInfo::isMacHost())
+ return 12;
+ if (Utils::HostOsInfo::isAnyUnixHost())
+ return 9;
+ return 10;
+}
+
+static QString defaultShell()
+{
+ if (HostOsInfo::isWindowsHost())
+ return qtcEnvironmentVariable("COMSPEC");
+
+ QString defaultShell = qtcEnvironmentVariable("SHELL");
+ if (FilePath::fromUserInput(defaultShell).isExecutableFile())
+ return defaultShell;
+
+ Utils::FilePath shPath = Utils::Environment::systemEnvironment().searchInPath("sh");
+ return shPath.nativePath();
+}
+
+TerminalSettings &TerminalSettings::instance()
+{
+ static TerminalSettings settings;
+ return settings;
+}
+
+void setupColor(TerminalSettings *settings,
+ ColorAspect &color,
+ const QString &label,
+ const QColor &defaultColor)
+{
+ color.setSettingsKey(label);
+ color.setDefaultValue(defaultColor);
+ color.setToolTip(Tr::tr("The color used for %1.").arg(label));
+
+ settings->registerAspect(&color);
+}
+
+TerminalSettings::TerminalSettings()
+{
+ setAutoApply(false);
+ setSettingsGroup("Terminal");
+
+ enableTerminal.setSettingsKey("EnableTerminal");
+ enableTerminal.setLabelText(Tr::tr("Use internal Terminal"));
+ enableTerminal.setToolTip(
+ Tr::tr("If enabled, use the internal terminal when \"Run In Terminal\" is "
+ "enabled and for \"Open Terminal here\"."));
+ enableTerminal.setDefaultValue(true);
+
+ font.setSettingsKey("FontFamily");
+ font.setLabelText(Tr::tr("Family:"));
+ font.setHistoryCompleter("Terminal.Fonts.History");
+ font.setToolTip(Tr::tr("The font family used in the terminal."));
+ font.setDefaultValue(defaultFontFamily());
+
+ fontSize.setSettingsKey("FontSize");
+ fontSize.setLabelText(Tr::tr("Size:"));
+ fontSize.setToolTip(Tr::tr("The font size used in the terminal. (in points)"));
+ fontSize.setDefaultValue(defaultFontSize());
+ fontSize.setRange(1, 100);
+
+ allowBlinkingCursor.setSettingsKey("AllowBlinkingCursor");
+ allowBlinkingCursor.setLabelText(Tr::tr("Allow blinking cursor"));
+ allowBlinkingCursor.setToolTip(Tr::tr("Allow the cursor to blink."));
+ allowBlinkingCursor.setDefaultValue(false);
+
+ shell.setSettingsKey("ShellPath");
+ shell.setLabelText(Tr::tr("Shell path:"));
+ shell.setExpectedKind(PathChooser::ExistingCommand);
+ shell.setDisplayStyle(StringAspect::PathChooserDisplay);
+ shell.setHistoryCompleter("Terminal.Shell.History");
+ shell.setToolTip(Tr::tr("The shell executable to be started as terminal"));
+ shell.setDefaultValue(defaultShell());
+
+ shellArguments.setSettingsKey("ShellArguments");
+ shellArguments.setLabelText(Tr::tr("Shell arguments:"));
+ shellArguments.setDisplayStyle(StringAspect::LineEditDisplay);
+ shellArguments.setHistoryCompleter("Terminal.Shell.History");
+ shellArguments.setToolTip(Tr::tr("The arguments to be passed to the shell"));
+ if (!HostOsInfo::isWindowsHost())
+ shellArguments.setDefaultValue(QString("-l"));
+
+ sendEscapeToTerminal.setSettingsKey("SendEscapeToTerminal");
+ sendEscapeToTerminal.setLabelText(Tr::tr("Send escape key to terminal"));
+ sendEscapeToTerminal.setToolTip(
+ Tr::tr("If enabled, pressing the escape key will send it to the terminal "
+ "instead of closing the terminal."));
+ sendEscapeToTerminal.setDefaultValue(false);
+
+ audibleBell.setSettingsKey("AudibleBell");
+ audibleBell.setLabelText(Tr::tr("Audible bell"));
+ audibleBell.setToolTip(Tr::tr("If enabled, the terminal will beep when a bell "
+ "character is received."));
+ audibleBell.setDefaultValue(true);
+
+ registerAspect(&font);
+ registerAspect(&fontSize);
+ registerAspect(&shell);
+ registerAspect(&allowBlinkingCursor);
+ registerAspect(&enableTerminal);
+ registerAspect(&sendEscapeToTerminal);
+ registerAspect(&audibleBell);
+ registerAspect(&shellArguments);
+
+ setupColor(this,
+ foregroundColor,
+ "Foreground",
+ Utils::creatorTheme()->color(Theme::TerminalForeground));
+ setupColor(this,
+ backgroundColor,
+ "Background",
+ Utils::creatorTheme()->color(Theme::TerminalBackground));
+ setupColor(this,
+ selectionColor,
+ "Selection",
+ Utils::creatorTheme()->color(Theme::TerminalSelection));
+
+ setupColor(this,
+ findMatchColor,
+ "Find matches",
+ Utils::creatorTheme()->color(Theme::TerminalFindMatch));
+
+ setupColor(this, colors[0], "0", Utils::creatorTheme()->color(Theme::TerminalAnsi0));
+ setupColor(this, colors[8], "8", Utils::creatorTheme()->color(Theme::TerminalAnsi8));
+
+ setupColor(this, colors[1], "1", Utils::creatorTheme()->color(Theme::TerminalAnsi1));
+ setupColor(this, colors[9], "9", Utils::creatorTheme()->color(Theme::TerminalAnsi9));
+
+ setupColor(this, colors[2], "2", Utils::creatorTheme()->color(Theme::TerminalAnsi2));
+ setupColor(this, colors[10], "10", Utils::creatorTheme()->color(Theme::TerminalAnsi10));
+
+ setupColor(this, colors[3], "3", Utils::creatorTheme()->color(Theme::TerminalAnsi3));
+ setupColor(this, colors[11], "11", Utils::creatorTheme()->color(Theme::TerminalAnsi11));
+
+ setupColor(this, colors[4], "4", Utils::creatorTheme()->color(Theme::TerminalAnsi4));
+ setupColor(this, colors[12], "12", Utils::creatorTheme()->color(Theme::TerminalAnsi12));
+
+ setupColor(this, colors[5], "5", Utils::creatorTheme()->color(Theme::TerminalAnsi5));
+ setupColor(this, colors[13], "13", Utils::creatorTheme()->color(Theme::TerminalAnsi13));
+
+ setupColor(this, colors[6], "6", Utils::creatorTheme()->color(Theme::TerminalAnsi6));
+ setupColor(this, colors[14], "14", Utils::creatorTheme()->color(Theme::TerminalAnsi14));
+
+ setupColor(this, colors[7], "7", Utils::creatorTheme()->color(Theme::TerminalAnsi7));
+ setupColor(this, colors[15], "15", Utils::creatorTheme()->color(Theme::TerminalAnsi15));
+}
+
+} // namespace Terminal
diff --git a/src/plugins/terminal/terminalsettings.h b/src/plugins/terminal/terminalsettings.h
new file mode 100644
index 00000000000..137f3abe3aa
--- /dev/null
+++ b/src/plugins/terminal/terminalsettings.h
@@ -0,0 +1,36 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <utils/aspects.h>
+
+namespace Terminal {
+class TerminalSettings : public Utils::AspectContainer
+{
+public:
+ TerminalSettings();
+
+ static TerminalSettings &instance();
+
+ Utils::BoolAspect enableTerminal;
+
+ Utils::StringAspect font;
+ Utils::IntegerAspect fontSize;
+ Utils::StringAspect shell;
+ Utils::StringAspect shellArguments;
+
+ Utils::ColorAspect foregroundColor;
+ Utils::ColorAspect backgroundColor;
+ Utils::ColorAspect selectionColor;
+ Utils::ColorAspect findMatchColor;
+
+ Utils::ColorAspect colors[16];
+
+ Utils::BoolAspect allowBlinkingCursor;
+
+ Utils::BoolAspect sendEscapeToTerminal;
+ Utils::BoolAspect audibleBell;
+};
+
+} // namespace Terminal
diff --git a/src/plugins/terminal/terminalsettingspage.cpp b/src/plugins/terminal/terminalsettingspage.cpp
new file mode 100644
index 00000000000..db7920bd61c
--- /dev/null
+++ b/src/plugins/terminal/terminalsettingspage.cpp
@@ -0,0 +1,456 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "terminalsettingspage.h"
+
+#include "terminalsettings.h"
+#include "terminaltr.h"
+
+#include <coreplugin/icore.h>
+
+#include <utils/aspects.h>
+#include <utils/dropsupport.h>
+#include <utils/expected.h>
+#include <utils/fileutils.h>
+#include <utils/layoutbuilder.h>
+#include <utils/pathchooser.h>
+
+#include <QFontComboBox>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QMessageBox>
+#include <QPushButton>
+#include <QRegularExpression>
+#include <QTemporaryFile>
+#include <QXmlStreamReader>
+
+using namespace Utils;
+
+namespace Terminal {
+
+TerminalSettingsPage::TerminalSettingsPage()
+{
+ setId("Terminal.General");
+ setDisplayName("Terminal");
+ setCategory("ZY.Terminal");
+ setDisplayCategory("Terminal");
+ setSettings(&TerminalSettings::instance());
+ setCategoryIconPath(":/terminal/images/settingscategory_terminal.png");
+}
+
+void TerminalSettingsPage::init() {}
+
+static expected_str<void> loadXdefaults(const FilePath &path)
+{
+ const expected_str<QByteArray> readResult = path.fileContents();
+ if (!readResult)
+ return make_unexpected(readResult.error());
+
+ QRegularExpression re(R"(.*\*(color[0-9]{1,2}|foreground|background):\s*(#[0-9a-f]{6}))");
+
+ for (const QByteArray &line : readResult->split('\n')) {
+ if (line.trimmed().startsWith('!'))
+ continue;
+
+ const auto match = re.match(QString::fromUtf8(line));
+ if (match.hasMatch()) {
+ const QString colorName = match.captured(1);
+ const QColor color(match.captured(2));
+ if (colorName == "foreground") {
+ TerminalSettings::instance().foregroundColor.setVolatileValue(color);
+ } else if (colorName == "background") {
+ TerminalSettings::instance().backgroundColor.setVolatileValue(color);
+ } else {
+ const int colorIndex = colorName.mid(5).toInt();
+ if (colorIndex >= 0 && colorIndex < 16)
+ TerminalSettings::instance().colors[colorIndex].setVolatileValue(color);
+ }
+ }
+ }
+
+ return {};
+}
+
+static expected_str<void> loadItermColors(const FilePath &path)
+{
+ QFile f(path.toFSPathString());
+ const bool opened = f.open(QIODevice::ReadOnly);
+ if (!opened)
+ return make_unexpected(Tr::tr("Failed to open file"));
+
+ QXmlStreamReader reader(&f);
+ while (!reader.atEnd() && reader.readNextStartElement()) {
+ if (reader.name() == u"plist") {
+ while (!reader.atEnd() && reader.readNextStartElement()) {
+ if (reader.name() == u"dict") {
+ QString colorName;
+ while (!reader.atEnd() && reader.readNextStartElement()) {
+ if (reader.name() == u"key") {
+ colorName = reader.readElementText();
+ } else if (reader.name() == u"dict") {
+ QColor color;
+ int component = 0;
+ while (!reader.atEnd() && reader.readNextStartElement()) {
+ if (reader.name() == u"key") {
+ const auto &text = reader.readElementText();
+ if (text == u"Red Component")
+ component = 0;
+ else if (text == u"Green Component")
+ component = 1;
+ else if (text == u"Blue Component")
+ component = 2;
+ else if (text == u"Alpha Component")
+ component = 3;
+ } else if (reader.name() == u"real") {
+ // clang-format off
+ switch (component) {
+ case 0: color.setRedF(reader.readElementText().toDouble()); break;
+ case 1: color.setGreenF(reader.readElementText().toDouble()); break;
+ case 2: color.setBlueF(reader.readElementText().toDouble()); break;
+ case 3: color.setAlphaF(reader.readElementText().toDouble()); break;
+ }
+ // clang-format on
+ } else {
+ reader.skipCurrentElement();
+ }
+ }
+
+ if (colorName.startsWith("Ansi")) {
+ const auto c = colorName.mid(5, 2);
+ const int colorIndex = c.toInt();
+ if (colorIndex >= 0 && colorIndex < 16)
+ TerminalSettings::instance().colors[colorIndex].setVolatileValue(
+ color);
+ } else if (colorName == "Foreground Color") {
+ TerminalSettings::instance().foregroundColor.setVolatileValue(color);
+ } else if (colorName == "Background Color") {
+ TerminalSettings::instance().backgroundColor.setVolatileValue(color);
+ } else if (colorName == "Selection Color") {
+ TerminalSettings::instance().selectionColor.setVolatileValue(color);
+ }
+ }
+ }
+ }
+ }
+ break;
+ }
+ }
+ if (reader.hasError())
+ return make_unexpected(reader.errorString());
+
+ return {};
+}
+
+static expected_str<void> loadVsCodeColors(const FilePath &path)
+{
+ const expected_str<QByteArray> readResult = path.fileContents();
+ if (!readResult)
+ return make_unexpected(readResult.error());
+
+ QJsonParseError error;
+ QJsonDocument doc = QJsonDocument::fromJson(*readResult, &error);
+ if (error.error != QJsonParseError::NoError)
+ return make_unexpected(Tr::tr("JSON parsing error: \"%1\", at offset: %2")
+ .arg(error.errorString())
+ .arg(error.offset));
+
+ const QJsonObject root = doc.object();
+ const auto itColors = root.find("colors");
+ if (itColors == root.end())
+ return make_unexpected(Tr::tr("No colors found"));
+
+ const QJsonObject colors = itColors->toObject();
+
+ // clang-format off
+ const QList<QPair<QStringView, ColorAspect *>> colorKeys = {
+ qMakePair(u"editor.background", &TerminalSettings::instance().backgroundColor),
+ qMakePair(u"terminal.foreground", &TerminalSettings::instance().foregroundColor),
+ qMakePair(u"terminal.selectionBackground", &TerminalSettings::instance().selectionColor),
+
+ qMakePair(u"terminal.ansiBlack", &TerminalSettings::instance().colors[0]),
+ qMakePair(u"terminal.ansiBrightBlack", &TerminalSettings::instance().colors[8]),
+
+ qMakePair(u"terminal.ansiRed", &TerminalSettings::instance().colors[1]),
+ qMakePair(u"terminal.ansiBrightRed", &TerminalSettings::instance().colors[9]),
+
+ qMakePair(u"terminal.ansiGreen", &TerminalSettings::instance().colors[2]),
+ qMakePair(u"terminal.ansiBrightGreen", &TerminalSettings::instance().colors[10]),
+
+ qMakePair(u"terminal.ansiYellow", &TerminalSettings::instance().colors[3]),
+ qMakePair(u"terminal.ansiBrightYellow", &TerminalSettings::instance().colors[11]),
+
+ qMakePair(u"terminal.ansiBlue", &TerminalSettings::instance().colors[4]),
+ qMakePair(u"terminal.ansiBrightBlue", &TerminalSettings::instance().colors[12]),
+
+ qMakePair(u"terminal.ansiMagenta", &TerminalSettings::instance().colors[5]),
+ qMakePair(u"terminal.ansiBrightMagenta", &TerminalSettings::instance().colors[13]),
+
+ qMakePair(u"terminal.ansiCyan", &TerminalSettings::instance().colors[6]),
+ qMakePair(u"terminal.ansiBrightCyan", &TerminalSettings::instance().colors[14]),
+
+ qMakePair(u"terminal.ansiWhite", &TerminalSettings::instance().colors[7]),
+ qMakePair(u"terminal.ansiBrightWhite", &TerminalSettings::instance().colors[15])
+ };
+ // clang-format on
+
+ for (const auto &pair : colorKeys) {
+ const auto it = colors.find(pair.first);
+ if (it != colors.end()) {
+ const QString colorString = it->toString();
+ if (colorString.startsWith("#")) {
+ QColor color(colorString.mid(0, 7));
+ if (colorString.size() > 7) {
+ int alpha = colorString.mid(7).toInt(nullptr, 16);
+ color.setAlpha(alpha);
+ }
+ if (color.isValid())
+ pair.second->setVolatileValue(color);
+ }
+ }
+ }
+
+ return {};
+}
+
+static expected_str<void> loadKonsoleColorScheme(const FilePath &path)
+{
+ QSettings settings(path.toFSPathString(), QSettings::IniFormat);
+
+ auto parseColor = [](const QStringList &parts) -> expected_str<QColor> {
+ if (parts.size() != 3 && parts.size() != 4)
+ return make_unexpected(Tr::tr("Invalid color format"));
+ int alpha = parts.size() == 4 ? parts[3].toInt() : 255;
+ return QColor(parts[0].toInt(), parts[1].toInt(), parts[2].toInt(), alpha);
+ };
+
+ // clang-format off
+ const QList<QPair<QString, ColorAspect *>> colorKeys = {
+ qMakePair(QLatin1String("Background/Color"), &TerminalSettings::instance().backgroundColor),
+ qMakePair(QLatin1String("Foreground/Color"), &TerminalSettings::instance().foregroundColor),
+
+ qMakePair(QLatin1String("Color0/Color"), &TerminalSettings::instance().colors[0]),
+ qMakePair(QLatin1String("Color0Intense/Color"), &TerminalSettings::instance().colors[8]),
+
+ qMakePair(QLatin1String("Color1/Color"), &TerminalSettings::instance().colors[1]),
+ qMakePair(QLatin1String("Color1Intense/Color"), &TerminalSettings::instance().colors[9]),
+
+ qMakePair(QLatin1String("Color2/Color"), &TerminalSettings::instance().colors[2]),
+ qMakePair(QLatin1String("Color2Intense/Color"), &TerminalSettings::instance().colors[10]),
+
+ qMakePair(QLatin1String("Color3/Color"), &TerminalSettings::instance().colors[3]),
+ qMakePair(QLatin1String("Color3Intense/Color"), &TerminalSettings::instance().colors[11]),
+
+ qMakePair(QLatin1String("Color4/Color"), &TerminalSettings::instance().colors[4]),
+ qMakePair(QLatin1String("Color4Intense/Color"), &TerminalSettings::instance().colors[12]),
+
+ qMakePair(QLatin1String("Color5/Color"), &TerminalSettings::instance().colors[5]),
+ qMakePair(QLatin1String("Color5Intense/Color"), &TerminalSettings::instance().colors[13]),
+
+ qMakePair(QLatin1String("Color6/Color"), &TerminalSettings::instance().colors[6]),
+ qMakePair(QLatin1String("Color6Intense/Color"), &TerminalSettings::instance().colors[14]),
+
+ qMakePair(QLatin1String("Color7/Color"), &TerminalSettings::instance().colors[7]),
+ qMakePair(QLatin1String("Color7Intense/Color"), &TerminalSettings::instance().colors[15])
+ };
+ // clang-format on
+
+ for (const auto &colorKey : colorKeys) {
+ if (settings.contains(colorKey.first)) {
+ const auto color = parseColor(settings.value(colorKey.first).toStringList());
+ if (!color)
+ return make_unexpected(color.error());
+
+ colorKey.second->setVolatileValue(*color);
+ }
+ }
+
+ return {};
+}
+
+static expected_str<void> loadXFCE4ColorScheme(const FilePath &path)
+{
+ expected_str<QByteArray> arr = path.fileContents();
+ if (!arr)
+ return make_unexpected(arr.error());
+
+ arr->replace(';', ',');
+
+ QTemporaryFile f;
+ f.open();
+ f.write(*arr);
+ f.close();
+
+ QSettings settings(f.fileName(), QSettings::IniFormat);
+
+ // clang-format off
+ const QList<QPair<QString, ColorAspect *>> colorKeys = {
+ qMakePair(QLatin1String("Scheme/ColorBackground"), &TerminalSettings::instance().backgroundColor),
+ qMakePair(QLatin1String("Scheme/ColorForeground"), &TerminalSettings::instance().foregroundColor),
+ };
+ // clang-format on
+
+ for (const auto &colorKey : colorKeys) {
+ if (settings.contains(colorKey.first)) {
+ colorKey.second->setVolatileValue(QColor(settings.value(colorKey.first).toString()));
+ }
+ }
+
+ QStringList colors = settings.value(QLatin1String("Scheme/ColorPalette")).toStringList();
+ int i = 0;
+ for (const auto &color : colors) {
+ TerminalSettings::instance().colors[i++].setVolatileValue(QColor(color));
+ }
+
+ return {};
+}
+
+static expected_str<void> loadColorScheme(const FilePath &path)
+{
+ if (path.endsWith("Xdefaults"))
+ return loadXdefaults(path);
+ else if (path.suffix() == "itermcolors")
+ return loadItermColors(path);
+ else if (path.suffix() == "json")
+ return loadVsCodeColors(path);
+ else if (path.suffix() == "colorscheme")
+ return loadKonsoleColorScheme(path);
+ else if (path.suffix() == "theme" || path.completeSuffix() == "theme.txt")
+ return loadXFCE4ColorScheme(path);
+
+ return make_unexpected(Tr::tr("Unknown color scheme format"));
+}
+
+QWidget *TerminalSettingsPage::widget()
+{
+ QWidget *widget = new QWidget;
+
+ using namespace Layouting;
+
+ QFontComboBox *fontComboBox = new QFontComboBox(widget);
+ fontComboBox->setFontFilters(QFontComboBox::MonospacedFonts);
+ fontComboBox->setCurrentFont(TerminalSettings::instance().font.value());
+
+ connect(fontComboBox, &QFontComboBox::currentFontChanged, this, [](const QFont &f) {
+ TerminalSettings::instance().font.setValue(f.family());
+ });
+
+ TerminalSettings &settings = TerminalSettings::instance();
+
+ QPushButton *loadThemeButton = new QPushButton(Tr::tr("Load Theme..."));
+ QPushButton *resetTheme = new QPushButton(Tr::tr("Reset Theme to default"));
+
+ connect(loadThemeButton, &QPushButton::clicked, this, [widget] {
+ const FilePath path = FileUtils::getOpenFilePath(
+ widget,
+ "Open Theme",
+ {},
+ "All Scheme formats (*.itermcolors *.json *.colorscheme *.theme *.theme.txt);;"
+ "Xdefaults (.Xdefaults Xdefaults);;"
+ "iTerm Color Schemes(*.itermcolors);;"
+ "VS Code Color Schemes(*.json);;"
+ "Konsole Color Schemes(*.colorscheme);;"
+ "XFCE4 Terminal Color Schemes(*.theme *.theme.txt);;"
+ "All files (*)",
+ nullptr,
+ {},
+ true,
+ false);
+
+ if (path.isEmpty())
+ return;
+
+ const expected_str<void> result = loadColorScheme(path);
+ if (!result)
+ QMessageBox::warning(widget, Tr::tr("Error"), result.error());
+ });
+
+ connect(resetTheme, &QPushButton::clicked, this, [] {
+ TerminalSettings &settings = TerminalSettings::instance();
+ settings.foregroundColor.setVolatileValue(settings.foregroundColor.defaultValue());
+ settings.backgroundColor.setVolatileValue(settings.backgroundColor.defaultValue());
+ settings.selectionColor.setVolatileValue(settings.selectionColor.defaultValue());
+
+ for (auto &color : settings.colors)
+ color.setVolatileValue(color.defaultValue());
+ });
+
+ // clang-format off
+ Column {
+ Group {
+ title(Tr::tr("General")),
+ Column {
+ settings.enableTerminal, st,
+ settings.sendEscapeToTerminal, st,
+ settings.audibleBell, st,
+ },
+ },
+ Group {
+ title(Tr::tr("Font")),
+ Row {
+ settings.font.labelText(), fontComboBox, Space(20),
+ settings.fontSize, st,
+ },
+ },
+ Group {
+ title(Tr::tr("Cursor")),
+ Row {
+ settings.allowBlinkingCursor, st,
+ },
+ },
+ Group {
+ title(Tr::tr("Colors")),
+ Column {
+ Row {
+ Tr::tr("Foreground"), settings.foregroundColor, st,
+ Tr::tr("Background"), settings.backgroundColor, st,
+ Tr::tr("Selection"), settings.selectionColor, st,
+ Tr::tr("Find match"), settings.findMatchColor, st,
+ },
+ Row {
+ settings.colors[0], settings.colors[1],
+ settings.colors[2], settings.colors[3],
+ settings.colors[4], settings.colors[5],
+ settings.colors[6], settings.colors[7]
+ },
+ Row {
+ settings.colors[8], settings.colors[9],
+ settings.colors[10], settings.colors[11],
+ settings.colors[12], settings.colors[13],
+ settings.colors[14], settings.colors[15]
+ },
+ Row {
+ loadThemeButton, resetTheme, st,
+ }
+ },
+ },
+ Column {
+ settings.shell,
+ settings.shellArguments,
+ },
+ st,
+ }.attachTo(widget);
+ // clang-format on
+
+ DropSupport *dropSupport = new DropSupport(widget);
+ connect(dropSupport,
+ &DropSupport::filesDropped,
+ this,
+ [widget](const QList<DropSupport::FileSpec> &files) {
+ if (files.size() != 1)
+ return;
+
+ const expected_str<void> result = loadColorScheme(files.at(0).filePath);
+ if (!result)
+ QMessageBox::warning(widget, Tr::tr("Error"), result.error());
+ });
+
+ return widget;
+}
+
+TerminalSettingsPage &TerminalSettingsPage::instance()
+{
+ static TerminalSettingsPage settingsPage;
+ return settingsPage;
+}
+
+} // namespace Terminal
diff --git a/src/plugins/terminal/terminalsettingspage.h b/src/plugins/terminal/terminalsettingspage.h
new file mode 100644
index 00000000000..4c12697b13f
--- /dev/null
+++ b/src/plugins/terminal/terminalsettingspage.h
@@ -0,0 +1,22 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <coreplugin/dialogs/ioptionspage.h>
+
+namespace Terminal {
+
+class TerminalSettingsPage : public Core::IOptionsPage
+{
+public:
+ TerminalSettingsPage();
+
+ static TerminalSettingsPage &instance();
+
+ void init();
+
+ QWidget *widget() override;
+};
+
+} // namespace Terminal
diff --git a/src/plugins/terminal/terminalsurface.cpp b/src/plugins/terminal/terminalsurface.cpp
new file mode 100644
index 00000000000..01b1f2c2aae
--- /dev/null
+++ b/src/plugins/terminal/terminalsurface.cpp
@@ -0,0 +1,531 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "terminalsurface.h"
+
+#include "keys.h"
+#include "scrollback.h"
+
+#include <utils/qtcassert.h>
+
+#include <vterm.h>
+
+#include <QLoggingCategory>
+
+Q_LOGGING_CATEGORY(log, "qtc.terminal.surface", QtWarningMsg);
+
+namespace Terminal::Internal {
+
+QColor toQColor(const VTermColor &c)
+{
+ return QColor(qRgb(c.rgb.red, c.rgb.green, c.rgb.blue));
+};
+
+struct TerminalSurfacePrivate
+{
+ TerminalSurfacePrivate(TerminalSurface *surface,
+ const QSize &initialGridSize,
+ ShellIntegration *shellIntegration)
+ : m_vterm(vterm_new(initialGridSize.height(), initialGridSize.width()), vterm_free)
+ , m_vtermScreen(vterm_obtain_screen(m_vterm.get()))
+ , m_scrollback(std::make_unique<Internal::Scrollback>(5000))
+ , m_shellIntegration(shellIntegration)
+ , q(surface)
+ {}
+
+ void init()
+ {
+ vterm_set_utf8(m_vterm.get(), true);
+
+ static auto writeToPty = [](const char *s, size_t len, void *user) {
+ auto p = static_cast<TerminalSurfacePrivate *>(user);
+ emit p->q->writeToPty(QByteArray(s, static_cast<int>(len)));
+ };
+
+ vterm_output_set_callback(m_vterm.get(), writeToPty, this);
+
+ memset(&m_vtermScreenCallbacks, 0, sizeof(m_vtermScreenCallbacks));
+
+ m_vtermScreenCallbacks.damage = [](VTermRect rect, void *user) {
+ auto p = static_cast<TerminalSurfacePrivate *>(user);
+ p->invalidate(rect);
+ return 1;
+ };
+ m_vtermScreenCallbacks.sb_pushline = [](int cols, const VTermScreenCell *cells, void *user) {
+ auto p = static_cast<TerminalSurfacePrivate *>(user);
+ return p->sb_pushline(cols, cells);
+ };
+ m_vtermScreenCallbacks.sb_popline = [](int cols, VTermScreenCell *cells, void *user) {
+ auto p = static_cast<TerminalSurfacePrivate *>(user);
+ return p->sb_popline(cols, cells);
+ };
+ m_vtermScreenCallbacks.settermprop = [](VTermProp prop, VTermValue *val, void *user) {
+ auto p = static_cast<TerminalSurfacePrivate *>(user);
+ return p->setTerminalProperties(prop, val);
+ };
+ m_vtermScreenCallbacks.movecursor =
+ [](VTermPos pos, VTermPos oldpos, int visible, void *user) {
+ auto p = static_cast<TerminalSurfacePrivate *>(user);
+ return p->movecursor(pos, oldpos, visible);
+ };
+ m_vtermScreenCallbacks.sb_clear = [](void *user) {
+ auto p = static_cast<TerminalSurfacePrivate *>(user);
+ return p->sb_clear();
+ };
+ m_vtermScreenCallbacks.bell = [](void *user) {
+ auto p = static_cast<TerminalSurfacePrivate *>(user);
+ emit p->q->bell();
+ return 1;
+ };
+
+ vterm_screen_set_callbacks(m_vtermScreen, &m_vtermScreenCallbacks, this);
+ vterm_screen_set_damage_merge(m_vtermScreen, VTERM_DAMAGE_SCROLL);
+ vterm_screen_enable_altscreen(m_vtermScreen, true);
+
+ memset(&m_vtermStateFallbacks, 0, sizeof(m_vtermStateFallbacks));
+
+ m_vtermStateFallbacks.osc = [](int cmd, VTermStringFragment fragment, void *user) {
+ auto p = static_cast<TerminalSurfacePrivate *>(user);
+ return p->osc(cmd, fragment);
+ };
+
+ VTermState *vts = vterm_obtain_state(m_vterm.get());
+ vterm_state_set_unrecognised_fallbacks(vts, &m_vtermStateFallbacks, this);
+ vterm_state_set_bold_highbright(vts, true);
+
+ VTermColor fg;
+ VTermColor bg;
+ vterm_color_indexed(&fg, ColorIndex::Foreground);
+ vterm_color_indexed(&bg, ColorIndex::Background);
+ vterm_state_set_default_colors(vts, &fg, &bg);
+
+ for (int i = 0; i < 16; ++i) {
+ VTermColor col;
+ vterm_color_indexed(&col, i);
+ vterm_state_set_palette_color(vts, i, &col);
+ }
+
+ vterm_screen_reset(m_vtermScreen, 1);
+ }
+
+ QSize liveSize() const
+ {
+ int rows;
+ int cols;
+ vterm_get_size(m_vterm.get(), &rows, &cols);
+
+ return QSize(cols, rows);
+ }
+
+ std::variant<int, QColor> toVariantColor(const VTermColor &color)
+ {
+ if (color.type & VTERM_COLOR_DEFAULT_BG)
+ return ColorIndex::Background;
+ else if (color.type & VTERM_COLOR_DEFAULT_FG)
+ return ColorIndex::Foreground;
+ else if (color.type & VTERM_COLOR_INDEXED) {
+ if (color.indexed.idx >= 16) {
+ VTermColor c = color;
+ vterm_state_convert_color_to_rgb(vterm_obtain_state(m_vterm.get()), &c);
+ return toQColor(c);
+ }
+ return color.indexed.idx;
+ } else if (color.type == VTERM_COLOR_RGB)
+ return toQColor(color);
+ else
+ return -1;
+ }
+
+ TerminalCell toCell(const VTermScreenCell &cell)
+ {
+ TerminalCell result;
+ result.width = cell.width;
+ result.text = QString::fromUcs4(cell.chars);
+
+ const VTermColor *bg = &cell.bg;
+ const VTermColor *fg = &cell.fg;
+
+ if (static_cast<bool>(cell.attrs.reverse))
+ std::swap(fg, bg);
+
+ result.backgroundColor = toVariantColor(*bg);
+ result.foregroundColor = toVariantColor(*fg);
+
+ result.bold = cell.attrs.bold;
+ result.strikeOut = cell.attrs.strike;
+
+ if (cell.attrs.underline > 0) {
+ result.underlineStyle = QTextCharFormat::NoUnderline;
+ switch (cell.attrs.underline) {
+ case VTERM_UNDERLINE_SINGLE:
+ result.underlineStyle = QTextCharFormat::SingleUnderline;
+ break;
+ case VTERM_UNDERLINE_DOUBLE:
+ // TODO: Double underline
+ result.underlineStyle = QTextCharFormat::SingleUnderline;
+ break;
+ case VTERM_UNDERLINE_CURLY:
+ result.underlineStyle = QTextCharFormat::WaveUnderline;
+ break;
+ case VTERM_UNDERLINE_DASHED:
+ result.underlineStyle = QTextCharFormat::DashUnderline;
+ break;
+ case VTERM_UNDERLINE_DOTTED:
+ result.underlineStyle = QTextCharFormat::DotLine;
+ break;
+ }
+ }
+
+ result.strikeOut = cell.attrs.strike;
+
+ return result;
+ }
+
+ // Callbacks from vterm
+ void invalidate(VTermRect rect)
+ {
+ if (!m_altscreen) {
+ rect.start_row += m_scrollback->size();
+ rect.end_row += m_scrollback->size();
+ }
+
+ emit q->invalidated(
+ QRect{QPoint{rect.start_col, rect.start_row}, QPoint{rect.end_col, rect.end_row - 1}});
+ }
+
+ int sb_pushline(int cols, const VTermScreenCell *cells)
+ {
+ m_scrollback->emplace(cols, cells);
+ emit q->fullSizeChanged(q->fullSize());
+ return 1;
+ }
+
+ int sb_popline(int cols, VTermScreenCell *cells)
+ {
+ if (m_scrollback->size() == 0)
+ return 0;
+
+ m_scrollback->popto(cols, cells);
+ emit q->fullSizeChanged(q->fullSize());
+ return 1;
+ }
+
+ int sb_clear()
+ {
+ m_scrollback->clear();
+ emit q->fullSizeChanged(q->fullSize());
+ return 1;
+ }
+
+ int osc(int cmd, const VTermStringFragment &fragment)
+ {
+ if (m_shellIntegration)
+ m_shellIntegration->onOsc(cmd, fragment);
+
+ return 1;
+ }
+
+ int setTerminalProperties(VTermProp prop, VTermValue *val)
+ {
+ switch (prop) {
+ case VTERM_PROP_CURSORVISIBLE: {
+ Cursor old = q->cursor();
+ m_cursor.visible = val->boolean;
+ q->cursorChanged(old, q->cursor());
+ break;
+ }
+ case VTERM_PROP_CURSORBLINK: {
+ Cursor old = q->cursor();
+ m_cursor.blink = val->boolean;
+ emit q->cursorChanged(old, q->cursor());
+ break;
+ }
+ case VTERM_PROP_CURSORSHAPE: {
+ Cursor old = q->cursor();
+ m_cursor.shape = (Cursor::Shape) val->number;
+ emit q->cursorChanged(old, q->cursor());
+ break;
+ }
+ case VTERM_PROP_ICONNAME:
+ break;
+ case VTERM_PROP_TITLE:
+ break;
+ case VTERM_PROP_ALTSCREEN:
+ m_altscreen = val->boolean;
+ emit q->altscreenChanged(m_altscreen);
+ break;
+ case VTERM_PROP_MOUSE:
+ qCDebug(log) << "Ignoring VTERM_PROP_MOUSE" << val->number;
+ break;
+ case VTERM_PROP_REVERSE:
+ qCDebug(log) << "Ignoring VTERM_PROP_REVERSE" << val->boolean;
+ break;
+ case VTERM_N_PROPS:
+ break;
+ }
+ return 1;
+ }
+ int movecursor(VTermPos pos, VTermPos oldpos, int visible)
+ {
+ Q_UNUSED(oldpos);
+ Cursor oldCursor = q->cursor();
+ m_cursor.position = {pos.col, pos.row};
+ m_cursor.visible = visible > 0;
+ q->cursorChanged(oldCursor, q->cursor());
+ return 1;
+ }
+
+ const VTermScreenCell *cellAt(int x, int y)
+ {
+ QTC_ASSERT(y >= 0 && x >= 0, return nullptr);
+ QTC_ASSERT(y < q->fullSize().height() && x < liveSize().width(), return nullptr);
+
+ if (!m_altscreen && y < m_scrollback->size()) {
+ const auto &sbl = m_scrollback->line((m_scrollback->size() - 1) - y);
+ if (x < sbl.cols()) {
+ return sbl.cell(x);
+ }
+ return nullptr;
+ }
+
+ if (!m_altscreen)
+ y -= m_scrollback->size();
+
+ static VTermScreenCell refCell{};
+ VTermPos vtp{y, x};
+ vterm_screen_get_cell(m_vtermScreen, vtp, &refCell);
+
+ return &refCell;
+ }
+
+ std::unique_ptr<VTerm, void (*)(VTerm *)> m_vterm;
+ VTermScreen *m_vtermScreen;
+ VTermScreenCallbacks m_vtermScreenCallbacks;
+ VTermStateFallbacks m_vtermStateFallbacks;
+
+ Cursor m_cursor;
+ QString m_currentCommand;
+
+ bool m_altscreen{false};
+
+ std::unique_ptr<Internal::Scrollback> m_scrollback;
+
+ ShellIntegration *m_shellIntegration{nullptr};
+
+ TerminalSurface *q;
+};
+
+TerminalSurface::TerminalSurface(QSize initialGridSize, ShellIntegration *shellIntegration)
+ : d(std::make_unique<TerminalSurfacePrivate>(this, initialGridSize, shellIntegration))
+{
+ d->init();
+}
+
+TerminalSurface::~TerminalSurface() = default;
+
+int TerminalSurface::cellWidthAt(int x, int y) const
+{
+ const VTermScreenCell *cell = d->cellAt(x, y);
+ if (!cell)
+ return 0;
+ return cell->width;
+}
+
+QSize TerminalSurface::liveSize() const
+{
+ return d->liveSize();
+}
+
+QSize TerminalSurface::fullSize() const
+{
+ if (d->m_altscreen)
+ return liveSize();
+ return QSize{d->liveSize().width(), d->liveSize().height() + d->m_scrollback->size()};
+}
+
+std::u32string::value_type TerminalSurface::fetchCharAt(int x, int y) const
+{
+ const VTermScreenCell *cell = d->cellAt(x, y);
+ if (!cell)
+ return 0;
+
+ if (cell->width == 0)
+ return 0;
+
+ QString s = QString::fromUcs4(cell->chars, 6).normalized(QString::NormalizationForm_C);
+ const QList<uint> ucs4 = s.toUcs4();
+ return std::u32string(ucs4.begin(), ucs4.end()).front();
+}
+
+TerminalCell TerminalSurface::fetchCell(int x, int y) const
+{
+ static TerminalCell emptyCell{1,
+ {},
+ {},
+ false,
+ ColorIndex::Foreground,
+ ColorIndex::Background,
+ QTextCharFormat::NoUnderline,
+ false};
+
+ QTC_ASSERT(y >= 0, return emptyCell);
+ QTC_ASSERT(y < fullSize().height() && x < fullSize().width(), return emptyCell);
+
+ const VTermScreenCell *refCell = d->cellAt(x, y);
+ if (!refCell)
+ return emptyCell;
+
+ return d->toCell(*refCell);
+}
+
+void TerminalSurface::clearAll()
+{
+ // Fake a scrollback clearing
+ QByteArray data{"\x1b[3J"};
+ vterm_input_write(d->m_vterm.get(), data.constData(), data.size());
+
+ // Send Ctrl+L which will clear the screen
+ emit writeToPty(QByteArray("\f"));
+}
+
+void TerminalSurface::resize(QSize newSize)
+{
+ vterm_set_size(d->m_vterm.get(), newSize.height(), newSize.width());
+}
+
+QPoint TerminalSurface::posToGrid(int pos) const
+{
+ return {pos % d->liveSize().width(), pos / d->liveSize().width()};
+}
+int TerminalSurface::gridToPos(QPoint gridPos) const
+{
+ return gridPos.y() * d->liveSize().width() + gridPos.x();
+}
+
+void TerminalSurface::dataFromPty(const QByteArray &data)
+{
+ vterm_input_write(d->m_vterm.get(), data.constData(), data.size());
+ vterm_screen_flush_damage(d->m_vtermScreen);
+}
+
+void TerminalSurface::flush()
+{
+ vterm_screen_flush_damage(d->m_vtermScreen);
+}
+
+void TerminalSurface::pasteFromClipboard(const QString &clipboardText)
+{
+ if (clipboardText.isEmpty())
+ return;
+
+ vterm_keyboard_start_paste(d->m_vterm.get());
+ for (unsigned int ch : clipboardText.toUcs4())
+ vterm_keyboard_unichar(d->m_vterm.get(), ch, VTERM_MOD_NONE);
+ vterm_keyboard_end_paste(d->m_vterm.get());
+
+ if (!d->m_altscreen) {
+ emit unscroll();
+ }
+}
+
+void TerminalSurface::sendKey(Qt::Key key)
+{
+ if (key == Qt::Key_Escape)
+ vterm_keyboard_key(d->m_vterm.get(), VTERM_KEY_ESCAPE, VTERM_MOD_NONE);
+}
+
+void TerminalSurface::sendKey(const QString &text)
+{
+ for (const unsigned int ch : text.toUcs4())
+ vterm_keyboard_unichar(d->m_vterm.get(), ch, VTERM_MOD_NONE);
+}
+
+void TerminalSurface::sendKey(QKeyEvent *event)
+{
+ bool keypad = event->modifiers() & Qt::KeypadModifier;
+ VTermModifier mod = Internal::qtModifierToVTerm(event->modifiers());
+ VTermKey key = Internal::qtKeyToVTerm(Qt::Key(event->key()), keypad);
+
+ if (key != VTERM_KEY_NONE) {
+ if (mod == VTERM_MOD_SHIFT && (key == VTERM_KEY_ESCAPE || key == VTERM_KEY_BACKSPACE))
+ mod = VTERM_MOD_NONE;
+
+ vterm_keyboard_key(d->m_vterm.get(), key, mod);
+ } else if (event->text().length() == 1) {
+ // This maps to delete word and is way to easy to mistakenly type
+ // if (event->key() == Qt::Key_Space && mod == VTERM_MOD_SHIFT)
+ // mod = VTERM_MOD_NONE;
+
+ // Per https://github.com/justinmk/neovim/commit/317d5ca7b0f92ef42de989b3556ca9503f0a3bf6
+ // libvterm prefers we send the full keycode rather than sending the
+ // ctrl modifier. This helps with ncurses applications which otherwise
+ // do not recognize ctrl+<key> and in the shell for getting common control characters
+ // like ctrl+i for tab or ctrl+j for newline.
+
+ // Workaround for "ALT+SHIFT+/" (\ on german mac keyboards)
+ if (mod == (VTERM_MOD_SHIFT | VTERM_MOD_ALT) && event->key() == Qt::Key_Slash) {
+ mod = VTERM_MOD_NONE;
+ }
+
+ vterm_keyboard_unichar(d->m_vterm.get(), event->text().toUcs4()[0], VTERM_MOD_NONE);
+ } else if (mod == VTERM_MOD_CTRL && event->key() >= Qt::Key_A && event->key() < Qt::Key_Z) {
+ vterm_keyboard_unichar(d->m_vterm.get(), 'a' + (event->key() - Qt::Key_A), mod);
+ }
+}
+
+Cursor TerminalSurface::cursor() const
+{
+ Cursor cursor = d->m_cursor;
+ if (!d->m_altscreen)
+ cursor.position.setY(cursor.position.y() + d->m_scrollback->size());
+
+ return cursor;
+}
+
+ShellIntegration *TerminalSurface::shellIntegration() const
+{
+ return d->m_shellIntegration;
+}
+
+CellIterator TerminalSurface::begin() const
+{
+ auto res = CellIterator(this, {0, 0});
+ res.m_state = CellIterator::State::BEGIN;
+ return res;
+}
+
+CellIterator TerminalSurface::end() const
+{
+ return CellIterator(this, CellIterator::State::END);
+}
+
+std::reverse_iterator<CellIterator> TerminalSurface::rbegin() const
+{
+ return std::make_reverse_iterator(end());
+}
+
+std::reverse_iterator<CellIterator> TerminalSurface::rend() const
+{
+ return std::make_reverse_iterator(begin());
+}
+
+CellIterator TerminalSurface::iteratorAt(QPoint pos) const
+{
+ return CellIterator(this, pos);
+}
+CellIterator TerminalSurface::iteratorAt(int pos) const
+{
+ return CellIterator(this, pos);
+}
+
+std::reverse_iterator<CellIterator> TerminalSurface::rIteratorAt(QPoint pos) const
+{
+ return std::make_reverse_iterator(iteratorAt(pos));
+}
+
+std::reverse_iterator<CellIterator> TerminalSurface::rIteratorAt(int pos) const
+{
+ return std::make_reverse_iterator(iteratorAt(pos));
+}
+
+} // namespace Terminal::Internal
diff --git a/src/plugins/terminal/terminalsurface.h b/src/plugins/terminal/terminalsurface.h
new file mode 100644
index 00000000000..a6fc7425d48
--- /dev/null
+++ b/src/plugins/terminal/terminalsurface.h
@@ -0,0 +1,111 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "celliterator.h"
+#include "shellintegration.h"
+
+#include <QKeyEvent>
+#include <QSize>
+#include <QTextCharFormat>
+
+#include <memory>
+
+namespace Terminal::Internal {
+
+class Scrollback;
+
+struct TerminalSurfacePrivate;
+
+enum ColorIndex { Foreground = 16, Background = 17 };
+
+struct TerminalCell
+{
+ int width;
+ QString text;
+ bool bold{false};
+ bool italic{false};
+ std::variant<int, QColor> foregroundColor;
+ std::variant<int, QColor> backgroundColor;
+ QTextCharFormat::UnderlineStyle underlineStyle{QTextCharFormat::NoUnderline};
+ bool strikeOut{false};
+};
+
+struct Cursor
+{
+ enum class Shape {
+ Block = 1,
+ Underline,
+ LeftBar,
+ };
+ QPoint position;
+ bool visible;
+ Shape shape;
+ bool blink{false};
+};
+
+class TerminalSurface : public QObject
+{
+ Q_OBJECT;
+
+public:
+ TerminalSurface(QSize initialGridSize, ShellIntegration *shellIntegration);
+ ~TerminalSurface();
+
+public:
+ CellIterator begin() const;
+ CellIterator end() const;
+ std::reverse_iterator<CellIterator> rbegin() const;
+ std::reverse_iterator<CellIterator> rend() const;
+
+ CellIterator iteratorAt(QPoint pos) const;
+ CellIterator iteratorAt(int pos) const;
+
+ std::reverse_iterator<CellIterator> rIteratorAt(QPoint pos) const;
+ std::reverse_iterator<CellIterator> rIteratorAt(int pos) const;
+
+public:
+ void clearAll();
+
+ void resize(QSize newSize);
+
+ TerminalCell fetchCell(int x, int y) const;
+ std::u32string::value_type fetchCharAt(int x, int y) const;
+ int cellWidthAt(int x, int y) const;
+
+ QSize liveSize() const;
+ QSize fullSize() const;
+
+ QPoint posToGrid(int pos) const;
+ int gridToPos(QPoint gridPos) const;
+
+ void dataFromPty(const QByteArray &data);
+ void flush();
+
+ void pasteFromClipboard(const QString &text);
+
+ void sendKey(Qt::Key key);
+ void sendKey(QKeyEvent *event);
+ void sendKey(const QString &text);
+
+ int invertedScrollOffset() const;
+
+ Cursor cursor() const;
+
+ ShellIntegration *shellIntegration() const;
+
+signals:
+ void writeToPty(const QByteArray &data);
+ void invalidated(QRect grid);
+ void fullSizeChanged(QSize newSize);
+ void cursorChanged(Cursor oldCursor, Cursor newCursor);
+ void altscreenChanged(bool altScreen);
+ void unscroll();
+ void bell();
+
+private:
+ std::unique_ptr<TerminalSurfacePrivate> d;
+};
+
+} // namespace Terminal::Internal
diff --git a/src/plugins/terminal/terminaltr.h b/src/plugins/terminal/terminaltr.h
new file mode 100644
index 00000000000..b645284833c
--- /dev/null
+++ b/src/plugins/terminal/terminaltr.h
@@ -0,0 +1,15 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <QCoreApplication>
+
+namespace Terminal {
+
+struct Tr
+{
+ Q_DECLARE_TR_FUNCTIONS(QtC::Terminal)
+};
+
+} // namespace Terminal
diff --git a/src/plugins/terminal/terminalwidget.cpp b/src/plugins/terminal/terminalwidget.cpp
new file mode 100644
index 00000000000..a7b3590a599
--- /dev/null
+++ b/src/plugins/terminal/terminalwidget.cpp
@@ -0,0 +1,1446 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "terminalwidget.h"
+#include "glyphcache.h"
+#include "terminalcommands.h"
+#include "terminalsettings.h"
+#include "terminalsurface.h"
+
+#include <aggregation/aggregate.h>
+
+#include <coreplugin/actionmanager/actionmanager.h>
+#include <coreplugin/coreconstants.h>
+#include <coreplugin/editormanager/editormanager.h>
+#include <coreplugin/fileutils.h>
+#include <coreplugin/icore.h>
+
+#include <utils/algorithm.h>
+#include <utils/environment.h>
+#include <utils/fileutils.h>
+#include <utils/hostosinfo.h>
+#include <utils/processinterface.h>
+#include <utils/stringutils.h>
+
+#include <vterm.h>
+
+#include <QApplication>
+#include <QCache>
+#include <QClipboard>
+#include <QDesktopServices>
+#include <QElapsedTimer>
+#include <QGlyphRun>
+#include <QLoggingCategory>
+#include <QMenu>
+#include <QPaintEvent>
+#include <QPainter>
+#include <QPainterPath>
+#include <QPixmapCache>
+#include <QRawFont>
+#include <QRegularExpression>
+#include <QScrollBar>
+#include <QTextItem>
+#include <QTextLayout>
+#include <QToolTip>
+
+Q_LOGGING_CATEGORY(terminalLog, "qtc.terminal", QtWarningMsg)
+Q_LOGGING_CATEGORY(selectionLog, "qtc.terminal.selection", QtWarningMsg)
+Q_LOGGING_CATEGORY(paintLog, "qtc.terminal.paint", QtWarningMsg)
+
+using namespace Utils;
+using namespace Utils::Terminal;
+
+namespace Terminal {
+
+namespace ColorIndex {
+enum Indices {
+ Foreground = Internal::ColorIndex::Foreground,
+ Background = Internal::ColorIndex::Background,
+ Selection,
+ FindMatch,
+};
+}
+
+using namespace std::chrono_literals;
+
+// Minimum time between two refreshes. (30fps)
+static constexpr std::chrono::milliseconds minRefreshInterval = 1s / 30;
+
+TerminalWidget::TerminalWidget(QWidget *parent, const OpenTerminalParameters &openParameters)
+ : QAbstractScrollArea(parent)
+ , m_openParameters(openParameters)
+ , m_lastFlush(std::chrono::system_clock::now())
+ , m_lastDoubleClick(std::chrono::system_clock::now())
+{
+ setupSurface();
+ setupFont();
+ setupColors();
+ setupActions();
+
+ m_cursorBlinkTimer.setInterval(750);
+ m_cursorBlinkTimer.setSingleShot(false);
+
+ connect(&m_cursorBlinkTimer, &QTimer::timeout, this, [this]() {
+ if (hasFocus())
+ m_cursorBlinkState = !m_cursorBlinkState;
+ else
+ m_cursorBlinkState = true;
+ updateViewportRect(gridToViewport(QRect{m_cursor.position, m_cursor.position}));
+ });
+
+ setAttribute(Qt::WA_InputMethodEnabled);
+ setAttribute(Qt::WA_MouseTracking);
+
+ setCursor(Qt::IBeamCursor);
+
+ setViewportMargins(1, 1, 1, 1);
+
+ setFocus();
+ setFocusPolicy(Qt::StrongFocus);
+
+ setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
+
+ m_flushDelayTimer.setSingleShot(true);
+ m_flushDelayTimer.setInterval(minRefreshInterval);
+
+ connect(&m_flushDelayTimer, &QTimer::timeout, this, [this]() { flushVTerm(true); });
+
+ m_scrollTimer.setSingleShot(false);
+ m_scrollTimer.setInterval(1s / 2);
+ connect(&m_scrollTimer, &QTimer::timeout, this, [this] {
+ if (m_scrollDirection < 0)
+ verticalScrollBar()->triggerAction(QAbstractSlider::SliderSingleStepSub);
+ else if (m_scrollDirection > 0)
+ verticalScrollBar()->triggerAction(QAbstractSlider::SliderSingleStepAdd);
+ });
+
+ connect(&TerminalSettings::instance(), &AspectContainer::applied, this, [this] {
+ // Setup colors first, as setupFont will redraw the screen.
+ setupColors();
+ setupFont();
+ configBlinkTimer();
+ });
+
+ m_aggregate = new Aggregation::Aggregate(this);
+ m_aggregate->add(this);
+ m_aggregate->add(m_search.get());
+}
+
+TerminalWidget::~TerminalWidget()
+{
+ // The Aggregate stuff tries to do clever deletion of the children, but we
+ // we don't want that.
+ m_aggregate->remove(this);
+ m_aggregate->remove(m_search.get());
+}
+
+void TerminalWidget::setupPty()
+{
+ m_process = std::make_unique<QtcProcess>();
+
+ Environment env = m_openParameters.environment.value_or(Environment::systemEnvironment());
+
+ CommandLine shellCommand = m_openParameters.shellCommand.value_or(
+ CommandLine{TerminalSettings::instance().shell.filePath(),
+ TerminalSettings::instance().shellArguments.value(),
+ CommandLine::Raw});
+
+ // For git bash on Windows
+ env.prependOrSetPath(shellCommand.executable().parentDir());
+ if (env.hasKey("CLINK_NOAUTORUN"))
+ env.unset("CLINK_NOAUTORUN");
+
+ m_process->setProcessMode(ProcessMode::Writer);
+ m_process->setPtyData(Utils::Pty::Data());
+ m_process->setCommand(shellCommand);
+ if (m_openParameters.workingDirectory.has_value())
+ m_process->setWorkingDirectory(*m_openParameters.workingDirectory);
+ m_process->setEnvironment(env);
+
+ if (m_surface->shellIntegration()) {
+ m_surface->shellIntegration()->prepareProcess(*m_process.get());
+ }
+
+ connect(m_process.get(), &QtcProcess::readyReadStandardOutput, this, [this]() {
+ onReadyRead(false);
+ });
+
+ connect(m_process.get(), &QtcProcess::done, this, [this] {
+ if (m_process) {
+ if (m_process->exitCode() != 0) {
+ QByteArray msg = QString("\r\n\033[31mProcess exited with code: %1")
+ .arg(m_process->exitCode())
+ .toUtf8();
+
+ if (!m_process->errorString().isEmpty())
+ msg += QString(" (%1)").arg(m_process->errorString()).toUtf8();
+
+ m_surface->dataFromPty(msg);
+
+ return;
+ }
+ }
+
+ if (m_openParameters.m_exitBehavior == ExitBehavior::Restart) {
+ QMetaObject::invokeMethod(
+ this,
+ [this] {
+ m_process.reset();
+ setupSurface();
+ setupPty();
+ },
+ Qt::QueuedConnection);
+ }
+
+ if (m_openParameters.m_exitBehavior == ExitBehavior::Close)
+ deleteLater();
+
+ if (m_openParameters.m_exitBehavior == ExitBehavior::Keep) {
+ QByteArray msg = QString("\r\nProcess exited with code: %1")
+ .arg(m_process ? m_process->exitCode() : -1)
+ .toUtf8();
+
+ m_surface->dataFromPty(msg);
+ }
+ });
+
+ connect(m_process.get(), &QtcProcess::started, this, [this] {
+ if (m_shellName.isEmpty())
+ m_shellName = m_process->commandLine().executable().fileName();
+ if (HostOsInfo::isWindowsHost() && m_shellName.endsWith(QTC_WIN_EXE_SUFFIX))
+ m_shellName.chop(QStringLiteral(QTC_WIN_EXE_SUFFIX).size());
+
+ applySizeChange();
+ emit started(m_process->processId());
+ });
+
+ m_process->start();
+}
+
+void TerminalWidget::setupFont()
+{
+ QFont f;
+ f.setFixedPitch(true);
+ f.setFamily(TerminalSettings::instance().font.value());
+ f.setPointSize(TerminalSettings::instance().fontSize.value());
+
+ setFont(f);
+}
+
+void TerminalWidget::setupColors()
+{
+ // Check if the colors have changed.
+ std::array<QColor, 20> newColors;
+ for (int i = 0; i < 16; ++i) {
+ newColors[i] = TerminalSettings::instance().colors[i].value();
+ }
+ newColors[ColorIndex::Background] = TerminalSettings::instance().backgroundColor.value();
+ newColors[ColorIndex::Foreground] = TerminalSettings::instance().foregroundColor.value();
+ newColors[ColorIndex::Selection] = TerminalSettings::instance().selectionColor.value();
+ newColors[ColorIndex::FindMatch] = TerminalSettings::instance().findMatchColor.value();
+
+ if (m_currentColors == newColors)
+ return;
+
+ m_currentColors = newColors;
+
+ updateViewport();
+ update();
+}
+
+void TerminalWidget::setupActions()
+{
+ WidgetActions &a = TerminalCommands::widgetActions();
+
+ auto ifHasFocus = [this](void (TerminalWidget::*f)()) {
+ return [this, f] {
+ if (hasFocus())
+ (this->*f)();
+ };
+ };
+
+ // clang-format off
+ connect(&a.copy, &QAction::triggered, this, ifHasFocus(&TerminalWidget::copyToClipboard));
+ connect(&a.paste, &QAction::triggered, this, ifHasFocus(&TerminalWidget::pasteFromClipboard));
+ connect(&a.clearSelection, &QAction::triggered, this, ifHasFocus(&TerminalWidget::clearSelection));
+ connect(&a.clearTerminal, &QAction::triggered, this, ifHasFocus(&TerminalWidget::clearContents));
+ connect(&a.moveCursorWordLeft, &QAction::triggered, this, ifHasFocus(&TerminalWidget::moveCursorWordLeft));
+ connect(&a.moveCursorWordRight, &QAction::triggered, this, ifHasFocus(&TerminalWidget::moveCursorWordRight));
+ // clang-format on
+}
+
+void TerminalWidget::writeToPty(const QByteArray &data)
+{
+ if (m_process && m_process->isRunning())
+ m_process->writeRaw(data);
+}
+
+void TerminalWidget::setupSurface()
+{
+ m_shellIntegration.reset(new ShellIntegration());
+ m_surface = std::make_unique<Internal::TerminalSurface>(QSize{80, 60}, m_shellIntegration.get());
+ m_search = std::make_unique<TerminalSearch>(m_surface.get());
+
+ connect(m_search.get(), &TerminalSearch::hitsChanged, this, &TerminalWidget::updateViewport);
+ connect(m_search.get(), &TerminalSearch::currentHitChanged, this, [this] {
+ SearchHit hit = m_search->currentHit();
+ if (hit.start >= 0) {
+ setSelection(Selection{hit.start, hit.end, true}, hit != m_lastSelectedHit);
+ m_lastSelectedHit = hit;
+ }
+ });
+
+ connect(m_surface.get(),
+ &Internal::TerminalSurface::writeToPty,
+ this,
+ &TerminalWidget::writeToPty);
+ connect(m_surface.get(), &Internal::TerminalSurface::fullSizeChanged, this, [this] {
+ updateScrollBars();
+ });
+ connect(m_surface.get(),
+ &Internal::TerminalSurface::invalidated,
+ this,
+ [this](const QRect &rect) {
+ setSelection(std::nullopt);
+ updateViewportRect(gridToViewport(rect));
+ verticalScrollBar()->setValue(m_surface->fullSize().height());
+ });
+ connect(m_surface.get(),
+ &Internal::TerminalSurface::cursorChanged,
+ this,
+ [this](const Internal::Cursor &oldCursor, const Internal::Cursor &newCursor) {
+ int startX = oldCursor.position.x();
+ int endX = newCursor.position.x();
+
+ if (startX > endX)
+ std::swap(startX, endX);
+
+ int startY = oldCursor.position.y();
+ int endY = newCursor.position.y();
+ if (startY > endY)
+ std::swap(startY, endY);
+
+ m_cursor = newCursor;
+
+ updateViewportRect(
+ gridToViewport(QRect{QPoint{startX, startY}, QPoint{endX, endY}}));
+ configBlinkTimer();
+ });
+ connect(m_surface.get(), &Internal::TerminalSurface::altscreenChanged, this, [this] {
+ updateScrollBars();
+ if (!setSelection(std::nullopt))
+ updateViewport();
+ });
+ connect(m_surface.get(), &Internal::TerminalSurface::unscroll, this, [this] {
+ verticalScrollBar()->setValue(verticalScrollBar()->maximum());
+ });
+ connect(m_surface.get(), &Internal::TerminalSurface::bell, this, [] {
+ if (TerminalSettings::instance().audibleBell.value())
+ QApplication::beep();
+ });
+
+ if (m_shellIntegration) {
+ connect(m_shellIntegration.get(),
+ &ShellIntegration::commandChanged,
+ this,
+ [this](const CommandLine &command) {
+ m_currentCommand = command;
+ emit commandChanged(m_currentCommand);
+ });
+ connect(m_shellIntegration.get(),
+ &ShellIntegration::currentDirChanged,
+ this,
+ [this](const QString &currentDir) {
+ m_cwd = FilePath::fromUserInput(currentDir);
+ emit cwdChanged(m_cwd);
+ });
+ }
+}
+
+void TerminalWidget::configBlinkTimer()
+{
+ bool shouldRun = m_cursor.visible && m_cursor.blink && hasFocus()
+ && TerminalSettings::instance().allowBlinkingCursor.value();
+ if (shouldRun != m_cursorBlinkTimer.isActive()) {
+ if (shouldRun)
+ m_cursorBlinkTimer.start();
+ else
+ m_cursorBlinkTimer.stop();
+ }
+}
+
+QColor TerminalWidget::toQColor(std::variant<int, QColor> color) const
+{
+ if (std::holds_alternative<int>(color)) {
+ int idx = std::get<int>(color);
+ if (idx >= 0 && idx < 18)
+ return m_currentColors[idx];
+
+ return m_currentColors[ColorIndex::Background];
+ }
+ return std::get<QColor>(color);
+}
+
+void TerminalWidget::updateCopyState()
+{
+ if (!hasFocus())
+ return;
+
+ TerminalCommands::widgetActions().copy.setEnabled(m_selection.has_value());
+}
+
+void TerminalWidget::setFont(const QFont &font)
+{
+ m_font = font;
+
+ QFontMetricsF qfm{m_font};
+ const qreal w = [qfm]() -> qreal {
+ if (HostOsInfo::isMacHost())
+ return qfm.maxWidth();
+ return qfm.averageCharWidth();
+ }();
+
+ qCInfo(terminalLog) << font.family() << font.pointSize() << w << viewport()->size();
+
+ m_cellSize = {w, (double) qCeil(qfm.height())};
+
+ QAbstractScrollArea::setFont(m_font);
+
+ if (m_process) {
+ applySizeChange();
+ }
+}
+
+void TerminalWidget::copyToClipboard()
+{
+ QTC_ASSERT(m_selection.has_value(), return);
+
+ QString text = textFromSelection();
+
+ qCDebug(selectionLog) << "Copied to clipboard: " << text;
+
+ setClipboardAndSelection(text);
+}
+
+void TerminalWidget::pasteFromClipboard()
+{
+ QClipboard *clipboard = QApplication::clipboard();
+ const QString clipboardText = clipboard->text(QClipboard::Clipboard);
+
+ if (clipboardText.isEmpty())
+ return;
+
+ m_surface->pasteFromClipboard(clipboardText);
+}
+
+void TerminalWidget::clearSelection()
+{
+ setSelection(std::nullopt);
+ m_surface->sendKey(Qt::Key_Escape);
+}
+
+void TerminalWidget::zoomIn()
+{
+ m_font.setPointSize(m_font.pointSize() + 1);
+ setFont(m_font);
+}
+
+void TerminalWidget::zoomOut()
+{
+ m_font.setPointSize(qMax(m_font.pointSize() - 1, 1));
+ setFont(m_font);
+}
+
+void TerminalWidget::moveCursorWordLeft()
+{
+ writeToPty("\x1b\x62");
+}
+
+void TerminalWidget::moveCursorWordRight()
+{
+ writeToPty("\x1b\x66");
+}
+
+void TerminalWidget::clearContents()
+{
+ m_surface->clearAll();
+}
+
+void TerminalWidget::onReadyRead(bool forceFlush)
+{
+ QByteArray data = m_process->readAllRawStandardOutput();
+
+ m_surface->dataFromPty(data);
+
+ flushVTerm(forceFlush);
+}
+
+void TerminalWidget::flushVTerm(bool force)
+{
+ const std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
+ const std::chrono::milliseconds timeSinceLastFlush
+ = std::chrono::duration_cast<std::chrono::milliseconds>(now - m_lastFlush);
+
+ const bool shouldFlushImmediately = timeSinceLastFlush > minRefreshInterval;
+ if (force || shouldFlushImmediately) {
+ if (m_flushDelayTimer.isActive())
+ m_flushDelayTimer.stop();
+
+ m_lastFlush = now;
+ m_surface->flush();
+ return;
+ }
+
+ if (!m_flushDelayTimer.isActive()) {
+ const std::chrono::milliseconds timeToNextFlush = (minRefreshInterval - timeSinceLastFlush);
+ m_flushDelayTimer.start(timeToNextFlush.count());
+ }
+}
+
+QString TerminalWidget::textFromSelection() const
+{
+ if (!m_selection)
+ return {};
+
+ Internal::CellIterator it = m_surface->iteratorAt(m_selection->start);
+ Internal::CellIterator end = m_surface->iteratorAt(m_selection->end);
+
+ std::u32string s;
+ bool previousWasZero = false;
+ for (; it != end; ++it) {
+ if (it.gridPos().x() == 0 && !s.empty() && previousWasZero)
+ s += U'\n';
+
+ if (*it != 0) {
+ previousWasZero = false;
+ s += *it;
+ } else {
+ previousWasZero = true;
+ }
+ }
+
+ return QString::fromUcs4(s.data(), static_cast<int>(s.size()));
+}
+
+bool TerminalWidget::setSelection(const std::optional<Selection> &selection, bool scroll)
+{
+ qCDebug(selectionLog) << "setSelection" << selection.has_value();
+ if (selection.has_value())
+ qCDebug(selectionLog) << "start:" << selection->start << "end:" << selection->end
+ << "final:" << selection->final;
+
+ if (selectionLog().isDebugEnabled())
+ updateViewport();
+
+ if (selection == m_selection)
+ return false;
+
+ m_selection = selection;
+
+ updateCopyState();
+
+ if (m_selection && m_selection->final) {
+ qCDebug(selectionLog) << "Copy enabled:" << selection.has_value();
+ QString text = textFromSelection();
+
+ QClipboard *clipboard = QApplication::clipboard();
+ if (clipboard->supportsSelection()) {
+ qCDebug(selectionLog) << "Selection set to clipboard: " << text;
+ clipboard->setText(text, QClipboard::Selection);
+ }
+
+ if (scroll) {
+ QPoint start = m_surface->posToGrid(m_selection->start);
+ QPoint end = m_surface->posToGrid(m_selection->end);
+ QRect viewRect = gridToViewport(QRect{start, end});
+ if (viewRect.y() >= viewport()->height() || viewRect.y() < 0) {
+ // Selection is outside of the viewport, scroll to it.
+ verticalScrollBar()->setValue(start.y());
+ }
+ }
+
+ m_search->setCurrentSelection(SearchHitWithText{{selection->start, selection->end}, text});
+ }
+
+ if (!selectionLog().isDebugEnabled())
+ updateViewport();
+
+ return true;
+}
+
+void TerminalWidget::setShellName(const QString &shellName)
+{
+ m_shellName = shellName;
+}
+
+QString TerminalWidget::shellName() const
+{
+ return m_shellName;
+}
+
+FilePath TerminalWidget::cwd() const
+{
+ return m_cwd;
+}
+
+CommandLine TerminalWidget::currentCommand() const
+{
+ return m_currentCommand;
+}
+
+std::optional<Id> TerminalWidget::identifier() const
+{
+ return m_openParameters.identifier;
+}
+
+QProcess::ProcessState TerminalWidget::processState() const
+{
+ if (m_process)
+ return m_process->state();
+
+ return QProcess::NotRunning;
+}
+
+void TerminalWidget::restart(const OpenTerminalParameters &openParameters)
+{
+ QTC_ASSERT(!m_process || !m_process->isRunning(), return);
+ m_openParameters = openParameters;
+
+ m_process.reset();
+ setupSurface();
+ setupPty();
+}
+
+QPoint TerminalWidget::viewportToGlobal(QPoint p) const
+{
+ int y = p.y() - topMargin();
+ const double offset = verticalScrollBar()->value() * m_cellSize.height();
+ y += offset;
+
+ return {p.x(), y};
+}
+
+QPoint TerminalWidget::globalToViewport(QPoint p) const
+{
+ int y = p.y() + topMargin();
+ const double offset = verticalScrollBar()->value() * m_cellSize.height();
+ y -= offset;
+
+ return {p.x(), y};
+}
+
+QPoint TerminalWidget::globalToGrid(QPointF p) const
+{
+ return QPoint(p.x() / m_cellSize.width(), p.y() / m_cellSize.height());
+}
+
+QPointF TerminalWidget::gridToGlobal(QPoint p, bool bottom, bool right) const
+{
+ QPointF result = QPointF(p.x() * m_cellSize.width(), p.y() * m_cellSize.height());
+ if (bottom || right)
+ result += {right ? m_cellSize.width() : 0, bottom ? m_cellSize.height() : 0};
+ return result;
+}
+
+qreal TerminalWidget::topMargin() const
+{
+ return viewport()->size().height() - (m_surface->liveSize().height() * m_cellSize.height());
+}
+
+static QPixmap generateWavyPixmap(qreal maxRadius, const QPen &pen)
+{
+ const qreal radiusBase = qMax(qreal(1), maxRadius);
+ const qreal pWidth = pen.widthF();
+
+ QString key = QLatin1String("WaveUnderline-") % pen.color().name()
+ % QString::number(*(size_t *) &radiusBase, 16)
+ % QString::number(*(size_t *) &pWidth);
+
+ QPixmap pixmap;
+ if (QPixmapCache::find(key, &pixmap))
+ return pixmap;
+
+ const qreal halfPeriod = qMax(qreal(2), qreal(radiusBase * 1.61803399)); // the golden ratio
+ const int width = qCeil(100 / (2 * halfPeriod)) * (2 * halfPeriod);
+ const qreal radius = qFloor(radiusBase * 2) / 2.;
+
+ QPainterPath path;
+
+ qreal xs = 0;
+ qreal ys = radius;
+
+ while (xs < width) {
+ xs += halfPeriod;
+ ys = -ys;
+ path.quadTo(xs - halfPeriod / 2, ys, xs, 0);
+ }
+
+ pixmap = QPixmap(width, radius * 2);
+ pixmap.fill(Qt::transparent);
+ {
+ QPen wavePen = pen;
+ wavePen.setCapStyle(Qt::SquareCap);
+
+ // This is to protect against making the line too fat, as happens on macOS
+ // due to it having a rather thick width for the regular underline.
+ const qreal maxPenWidth = .8 * radius;
+ if (wavePen.widthF() > maxPenWidth)
+ wavePen.setWidthF(maxPenWidth);
+
+ QPainter imgPainter(&pixmap);
+ imgPainter.setPen(wavePen);
+ imgPainter.setRenderHint(QPainter::Antialiasing);
+ imgPainter.translate(0, radius);
+ imgPainter.drawPath(path);
+ }
+
+ QPixmapCache::insert(key, pixmap);
+
+ return pixmap;
+}
+
+// Copied from qpainter.cpp
+static void drawTextItemDecoration(QPainter &painter,
+ const QPointF &pos,
+ QTextCharFormat::UnderlineStyle underlineStyle,
+ QTextItem::RenderFlags flags,
+ qreal width,
+ const QColor &underlineColor,
+ const QRawFont &font)
+{
+ if (underlineStyle == QTextCharFormat::NoUnderline
+ && !(flags & (QTextItem::StrikeOut | QTextItem::Overline)))
+ return;
+
+ const QPen oldPen = painter.pen();
+ const QBrush oldBrush = painter.brush();
+ painter.setBrush(Qt::NoBrush);
+ QPen pen = oldPen;
+ pen.setStyle(Qt::SolidLine);
+ pen.setWidthF(font.lineThickness());
+ pen.setCapStyle(Qt::FlatCap);
+
+ QLineF line(qFloor(pos.x()), pos.y(), qFloor(pos.x() + width), pos.y());
+
+ const qreal underlineOffset = font.underlinePosition();
+
+ /*if (underlineStyle == QTextCharFormat::SpellCheckUnderline) {
+ QPlatformTheme *theme = QGuiApplicationPrivate::platformTheme();
+ if (theme)
+ underlineStyle = QTextCharFormat::UnderlineStyle(
+ theme->themeHint(QPlatformTheme::SpellCheckUnderlineStyle).toInt());
+ if (underlineStyle == QTextCharFormat::SpellCheckUnderline) // still not resolved
+ underlineStyle = QTextCharFormat::WaveUnderline;
+ }*/
+
+ if (underlineStyle == QTextCharFormat::WaveUnderline) {
+ painter.save();
+ painter.translate(0, pos.y() + 1);
+ qreal maxHeight = font.descent() - qreal(1);
+
+ QColor uc = underlineColor;
+ if (uc.isValid())
+ pen.setColor(uc);
+
+ // Adapt wave to underlineOffset or pen width, whatever is larger, to make it work on all platforms
+ const QPixmap wave = generateWavyPixmap(qMin(qMax(underlineOffset, pen.widthF()),
+ maxHeight / qreal(2.)),
+ pen);
+ const int descent = qFloor(maxHeight);
+
+ painter.setBrushOrigin(painter.brushOrigin().x(), 0);
+ painter.fillRect(pos.x(), 0, qCeil(width), qMin(wave.height(), descent), wave);
+ painter.restore();
+ } else if (underlineStyle != QTextCharFormat::NoUnderline) {
+ // Deliberately ceil the offset to avoid the underline coming too close to
+ // the text above it, but limit it to stay within descent.
+ qreal adjustedUnderlineOffset = std::ceil(underlineOffset) + 0.5;
+ if (underlineOffset <= font.descent())
+ adjustedUnderlineOffset = qMin(adjustedUnderlineOffset, font.descent() - qreal(0.5));
+ const qreal underlinePos = pos.y() + adjustedUnderlineOffset;
+ QColor uc = underlineColor;
+ if (uc.isValid())
+ pen.setColor(uc);
+
+ pen.setStyle((Qt::PenStyle)(underlineStyle));
+ painter.setPen(pen);
+ QLineF underline(line.x1(), underlinePos, line.x2(), underlinePos);
+ painter.drawLine(underline);
+ }
+
+ pen.setStyle(Qt::SolidLine);
+ pen.setColor(oldPen.color());
+
+ if (flags & QTextItem::StrikeOut) {
+ QLineF strikeOutLine = line;
+ strikeOutLine.translate(0., -font.ascent() / 3.);
+ QColor uc = underlineColor;
+ if (uc.isValid())
+ pen.setColor(uc);
+ painter.setPen(pen);
+ painter.drawLine(strikeOutLine);
+ }
+
+ if (flags & QTextItem::Overline) {
+ QLineF overline = line;
+ overline.translate(0., -font.ascent());
+ QColor uc = underlineColor;
+ if (uc.isValid())
+ pen.setColor(uc);
+ painter.setPen(pen);
+ painter.drawLine(overline);
+ }
+
+ painter.setPen(oldPen);
+ painter.setBrush(oldBrush);
+}
+
+bool TerminalWidget::paintFindMatches(QPainter &p,
+ QList<SearchHit>::const_iterator &it,
+ const QRectF &cellRect,
+ const QPoint gridPos) const
+{
+ if (it == m_search->hits().constEnd())
+ return false;
+
+ const int pos = m_surface->gridToPos(gridPos);
+ while (it != m_search->hits().constEnd()) {
+ if (pos < it->start)
+ return false;
+
+ if (pos >= it->end) {
+ ++it;
+ continue;
+ }
+ break;
+ }
+
+ if (it == m_search->hits().constEnd())
+ return false;
+
+ p.fillRect(cellRect, m_currentColors[ColorIndex::FindMatch]);
+
+ return true;
+}
+
+bool TerminalWidget::paintSelection(QPainter &p, const QRectF &cellRect, const QPoint gridPos) const
+{
+ bool isInSelection = false;
+ const int pos = m_surface->gridToPos(gridPos);
+
+ if (m_selection)
+ isInSelection = pos >= m_selection->start && pos < m_selection->end;
+
+ if (isInSelection)
+ p.fillRect(cellRect, m_currentColors[ColorIndex::Selection]);
+
+ return isInSelection;
+}
+
+int TerminalWidget::paintCell(QPainter &p,
+ const QRectF &cellRect,
+ QPoint gridPos,
+ const Internal::TerminalCell &cell,
+ QFont &f,
+ QList<SearchHit>::const_iterator &searchIt) const
+{
+ bool paintBackground = !paintSelection(p, cellRect, gridPos)
+ && !paintFindMatches(p, searchIt, cellRect, gridPos);
+
+ bool isDefaultBg = std::holds_alternative<int>(cell.backgroundColor)
+ && std::get<int>(cell.backgroundColor) == 17;
+
+ if (paintBackground && !isDefaultBg)
+ p.fillRect(cellRect, toQColor(cell.backgroundColor));
+
+ p.setPen(toQColor(cell.foregroundColor));
+
+ f.setBold(cell.bold);
+ f.setItalic(cell.italic);
+
+ if (!cell.text.isEmpty()) {
+ const auto r = Internal::GlyphCache::instance().get(f, cell.text);
+
+ if (r) {
+ const auto brSize = r->boundingRect().size();
+ QPointF brOffset;
+ if (brSize.width() > cellRect.size().width())
+ brOffset.setX(-(brSize.width() - cellRect.size().width()) / 2.0);
+ if (brSize.height() > cellRect.size().height())
+ brOffset.setY(-(brSize.height() - cellRect.size().height()) / 2.0);
+
+ QPointF finalPos = cellRect.topLeft() + brOffset;
+
+ p.drawGlyphRun(finalPos, *r);
+
+ bool tempLink = false;
+ if (m_linkSelection) {
+ int chPos = m_surface->gridToPos(gridPos);
+ tempLink = chPos >= m_linkSelection->start && chPos < m_linkSelection->end;
+ }
+ if (cell.underlineStyle != QTextCharFormat::NoUnderline || cell.strikeOut || tempLink) {
+ QTextItem::RenderFlags flags;
+ //flags.setFlag(QTextItem::RenderFlag::Underline, cell.format.fontUnderline());
+ flags.setFlag(QTextItem::StrikeOut, cell.strikeOut);
+ finalPos.setY(finalPos.y() + r->rawFont().ascent());
+ drawTextItemDecoration(p,
+ finalPos,
+ tempLink ? QTextCharFormat::DashUnderline
+ : cell.underlineStyle,
+ flags,
+ cellRect.size().width(),
+ {},
+ r->rawFont());
+ }
+ }
+ }
+
+ return cell.width;
+}
+
+void TerminalWidget::paintCursor(QPainter &p) const
+{
+ if (!m_process || !m_process->isRunning())
+ return;
+
+ auto cursor = m_surface->cursor();
+
+ const bool blinkState = !cursor.blink || m_cursorBlinkState
+ || !TerminalSettings::instance().allowBlinkingCursor.value();
+
+ if (cursor.visible && blinkState) {
+ const int cursorCellWidth = m_surface->cellWidthAt(cursor.position.x(), cursor.position.y());
+
+ QRectF cursorRect = QRectF(gridToGlobal(cursor.position),
+ gridToGlobal({cursor.position.x() + cursorCellWidth,
+ cursor.position.y()},
+ true));
+
+ cursorRect.adjust(0, 0, 0, -1);
+
+ if (hasFocus()) {
+ QPainter::CompositionMode oldMode = p.compositionMode();
+ p.setCompositionMode(QPainter::RasterOp_NotDestination);
+ switch (cursor.shape) {
+ case Internal::Cursor::Shape::Block:
+ p.fillRect(cursorRect, p.pen().brush());
+ break;
+ case Internal::Cursor::Shape::Underline:
+ p.drawLine(cursorRect.bottomLeft(), cursorRect.bottomRight());
+ break;
+ case Internal::Cursor::Shape::LeftBar:
+ p.drawLine(cursorRect.topLeft(), cursorRect.bottomLeft());
+ break;
+ }
+ p.setCompositionMode(oldMode);
+ } else {
+ p.drawRect(cursorRect);
+ }
+ }
+}
+
+void TerminalWidget::paintPreedit(QPainter &p) const
+{
+ auto cursor = m_surface->cursor();
+ if (!m_preEditString.isEmpty()) {
+ QRectF rect = QRectF(gridToGlobal(cursor.position),
+ gridToGlobal({cursor.position.x(), cursor.position.y()}, true, true));
+
+ p.fillRect(rect, QColor::fromRgb(0, 0, 0));
+ p.setPen(Qt::white);
+ p.drawText(rect, m_preEditString);
+ }
+}
+
+void TerminalWidget::paintCells(QPainter &p, QPaintEvent *event) const
+{
+ QFont f = m_font;
+
+ const int scrollOffset = verticalScrollBar()->value();
+
+ const int maxRow = m_surface->fullSize().height();
+ const int startRow = qFloor((qreal) event->rect().y() / m_cellSize.height()) + scrollOffset;
+ const int endRow = qMin(maxRow,
+ qCeil((event->rect().y() + event->rect().height()) / m_cellSize.height())
+ + scrollOffset);
+
+ QList<SearchHit>::const_iterator searchIt
+ = std::lower_bound(m_search->hits().constBegin(),
+ m_search->hits().constEnd(),
+ startRow,
+ [this](const SearchHit &hit, int value) {
+ return m_surface->posToGrid(hit.start).y() < value;
+ });
+
+ for (int cellY = startRow; cellY < endRow; ++cellY) {
+ for (int cellX = 0; cellX < m_surface->liveSize().width();) {
+ const auto cell = m_surface->fetchCell(cellX, cellY);
+
+ QRectF cellRect(gridToGlobal({cellX, cellY}),
+ QSizeF{m_cellSize.width() * cell.width, m_cellSize.height()});
+
+ int numCells = paintCell(p, cellRect, {cellX, cellY}, cell, f, searchIt);
+
+ cellX += numCells;
+ }
+ }
+}
+
+void TerminalWidget::paintDebugSelection(QPainter &p, const Selection &selection) const
+{
+ auto s = globalToViewport(gridToGlobal(m_surface->posToGrid(selection.start)).toPoint());
+ const auto e = globalToViewport(
+ gridToGlobal(m_surface->posToGrid(selection.end), true).toPoint());
+
+ p.setPen(QPen(Qt::green, 1, Qt::DashLine));
+ p.drawLine(s.x(), 0, s.x(), height());
+ p.drawLine(0, s.y(), width(), s.y());
+
+ p.setPen(QPen(Qt::red, 1, Qt::DashLine));
+
+ p.drawLine(e.x(), 0, e.x(), height());
+ p.drawLine(0, e.y(), width(), e.y());
+}
+
+void TerminalWidget::paintEvent(QPaintEvent *event)
+{
+ QElapsedTimer t;
+ t.start();
+ event->accept();
+ QPainter p(viewport());
+
+ p.save();
+
+ if (paintLog().isDebugEnabled())
+ p.fillRect(event->rect(), QColor::fromRgb(rand() % 60, rand() % 60, rand() % 60));
+ else
+ p.fillRect(event->rect(), m_currentColors[ColorIndex::Background]);
+
+ int scrollOffset = verticalScrollBar()->value();
+ int offset = -(scrollOffset * m_cellSize.height());
+
+ qreal margin = topMargin();
+
+ p.translate(QPointF{0.0, offset + margin});
+
+ paintCells(p, event);
+ paintCursor(p);
+ paintPreedit(p);
+
+ p.restore();
+
+ p.fillRect(QRectF{{0, 0}, QSizeF{(qreal) width(), topMargin()}},
+ m_currentColors[ColorIndex::Background]);
+
+ if (selectionLog().isDebugEnabled()) {
+ if (m_selection)
+ paintDebugSelection(p, *m_selection);
+ if (m_linkSelection)
+ paintDebugSelection(p, *m_linkSelection);
+ }
+
+ if (paintLog().isDebugEnabled()) {
+ QToolTip::showText(this->mapToGlobal(QPoint(width() - 200, 0)),
+ QString("Paint: %1ms").arg(t.elapsed()));
+ }
+}
+
+void TerminalWidget::keyPressEvent(QKeyEvent *event)
+{
+ // Don't blink during typing
+ if (m_cursorBlinkTimer.isActive()) {
+ m_cursorBlinkTimer.start();
+ m_cursorBlinkState = true;
+ }
+
+ if (event->key() == Qt::Key_Escape) {
+ bool sendToTerminal = TerminalSettings::instance().sendEscapeToTerminal.value();
+ bool send = false;
+ if (sendToTerminal && event->modifiers() == Qt::NoModifier)
+ send = true;
+ else if (!sendToTerminal && event->modifiers() == Qt::ShiftModifier)
+ send = true;
+
+ if (send) {
+ event->setModifiers(Qt::NoModifier);
+ m_surface->sendKey(event);
+ return;
+ }
+
+ if (m_selection)
+ TerminalCommands::widgetActions().clearSelection.trigger();
+ else {
+ QTC_ASSERT(Core::ActionManager::command(Core::Constants::S_RETURNTOEDITOR), return);
+ Core::ActionManager::command(Core::Constants::S_RETURNTOEDITOR)->action()->trigger();
+ }
+ return;
+ }
+
+ auto oldSelection = m_selection;
+ if (TerminalCommands::triggerAction(event)) {
+ if (oldSelection && oldSelection == m_selection)
+ setSelection(std::nullopt);
+ return;
+ }
+
+ event->accept();
+
+ m_surface->sendKey(event);
+}
+
+void TerminalWidget::applySizeChange()
+{
+ QSize newLiveSize = {
+ qFloor((qreal) (viewport()->size().width()) / (qreal) m_cellSize.width()),
+ qFloor((qreal) (viewport()->size().height()) / m_cellSize.height()),
+ };
+
+ if (newLiveSize.height() <= 0)
+ newLiveSize.setHeight(1);
+
+ if (newLiveSize.width() <= 0)
+ newLiveSize.setWidth(1);
+
+ if (m_process && m_process->ptyData())
+ m_process->ptyData()->resize(newLiveSize);
+
+ m_surface->resize(newLiveSize);
+ flushVTerm(true);
+}
+
+void TerminalWidget::updateScrollBars()
+{
+ int scrollSize = m_surface->fullSize().height() - m_surface->liveSize().height();
+ verticalScrollBar()->setRange(0, scrollSize);
+ verticalScrollBar()->setValue(verticalScrollBar()->maximum());
+ updateViewport();
+}
+
+void TerminalWidget::resizeEvent(QResizeEvent *event)
+{
+ event->accept();
+
+ // If increasing in size, we'll trigger libvterm to call sb_popline in
+ // order to pull lines out of the history. This will cause the scrollback
+ // to decrease in size which reduces the size of the verticalScrollBar.
+ // That will trigger a scroll offset increase which we want to ignore.
+ m_ignoreScroll = true;
+
+ applySizeChange();
+
+ setSelection(std::nullopt);
+ m_ignoreScroll = false;
+}
+
+QRect TerminalWidget::gridToViewport(QRect rect) const
+{
+ int offset = verticalScrollBar()->value();
+
+ int startRow = rect.y() - offset;
+ int numRows = rect.height();
+ int numCols = rect.width();
+
+ QRect r{qFloor(rect.x() * m_cellSize.width()),
+ qFloor(startRow * m_cellSize.height()),
+ qCeil(numCols * m_cellSize.width()),
+ qCeil(numRows * m_cellSize.height())};
+
+ r.translate(0, topMargin());
+
+ return r;
+}
+
+void TerminalWidget::updateViewport()
+{
+ viewport()->update();
+}
+
+void TerminalWidget::updateViewportRect(const QRect &rect)
+{
+ viewport()->update(rect);
+}
+
+void TerminalWidget::wheelEvent(QWheelEvent *event)
+{
+ verticalScrollBar()->event(event);
+}
+
+void TerminalWidget::focusInEvent(QFocusEvent *)
+{
+ updateViewport();
+ configBlinkTimer();
+ updateCopyState();
+}
+void TerminalWidget::focusOutEvent(QFocusEvent *)
+{
+ updateViewport();
+ configBlinkTimer();
+}
+
+void TerminalWidget::inputMethodEvent(QInputMethodEvent *event)
+{
+ m_preEditString = event->preeditString();
+
+ if (event->commitString().isEmpty()) {
+ updateViewport();
+ return;
+ }
+
+ m_surface->sendKey(event->commitString());
+}
+
+void TerminalWidget::mousePressEvent(QMouseEvent *event)
+{
+ m_scrollDirection = 0;
+
+ m_activeMouseSelect.start = viewportToGlobal(event->pos());
+
+ if (event->button() == Qt::LeftButton && event->modifiers() == Qt::ControlModifier) {
+ if (m_linkSelection) {
+ if (m_linkSelection->link.targetFilePath.scheme().toString().startsWith("http")) {
+ QDesktopServices::openUrl(m_linkSelection->link.targetFilePath.toUrl());
+ return;
+ }
+
+ if (m_linkSelection->link.targetFilePath.isDir())
+ Core::FileUtils::showInFileSystemView(m_linkSelection->link.targetFilePath);
+ else
+ Core::EditorManager::openEditorAt(m_linkSelection->link);
+ }
+ return;
+ }
+
+ if (event->button() == Qt::LeftButton) {
+ if (std::chrono::system_clock::now() - m_lastDoubleClick < 500ms) {
+ m_selectLineMode = true;
+ const Selection newSelection{m_surface->gridToPos(
+ {0, m_surface->posToGrid(m_selection->start).y()}),
+ m_surface->gridToPos(
+ {m_surface->liveSize().width(),
+ m_surface->posToGrid(m_selection->end).y()}),
+ false};
+ setSelection(newSelection);
+ } else {
+ m_selectLineMode = false;
+ int pos = m_surface->gridToPos(globalToGrid(viewportToGlobal(event->pos())));
+ setSelection(Selection{pos, pos, false});
+ }
+ event->accept();
+ updateViewport();
+ } else if (event->button() == Qt::RightButton) {
+ if (event->modifiers() == Qt::ShiftModifier) {
+ QMenu *contextMenu = new QMenu(this);
+ contextMenu->addAction(&TerminalCommands::widgetActions().copy);
+ contextMenu->addAction(&TerminalCommands::widgetActions().paste);
+ contextMenu->addSeparator();
+ contextMenu->addAction(&TerminalCommands::widgetActions().clearTerminal);
+ contextMenu->addSeparator();
+ contextMenu->addAction(TerminalCommands::openSettingsAction());
+
+ contextMenu->popup(event->globalPos());
+ } else if (m_selection) {
+ copyToClipboard();
+ setSelection(std::nullopt);
+ } else {
+ pasteFromClipboard();
+ }
+ } else if (event->button() == Qt::MiddleButton) {
+ QClipboard *clipboard = QApplication::clipboard();
+ if (clipboard->supportsSelection()) {
+ const QString selectionText = clipboard->text(QClipboard::Selection);
+ if (!selectionText.isEmpty())
+ m_surface->pasteFromClipboard(selectionText);
+ } else {
+ m_surface->pasteFromClipboard(textFromSelection());
+ }
+ }
+}
+void TerminalWidget::mouseMoveEvent(QMouseEvent *event)
+{
+ if (m_selection && event->buttons() & Qt::LeftButton) {
+ Selection newSelection = *m_selection;
+ int scrollVelocity = 0;
+ if (event->pos().y() < 0) {
+ scrollVelocity = (event->pos().y());
+ } else if (event->pos().y() > viewport()->height()) {
+ scrollVelocity = (event->pos().y() - viewport()->height());
+ }
+
+ if ((scrollVelocity != 0) != m_scrollTimer.isActive()) {
+ if (scrollVelocity != 0)
+ m_scrollTimer.start();
+ else
+ m_scrollTimer.stop();
+ }
+
+ m_scrollDirection = scrollVelocity;
+
+ if (m_scrollTimer.isActive() && scrollVelocity != 0) {
+ const std::chrono::milliseconds scrollInterval = 1000ms / qAbs(scrollVelocity);
+ if (m_scrollTimer.intervalAsDuration() != scrollInterval)
+ m_scrollTimer.setInterval(scrollInterval);
+ }
+
+ int start = m_surface->gridToPos(globalToGrid(m_activeMouseSelect.start));
+ int newEnd = m_surface->gridToPos(globalToGrid(viewportToGlobal(event->pos())));
+
+ if (start > newEnd) {
+ std::swap(start, newEnd);
+ }
+ if (start < 0)
+ start = 0;
+
+ if (m_selectLineMode) {
+ newSelection.start = m_surface->gridToPos({0, m_surface->posToGrid(start).y()});
+ newSelection.end = m_surface->gridToPos(
+ {m_surface->liveSize().width(), m_surface->posToGrid(newEnd).y()});
+ } else {
+ newSelection.start = start;
+ newSelection.end = newEnd;
+ }
+
+ setSelection(newSelection);
+ } else if (event->modifiers() == Qt::ControlModifier) {
+ checkLinkAt(event->pos());
+ } else if (m_linkSelection) {
+ m_linkSelection.reset();
+ updateViewport();
+ }
+
+ if (m_linkSelection) {
+ setCursor(Qt::PointingHandCursor);
+ } else {
+ setCursor(Qt::IBeamCursor);
+ }
+}
+
+void TerminalWidget::checkLinkAt(const QPoint &pos)
+{
+ const TextAndOffsets hit = textAt(pos);
+
+ if (hit.text.size() > 0) {
+ QString t = QString::fromUcs4(hit.text.c_str(), hit.text.size()).trimmed();
+ t = chopIfEndsWith(t, ':');
+
+ if (t.isEmpty())
+ return;
+
+ if (t.startsWith("~/")) {
+ t = QDir::homePath() + t.mid(1);
+ }
+
+ Link link = Link::fromString(t, true);
+
+ if (!link.targetFilePath.isAbsolutePath())
+ link.targetFilePath = m_cwd.pathAppended(link.targetFilePath.path());
+
+ if (link.hasValidTarget()
+ && (link.targetFilePath.scheme().toString().startsWith("http")
+ || link.targetFilePath.exists())) {
+ const LinkSelection newSelection = LinkSelection{{hit.start, hit.end}, link};
+ if (!m_linkSelection || *m_linkSelection != newSelection) {
+ m_linkSelection = newSelection;
+ updateViewport();
+ }
+ return;
+ }
+ }
+
+ if (m_linkSelection) {
+ m_linkSelection.reset();
+ updateViewport();
+ }
+}
+
+void TerminalWidget::mouseReleaseEvent(QMouseEvent *event)
+{
+ m_scrollTimer.stop();
+
+ if (m_selection && event->button() == Qt::LeftButton) {
+ if (m_selection->end - m_selection->start == 0)
+ setSelection(std::nullopt);
+ else
+ setSelection(Selection{m_selection->start, m_selection->end, true});
+ }
+}
+
+TerminalWidget::TextAndOffsets TerminalWidget::textAt(const QPoint &pos) const
+{
+ auto it = m_surface->iteratorAt(globalToGrid(viewportToGlobal(pos)));
+ auto itRev = m_surface->rIteratorAt(globalToGrid(viewportToGlobal(pos)));
+
+ std::u32string whiteSpaces = U" \t\x00a0";
+
+ const bool inverted = whiteSpaces.find(*it) != std::u32string::npos || *it == 0;
+
+ auto predicate = [inverted, whiteSpaces](const std::u32string::value_type &ch) {
+ if (inverted)
+ return ch != 0 && whiteSpaces.find(ch) == std::u32string::npos;
+ else
+ return ch == 0 || whiteSpaces.find(ch) != std::u32string::npos;
+ };
+
+ auto itRight = std::find_if(it, m_surface->end(), predicate);
+ auto itLeft = std::find_if(itRev, m_surface->rend(), predicate);
+
+ std::u32string text;
+ std::copy(itLeft.base(), it, std::back_inserter(text));
+ std::copy(it, itRight, std::back_inserter(text));
+ std::transform(text.begin(), text.end(), text.begin(), [](const char32_t &ch) {
+ return ch == 0 ? U' ' : ch;
+ });
+
+ return {(itLeft.base()).position(), itRight.position(), text};
+}
+
+void TerminalWidget::mouseDoubleClickEvent(QMouseEvent *event)
+{
+ const auto hit = textAt(event->pos());
+
+ setSelection(Selection{hit.start, hit.end, true});
+
+ m_lastDoubleClick = std::chrono::system_clock::now();
+
+ event->accept();
+}
+
+void TerminalWidget::showEvent(QShowEvent *event)
+{
+ Q_UNUSED(event);
+
+ if (!m_process)
+ setupPty();
+
+ QAbstractScrollArea::showEvent(event);
+}
+
+bool TerminalWidget::event(QEvent *event)
+{
+ if (event->type() == QEvent::ShortcutOverride) {
+ if (hasFocus()) {
+ event->accept();
+ return true;
+ }
+ }
+
+ if (event->type() == QEvent::KeyPress) {
+ QKeyEvent *k = (QKeyEvent *) event;
+ keyPressEvent(k);
+ return true;
+ }
+ if (event->type() == QEvent::KeyRelease) {
+ QKeyEvent *k = (QKeyEvent *) event;
+ keyReleaseEvent(k);
+ return true;
+ }
+
+ if (event->type() == QEvent::Paint) {
+ QPainter p(this);
+ p.fillRect(QRect(QPoint(0, 0), size()), m_currentColors[ColorIndex::Background]);
+ return true;
+ }
+
+ return QAbstractScrollArea::event(event);
+}
+
+} // namespace Terminal
diff --git a/src/plugins/terminal/terminalwidget.h b/src/plugins/terminal/terminalwidget.h
new file mode 100644
index 00000000000..215d8c6d58e
--- /dev/null
+++ b/src/plugins/terminal/terminalwidget.h
@@ -0,0 +1,226 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include "terminalsearch.h"
+#include "terminalsurface.h"
+
+#include <aggregation/aggregate.h>
+
+#include <utils/link.h>
+#include <utils/qtcprocess.h>
+#include <utils/terminalhooks.h>
+
+#include <QAbstractScrollArea>
+#include <QAction>
+#include <QTextLayout>
+#include <QTimer>
+
+#include <chrono>
+#include <memory>
+
+namespace Terminal {
+
+class TerminalWidget : public QAbstractScrollArea
+{
+ friend class CellIterator;
+ Q_OBJECT
+public:
+ TerminalWidget(QWidget *parent = nullptr,
+ const Utils::Terminal::OpenTerminalParameters &openParameters = {});
+ ~TerminalWidget() override;
+
+ void setFont(const QFont &font);
+
+ void copyToClipboard();
+ void pasteFromClipboard();
+
+ void clearSelection();
+
+ void zoomIn();
+ void zoomOut();
+
+ void moveCursorWordLeft();
+ void moveCursorWordRight();
+
+ void clearContents();
+
+ TerminalSearch *search() { return m_search.get(); }
+
+ struct Selection
+ {
+ int start;
+ int end;
+ bool final{false};
+
+ bool operator!=(const Selection &other) const
+ {
+ return start != other.start || end != other.end || final != other.final;
+ }
+
+ bool operator==(const Selection &other) const { return !operator!=(other); }
+ };
+
+ struct LinkSelection : public Selection
+ {
+ Utils::Link link;
+
+ bool operator!=(const LinkSelection &other) const
+ {
+ return link != other.link || Selection::operator!=(other);
+ }
+ };
+
+ void setShellName(const QString &shellName);
+ QString shellName() const;
+
+ Utils::FilePath cwd() const;
+ Utils::CommandLine currentCommand() const;
+ std::optional<Utils::Id> identifier() const;
+ QProcess::ProcessState processState() const;
+
+ void restart(const Utils::Terminal::OpenTerminalParameters &openParameters);
+
+signals:
+ void started(qint64 pid);
+ void cwdChanged(const Utils::FilePath &cwd);
+ void commandChanged(const Utils::CommandLine &cmd);
+
+protected:
+ void paintEvent(QPaintEvent *event) override;
+ void keyPressEvent(QKeyEvent *event) override;
+ void resizeEvent(QResizeEvent *event) override;
+ void wheelEvent(QWheelEvent *event) override;
+ void focusInEvent(QFocusEvent *event) override;
+ void focusOutEvent(QFocusEvent *event) override;
+ void inputMethodEvent(QInputMethodEvent *event) override;
+
+ void mousePressEvent(QMouseEvent *event) override;
+ void mouseMoveEvent(QMouseEvent *event) override;
+ void mouseReleaseEvent(QMouseEvent *event) override;
+ void mouseDoubleClickEvent(QMouseEvent *event) override;
+
+ void showEvent(QShowEvent *event) override;
+
+ bool event(QEvent *event) override;
+
+protected:
+ void onReadyRead(bool forceFlush);
+ void setupSurface();
+ void setupFont();
+ void setupPty();
+ void setupColors();
+ void setupActions();
+
+ void writeToPty(const QByteArray &data);
+
+ int paintCell(QPainter &p,
+ const QRectF &cellRect,
+ QPoint gridPos,
+ const Internal::TerminalCell &cell,
+ QFont &f,
+ QList<SearchHit>::const_iterator &searchIt) const;
+ void paintCells(QPainter &painter, QPaintEvent *event) const;
+ void paintCursor(QPainter &painter) const;
+ void paintPreedit(QPainter &painter) const;
+ bool paintFindMatches(QPainter &painter,
+ QList<SearchHit>::const_iterator &searchIt,
+ const QRectF &cellRect,
+ const QPoint gridPos) const;
+ bool paintSelection(QPainter &painter, const QRectF &cellRect, const QPoint gridPos) const;
+ void paintDebugSelection(QPainter &painter, const Selection &selection) const;
+
+ qreal topMargin() const;
+
+ QPoint viewportToGlobal(QPoint p) const;
+ QPoint globalToViewport(QPoint p) const;
+ QPoint globalToGrid(QPointF p) const;
+ QPointF gridToGlobal(QPoint p, bool bottom = false, bool right = false) const;
+ QRect gridToViewport(QRect rect) const;
+
+ void updateViewport();
+ void updateViewportRect(const QRect &rect);
+
+ int textLineFromPixel(int y) const;
+ std::optional<int> textPosFromPoint(const QTextLayout &textLayout, QPoint p) const;
+
+ std::optional<QTextLayout::FormatRange> selectionToFormatRange(
+ TerminalWidget::Selection selection, const QTextLayout &layout, int rowOffset) const;
+
+ void checkLinkAt(const QPoint &pos);
+
+ struct TextAndOffsets
+ {
+ int start;
+ int end;
+ std::u32string text;
+ };
+
+ TextAndOffsets textAt(const QPoint &pos) const;
+
+ void applySizeChange();
+ void updateScrollBars();
+
+ void flushVTerm(bool force);
+
+ bool setSelection(const std::optional<Selection> &selection, bool scroll = true);
+ QString textFromSelection() const;
+
+ void configBlinkTimer();
+
+ QColor toQColor(std::variant<int, QColor> color) const;
+
+ void updateCopyState();
+
+private:
+ std::unique_ptr<Utils::QtcProcess> m_process;
+ std::unique_ptr<Internal::TerminalSurface> m_surface;
+ std::unique_ptr<ShellIntegration> m_shellIntegration;
+
+ QString m_shellName;
+ Utils::Id m_identifier;
+
+ QFont m_font;
+ QSizeF m_cellSize;
+
+ bool m_ignoreScroll{false};
+
+ QString m_preEditString;
+
+ std::optional<Selection> m_selection;
+ std::optional<LinkSelection> m_linkSelection;
+
+ struct
+ {
+ QPoint start;
+ QPoint end;
+ } m_activeMouseSelect;
+
+ QTimer m_flushDelayTimer;
+
+ QTimer m_scrollTimer;
+ int m_scrollDirection{0};
+
+ std::array<QColor, 20> m_currentColors;
+
+ Utils::Terminal::OpenTerminalParameters m_openParameters;
+
+ std::chrono::system_clock::time_point m_lastFlush;
+ std::chrono::system_clock::time_point m_lastDoubleClick;
+ bool m_selectLineMode{false};
+
+ Internal::Cursor m_cursor;
+ QTimer m_cursorBlinkTimer;
+ bool m_cursorBlinkState{true};
+
+ Utils::FilePath m_cwd;
+ Utils::CommandLine m_currentCommand;
+
+ std::unique_ptr<TerminalSearch> m_search;
+
+ Aggregation::Aggregate *m_aggregate{nullptr};
+ SearchHit m_lastSelectedHit{};
+};
+
+} // namespace Terminal
diff --git a/src/plugins/terminal/tests/colors b/src/plugins/terminal/tests/colors
new file mode 100755
index 00000000000..a1910d45cc7
--- /dev/null
+++ b/src/plugins/terminal/tests/colors
@@ -0,0 +1,112 @@
+#!/usr/bin/python3
+# Source: https://gist.github.com/lilydjwg/fdeaf79e921c2f413f44b6f613f6ad53
+
+from functools import partial
+
+
+def colors16():
+ for bold in [0, 1]:
+ for i in range(30, 38):
+ for j in range(40, 48):
+ print(f'\x1b[{bold};{i};{j}m {bold};{i};{j} |\x1b[0m', end='')
+ print()
+ print()
+
+ for bold in [0, 1]:
+ for i in range(90, 98):
+ for j in range(100, 108):
+ print(f'\x1b[{bold};{i};{j}m {bold};{i};{j} |\x1b[0m', end='')
+ print()
+ print()
+
+
+def color1(c, n=0):
+ print(f'\x1b[{n};38;5;{c}m{c:4}\x1b[0m', end='')
+
+
+def color1_sep(c):
+ if (c - 15) % 18 == 0:
+ print()
+
+
+def color2(c):
+ print(f'\x1b[48;5;{c}m \x1b[0m', end='')
+
+
+def color2_sep(c):
+ if (c - 15) % 36 == 0:
+ print()
+ elif (c - 15) % 6 == 0:
+ print(' ', end='')
+
+
+def colors256(color, sepfunc):
+ for i in range(0, 8):
+ color(i)
+ print()
+ for i in range(8, 16):
+ color(i)
+ print('\n')
+
+ for i in range(16, 232):
+ color(i)
+ sepfunc(i)
+ print()
+
+ for i in range(232, 256):
+ color(i)
+ print('\n')
+
+
+def colors_gradient():
+ s = '/\\' * 40
+ for col in range(0, 77):
+ r = 255 - col * 255 // 76
+ g = col * 510 // 76
+ b = col * 255 // 76
+ if g > 255:
+ g = 510 - g
+ print(
+ f'\x1b[48;2;{r};{g};{b}m\x1b[38;2;{255-r};{255-g};{255-b}m{s[col]}\x1b[0m', end='')
+ print()
+
+
+def other_attributes():
+ for i in range(0, 10):
+ print(f' \x1b[{i}mSGR {i:2}\x1b[m', end=' ')
+ print(' \x1b[53mSGR 53\x1b[m', end=' ') # overline
+ print('\n')
+ # https://askubuntu.com/a/985386/235132
+ for i in range(1, 6):
+ print(f' \x1b[4:{i}mSGR 4:{i}\x1b[m', end=' ')
+ print(' \x1b[21mSGR 21\x1b[m', end=' ')
+
+ print(
+ ' \x1b[4:3m\x1b[58;2;135;0;255mtruecolor underline\x1b[59m\x1b[4:0m', end=' ')
+ print(' \x1b]8;;https://askubuntu.com/a/985386/235132\x1b\\hyperlink\x1b]8;;\x1b\\')
+
+
+if __name__ == '__main__':
+ print('basic 16 colors, foreground & background:\n')
+ colors16()
+
+ print('256 colors:\n')
+ colors256(color1, color1_sep)
+
+ print('256 colors, bold:\n')
+ colors256(partial(color1, n=1), color1_sep)
+
+ print('256 colors, dim:\n')
+ colors256(partial(color1, n=2), color1_sep)
+
+ print('256 colors, bold dim:\n')
+ colors256(partial(color1, n='1;2'), color1_sep)
+
+ print('256 colors, solid background:\n')
+ colors256(color2, color2_sep)
+
+ print('true colors gradient:\n')
+ colors_gradient()
+
+ print('other attributes:\n')
+ other_attributes()
diff --git a/src/plugins/terminal/tests/cursor/bar b/src/plugins/terminal/tests/cursor/bar
new file mode 100755
index 00000000000..a7bd99b55dd
--- /dev/null
+++ b/src/plugins/terminal/tests/cursor/bar
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+echo -e -n "\x1b[\x36 q" # Steady bar
diff --git a/src/plugins/terminal/tests/cursor/blinkbar b/src/plugins/terminal/tests/cursor/blinkbar
new file mode 100755
index 00000000000..0acf6179d9c
--- /dev/null
+++ b/src/plugins/terminal/tests/cursor/blinkbar
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+echo -e -n "\x1b[\x35 q" # Blinking bar
diff --git a/src/plugins/terminal/tests/cursor/blinkblock b/src/plugins/terminal/tests/cursor/blinkblock
new file mode 100755
index 00000000000..c536c83b8b7
--- /dev/null
+++ b/src/plugins/terminal/tests/cursor/blinkblock
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+echo -e -n "\x1b[\x31 q" # Blinking block (default)
diff --git a/src/plugins/terminal/tests/cursor/blinkunderline b/src/plugins/terminal/tests/cursor/blinkunderline
new file mode 100755
index 00000000000..745d9e2a29c
--- /dev/null
+++ b/src/plugins/terminal/tests/cursor/blinkunderline
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+echo -e -n "\x1b[\x33 q" # Blinking underline
diff --git a/src/plugins/terminal/tests/cursor/block b/src/plugins/terminal/tests/cursor/block
new file mode 100755
index 00000000000..421df3b8c5a
--- /dev/null
+++ b/src/plugins/terminal/tests/cursor/block
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+echo -e -n "\x1b[\x32 q" # Steady block
diff --git a/src/plugins/terminal/tests/cursor/underline b/src/plugins/terminal/tests/cursor/underline
new file mode 100755
index 00000000000..7638b5358d0
--- /dev/null
+++ b/src/plugins/terminal/tests/cursor/underline
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+echo -e -n "\x1b[\x34 q" # Steady underline
diff --git a/src/plugins/terminal/tests/decoration b/src/plugins/terminal/tests/decoration
new file mode 100755
index 00000000000..a584d460927
--- /dev/null
+++ b/src/plugins/terminal/tests/decoration
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+printf "\e[1mbold\e[0m\n"
+printf "\e[3mitalic\e[0m\n"
+printf "\e[3m\e[1mbold italic\e[0m\n"
+printf "\e[4munderline (abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ)\e[0m\n"
+printf "\e[9mstrikethrough\e[0m\n"
+printf "\e[31mHello World\e[0m\n"
+printf "\x1B[31mHello World\e[0m\n"
diff --git a/src/plugins/terminal/tests/filenames b/src/plugins/terminal/tests/filenames
new file mode 100755
index 00000000000..6a4e33e3ede
--- /dev/null
+++ b/src/plugins/terminal/tests/filenames
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+
+echo "Home:"
+echo "~/"
+
+FULLPATH=$(readlink -f "$0")
+
+echo "This file:"
+echo "$FULLPATH"
+
+echo "This file, this line:"
+echo "$FULLPATH:11"
+
+echo "This file, this line, this word:"
+echo "$FULLPATH:14:34"
+
+echo "This file, with an error message:"
+echo "$FULLPATH:18:23: error: C++ requires a type specifier for all declarations"
+
+echo "A link: http://google.com"
+echo "Another link: https://www.qt.io"
+echo "Another one: https://codereview.qt-project.org/c/qt-creator/qt-creator/+/464740"
diff --git a/src/plugins/terminal/tests/integration b/src/plugins/terminal/tests/integration
new file mode 100755
index 00000000000..ac17432cb66
--- /dev/null
+++ b/src/plugins/terminal/tests/integration
@@ -0,0 +1,70 @@
+#!/bin/bash
+
+echo "Testing integration response, best start this from a terminal that has no builtin integration"
+echo "e.g. 'sh'"
+echo
+
+echo -e "\033[1m ⎆ Current dir should have changed to '/Some/Dir/Here'\033[0m"
+printf "\033]7;file:///Some/Dir/Here\033\\"
+
+read -p " ⎆ Press enter to continue " -n1 -s
+echo
+echo
+
+echo -e "\033[1m ⎆ Current dir should have changed to '/Some/Other/Dir/Here'\033[0m"
+printf "\033]1337;CurrentDir=/Some/Other/Dir/Here\033\\"
+
+read -p " ⎆ Press enter to continue " -n1 -s
+echo
+echo
+
+
+echo -e "\033[1m ⎆ Current dir should have changed to '/VSCode/dir/with space'\033[0m"
+printf "\033]633P;Cwd=/VSCode/dir/with space\033\\"
+
+read -p " ⎆ Press enter to continue " -n1 -s
+echo
+echo
+
+echo -e "\033[1m ⎆ The current process should have changed to 'test'\033[0m"
+printf "\033]633E;test with arguments\033\\"
+
+read -p " ⎆ Press enter to continue " -n1 -s
+echo
+echo
+
+echo -e "\033[1m ⎆ The current process should have changed to 'test with space'\033[0m"
+printf "\033]633E;'test with space'\033\\"
+
+read -p " ⎆ Press enter to continue " -n1 -s
+echo
+echo
+
+echo -e "\033[1m ⎆ The current process should have changed to 'test with space v2'\033[0m"
+printf "\033]633E;\"test with space v2\"\033\\"
+
+read -p " ⎆ Press enter to continue " -n1 -s
+echo
+echo
+
+echo -e "\033[1m ⎆ The current process should have changed to 'test with space v3'\033[0m"
+printf "\033]633E;\"./test/test with space v3\" -argument\033\\"
+
+read -p " ⎆ Press enter to continue " -n1 -s
+echo
+echo
+
+echo -e "\033[1m ⎆ The current process should have changed to 'cat'\033[0m"
+printf "\033]633E;cat /dev/random | base64 -argument\033\\"
+
+read -p " ⎆ Press enter to continue " -n1 -s
+echo
+echo
+
+echo -e "\033[1m ⎆ The current process should have changed to 'cat me'\033[0m"
+printf "\033]633E;cat\\ me args \033\\"
+
+read -p " ⎆ Press enter to continue " -n1 -s
+echo
+echo
+
diff --git a/src/plugins/texteditor/CMakeLists.txt b/src/plugins/texteditor/CMakeLists.txt
index 01636fe7544..eebe8bf0bed 100644
--- a/src/plugins/texteditor/CMakeLists.txt
+++ b/src/plugins/texteditor/CMakeLists.txt
@@ -70,6 +70,7 @@ add_qtc_plugin(TextEditor
ioutlinewidget.h
linenumberfilter.cpp linenumberfilter.h
marginsettings.cpp marginsettings.h
+ markdowneditor.cpp markdowneditor.h
outlinefactory.cpp outlinefactory.h
plaintexteditorfactory.cpp plaintexteditorfactory.h
quickfix.cpp quickfix.h
diff --git a/src/plugins/texteditor/basehoverhandler.h b/src/plugins/texteditor/basehoverhandler.h
index 9b6d90fd89a..c24ae4a1df5 100644
--- a/src/plugins/texteditor/basehoverhandler.h
+++ b/src/plugins/texteditor/basehoverhandler.h
@@ -39,7 +39,8 @@ protected:
Priority_None = 0,
Priority_Tooltip = 5,
Priority_Help = 10,
- Priority_Diagnostic = 20
+ Priority_Diagnostic = 20,
+ Priority_Suggestion = 40
};
void setPriority(int priority);
int priority() const;
diff --git a/src/plugins/texteditor/codeassist/asyncprocessor.cpp b/src/plugins/texteditor/codeassist/asyncprocessor.cpp
index 90f993a39c7..bd074f04c0d 100644
--- a/src/plugins/texteditor/codeassist/asyncprocessor.cpp
+++ b/src/plugins/texteditor/codeassist/asyncprocessor.cpp
@@ -6,7 +6,7 @@
#include "assistinterface.h"
#include "iassistproposal.h"
-#include <utils/runextensions.h>
+#include <utils/asynctask.h>
namespace TextEditor {
@@ -21,7 +21,7 @@ IAssistProposal *AsyncProcessor::perform()
{
IAssistProposal *result = immediateProposal();
interface()->prepareForAsyncUse();
- m_watcher.setFuture(Utils::runAsync([this] {
+ m_watcher.setFuture(Utils::asyncRun([this] {
interface()->recreateTextDocument();
return performAsync();
}));
diff --git a/src/plugins/texteditor/codeassist/codeassistant.cpp b/src/plugins/texteditor/codeassist/codeassistant.cpp
index 34ba897d8db..b81161dd6a7 100644
--- a/src/plugins/texteditor/codeassist/codeassistant.cpp
+++ b/src/plugins/texteditor/codeassist/codeassistant.cpp
@@ -252,6 +252,14 @@ void CodeAssistantPrivate::displayProposal(IAssistProposal *newProposal, AssistR
return;
}
+ if (m_editorWidget->suggestionVisible()) {
+ if (reason != ExplicitlyInvoked) {
+ destroyContext();
+ return;
+ }
+ m_editorWidget->clearSuggestion();
+ }
+
const QString prefix = m_editorWidget->textAt(basePosition,
m_editorWidget->position() - basePosition);
if (!newProposal->hasItemsToPropose(prefix, reason)) {
@@ -444,6 +452,7 @@ void CodeAssistantPrivate::automaticProposalTimeout()
{
if (isWaitingForProposal()
|| m_editorWidget->multiTextCursor().hasMultipleCursors()
+ || m_editorWidget->suggestionVisible()
|| (isDisplayingProposal() && !m_proposalWidget->isFragile())) {
return;
}
diff --git a/src/plugins/texteditor/formattexteditor.cpp b/src/plugins/texteditor/formattexteditor.cpp
index 6f1139e057b..ae25a574796 100644
--- a/src/plugins/texteditor/formattexteditor.cpp
+++ b/src/plugins/texteditor/formattexteditor.cpp
@@ -10,10 +10,10 @@
#include <coreplugin/messagemanager.h>
+#include <utils/asynctask.h>
#include <utils/differ.h>
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
-#include <utils/runextensions.h>
#include <utils/temporarydirectory.h>
#include <utils/textutils.h>
@@ -324,7 +324,7 @@ void formatEditorAsync(TextEditorWidget *editor, const Command &command, int sta
checkAndApplyTask(watcher->result());
watcher->deleteLater();
});
- watcher->setFuture(Utils::runAsync(&format, FormatTask(editor, doc->filePath(), sd,
+ watcher->setFuture(Utils::asyncRun(&format, FormatTask(editor, doc->filePath(), sd,
command, startPos, endPos)));
}
diff --git a/src/plugins/texteditor/markdowneditor.cpp b/src/plugins/texteditor/markdowneditor.cpp
new file mode 100644
index 00000000000..4041a0069e9
--- /dev/null
+++ b/src/plugins/texteditor/markdowneditor.cpp
@@ -0,0 +1,103 @@
+// Copyright (C) 2023 Tasuku Suzuki
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "markdowneditor.h"
+
+#include "textdocument.h"
+#include "texteditor.h"
+#include "texteditortr.h"
+
+#include <coreplugin/icore.h>
+#include <coreplugin/coreconstants.h>
+#include <coreplugin/coreplugintr.h>
+#include <coreplugin/minisplitter.h>
+
+#include <QHBoxLayout>
+#include <QScrollBar>
+#include <QTextBrowser>
+#include <QToolButton>
+
+namespace TextEditor::Internal {
+
+const char MARKDOWNVIEWER_ID[] = "Editors.MarkdownViewer";
+const char MARKDOWNVIEWER_MIME_TYPE[] = "text/markdown";
+
+class MarkdownEditor : public Core::IEditor
+{
+public:
+ MarkdownEditor()
+ : m_document(new TextDocument(MARKDOWNVIEWER_ID))
+ {
+ m_document->setMimeType(MARKDOWNVIEWER_MIME_TYPE);
+
+ // Left side
+ auto browser = new QTextBrowser(&m_widget);
+ browser->setOpenExternalLinks(true);
+ browser->setFrameShape(QFrame::NoFrame);
+
+ // Right side (hidable)
+ auto editor = new TextEditorWidget(&m_widget);
+ editor->setTextDocument(m_document);
+ editor->setupGenericHighlighter();
+ editor->setMarksVisible(false);
+
+ setContext(Core::Context(MARKDOWNVIEWER_ID));
+ setWidget(&m_widget);
+
+ auto toggleEditorVisible = new QToolButton;
+ toggleEditorVisible->setText(Tr::tr("Hide Editor"));
+ toggleEditorVisible->setCheckable(true);
+ toggleEditorVisible->setChecked(true);
+
+ auto layout = new QHBoxLayout(&m_toolbar);
+ layout->setSpacing(0);
+ layout->setContentsMargins(0, 0, 0, 0);
+ layout->addStretch();
+ layout->addWidget(toggleEditorVisible);
+
+ connect(m_document.data(), &TextDocument::mimeTypeChanged,
+ m_document.data(), &TextDocument::changed);
+
+ connect(toggleEditorVisible, &QToolButton::toggled,
+ editor, [editor, toggleEditorVisible](bool editorVisible) {
+ if (editor->isVisible() == editorVisible)
+ return;
+ editor->setVisible(editorVisible);
+ toggleEditorVisible->setText(editorVisible ? Tr::tr("Hide Editor") : Tr::tr("Show Editor"));
+ });
+
+ connect(m_document->document(), &QTextDocument::contentsChanged, this, [this, browser] {
+ QHash<QScrollBar *, int> positions;
+ const auto scrollBars = browser->findChildren<QScrollBar *>();
+
+ // save scroll positions
+ for (QScrollBar *scrollBar : scrollBars)
+ positions.insert(scrollBar, scrollBar->value());
+
+ browser->setMarkdown(m_document->plainText());
+
+ // restore scroll positions
+ for (QScrollBar *scrollBar : scrollBars)
+ scrollBar->setValue(positions.value(scrollBar));
+ });
+ }
+
+ QWidget *toolBar() override { return &m_toolbar; }
+
+ Core::IDocument *document() const override { return m_document.data(); }
+
+private:
+ Core::MiniSplitter m_widget;
+ TextDocumentPtr m_document;
+ QWidget m_toolbar;
+};
+
+MarkdownEditorFactory::MarkdownEditorFactory()
+{
+ setId(MARKDOWNVIEWER_ID);
+ setDisplayName(::Core::Tr::tr("Markdown Viewer"));
+ addMimeType(MARKDOWNVIEWER_MIME_TYPE);
+ setEditorCreator([] { return new MarkdownEditor; });
+}
+
+} // TextEditor::Internal
diff --git a/src/plugins/texteditor/markdowneditor.h b/src/plugins/texteditor/markdowneditor.h
new file mode 100644
index 00000000000..21944657bc4
--- /dev/null
+++ b/src/plugins/texteditor/markdowneditor.h
@@ -0,0 +1,16 @@
+// Copyright (C) 2023 Tasuku Suzuki
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <coreplugin/editormanager/ieditorfactory.h>
+
+namespace TextEditor::Internal {
+
+class MarkdownEditorFactory final : public Core::IEditorFactory
+{
+public:
+ MarkdownEditorFactory();
+};
+
+} // TextEditor::Internal
diff --git a/src/plugins/texteditor/quickfix.h b/src/plugins/texteditor/quickfix.h
index 6469bd7d8bc..e456230ac20 100644
--- a/src/plugins/texteditor/quickfix.h
+++ b/src/plugins/texteditor/quickfix.h
@@ -35,8 +35,7 @@ public:
virtual ~QuickFixOperation();
/*!
- \returns The priority for this quick-fix. See the QuickFixCollector for more
- information.
+ Returns The priority for this quick-fix. See the QuickFixCollector for more information.
*/
virtual int priority() const;
@@ -44,8 +43,7 @@ public:
void setPriority(int priority);
/*!
- \returns The description for this quick-fix. This description is shown to the
- user.
+ Returns The description for this quick-fix. This description is shown to the user.
*/
virtual QString description() const;
diff --git a/src/plugins/texteditor/textdocument.cpp b/src/plugins/texteditor/textdocument.cpp
index 61c3b319b36..4a7e80f52ce 100644
--- a/src/plugins/texteditor/textdocument.cpp
+++ b/src/plugins/texteditor/textdocument.cpp
@@ -373,6 +373,16 @@ QAction *TextDocument::createDiffAgainstCurrentFileAction(
return diffAction;
}
+void TextDocument::insertSuggestion(std::unique_ptr<TextSuggestion> &&suggestion)
+{
+ QTextCursor cursor(&d->m_document);
+ cursor.setPosition(suggestion->position());
+ const QTextBlock block = cursor.block();
+ TextDocumentLayout::userData(block)->insertSuggestion(std::move(suggestion));
+ TextDocumentLayout::updateSuggestionFormats(block, fontSettings());
+ updateLayout();
+}
+
#ifdef WITH_TESTS
void TextDocument::setSilentReload()
{
@@ -419,6 +429,12 @@ IAssistProvider *TextDocument::quickFixAssistProvider() const
void TextDocument::applyFontSettings()
{
d->m_fontSettingsNeedsApply = false;
+ QTextBlock block = document()->firstBlock();
+ while (block.isValid()) {
+ TextDocumentLayout::updateSuggestionFormats(block, fontSettings());
+ block = block.next();
+ }
+ updateLayout();
if (d->m_highlighter) {
d->m_highlighter->setFontSettings(d->m_fontSettings);
d->m_highlighter->rehighlight();
diff --git a/src/plugins/texteditor/textdocument.h b/src/plugins/texteditor/textdocument.h
index dc52e1e9a8e..d1f686f6a0c 100644
--- a/src/plugins/texteditor/textdocument.h
+++ b/src/plugins/texteditor/textdocument.h
@@ -37,6 +37,7 @@ class SyntaxHighlighter;
class TabSettings;
class TextDocumentPrivate;
class TextMark;
+class TextSuggestion;
class TypingSettings;
using TextMarks = QList<TextMark *>;
@@ -144,6 +145,9 @@ public:
static QAction *createDiffAgainstCurrentFileAction(QObject *parent,
const std::function<Utils::FilePath()> &filePath);
+ void insertSuggestion(const QString &text, const QTextCursor &cursor);
+ void insertSuggestion(std::unique_ptr<TextSuggestion> &&suggestion);
+
#ifdef WITH_TESTS
void setSilentReload();
#endif
diff --git a/src/plugins/texteditor/textdocumentlayout.cpp b/src/plugins/texteditor/textdocumentlayout.cpp
index 1f26530dbb0..a80a1b77515 100644
--- a/src/plugins/texteditor/textdocumentlayout.cpp
+++ b/src/plugins/texteditor/textdocumentlayout.cpp
@@ -345,6 +345,21 @@ void TextBlockUserData::setCodeFormatterData(CodeFormatterData *data)
m_codeFormatterData = data;
}
+void TextBlockUserData::insertSuggestion(std::unique_ptr<TextSuggestion> &&suggestion)
+{
+ m_suggestion = std::move(suggestion);
+}
+
+TextSuggestion *TextBlockUserData::suggestion() const
+{
+ return m_suggestion.get();
+}
+
+void TextBlockUserData::clearSuggestion()
+{
+ m_suggestion.release();
+}
+
void TextBlockUserData::addMark(TextMark *mark)
{
int i = 0;
@@ -355,7 +370,6 @@ void TextBlockUserData::addMark(TextMark *mark)
m_marks.insert(i, mark);
}
-
TextDocumentLayout::TextDocumentLayout(QTextDocument *doc)
: QPlainTextDocumentLayout(doc)
{}
@@ -519,6 +533,81 @@ QByteArray TextDocumentLayout::expectedRawStringSuffix(const QTextBlock &block)
return {};
}
+TextSuggestion *TextDocumentLayout::suggestion(const QTextBlock &block)
+{
+ if (TextBlockUserData *userData = textUserData(block))
+ return userData->suggestion();
+ return nullptr;
+}
+
+void TextDocumentLayout::updateSuggestionFormats(const QTextBlock &block,
+ const FontSettings &fontSettings)
+{
+ if (TextSuggestion *suggestion = TextDocumentLayout::suggestion(block)) {
+ QTextDocument *suggestionDoc = suggestion->document();
+ const QTextCharFormat replacementFormat = fontSettings.toTextCharFormat(
+ TextStyles{C_TEXT, {C_DISABLED_CODE}});
+ QList<QTextLayout::FormatRange> formats = block.layout()->formats();
+ QTextCursor cursor(suggestionDoc);
+ cursor.select(QTextCursor::Document);
+ cursor.setCharFormat(fontSettings.toTextCharFormat(C_TEXT));
+ const int position = suggestion->currentPosition() - block.position();
+ cursor.setPosition(position);
+ const QString trailingText = block.text().mid(position);
+ if (!trailingText.isEmpty()) {
+ const int trailingIndex = suggestionDoc->firstBlock().text().indexOf(trailingText,
+ position);
+ if (trailingIndex >= 0) {
+ cursor.setPosition(trailingIndex, QTextCursor::KeepAnchor);
+ cursor.setCharFormat(replacementFormat);
+ cursor.setPosition(trailingIndex + trailingText.size());
+ const int length = std::max(trailingIndex - position, 0);
+ if (length) {
+ // we have a replacement in the middle of the line adjust all formats that are
+ // behind the replacement
+ QTextLayout::FormatRange rest;
+ rest.start = -1;
+ for (QTextLayout::FormatRange &range : formats) {
+ if (range.start >= position) {
+ range.start += length;
+ } else if (range.start + range.length > position) {
+ // the format range starts before and ends after the position so we need to
+ // split the format into before and after the suggestion format ranges
+ rest.start = trailingIndex;
+ rest.length = range.length - (position - range.start);
+ rest.format = range.format;
+ range.length = position - range.start;
+ }
+ }
+ if (rest.start >= 0)
+ formats += rest;
+ }
+ }
+ }
+ cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
+ cursor.setCharFormat(replacementFormat);
+ suggestionDoc->firstBlock().layout()->setFormats(formats);
+ }
+}
+
+bool TextDocumentLayout::updateSuggestion(const QTextBlock &block,
+ int position,
+ const FontSettings &fontSettings)
+{
+ if (TextSuggestion *suggestion = TextDocumentLayout::suggestion(block)) {
+ auto positionInBlock = position - block.position();
+ const QString start = block.text().left(positionInBlock);
+ const QString end = block.text().mid(positionInBlock);
+ const QString replacement = suggestion->document()->firstBlock().text();
+ if (replacement.startsWith(start) && replacement.indexOf(end, start.size()) >= 0) {
+ suggestion->setCurrentPosition(position);
+ TextDocumentLayout::updateSuggestionFormats(block, fontSettings);
+ return true;
+ }
+ }
+ return false;
+}
+
void TextDocumentLayout::requestExtraAreaUpdate()
{
emit updateExtraArea();
@@ -632,8 +721,30 @@ void TextDocumentLayout::requestUpdateNow()
requestUpdate();
}
+static QRectF replacementBoundingRect(const QTextDocument *replacement)
+{
+ QTC_ASSERT(replacement, return {});
+ auto *layout = static_cast<QPlainTextDocumentLayout *>(replacement->documentLayout());
+ QRectF boundingRect;
+ QTextBlock block = replacement->firstBlock();
+ while (block.isValid()) {
+ const QRectF blockBoundingRect = layout->blockBoundingRect(block);
+ boundingRect.setWidth(std::max(boundingRect.width(), blockBoundingRect.width()));
+ boundingRect.setHeight(boundingRect.height() + blockBoundingRect.height());
+ block = block.next();
+ }
+ return boundingRect;
+}
+
QRectF TextDocumentLayout::blockBoundingRect(const QTextBlock &block) const
{
+ if (TextSuggestion *suggestion = TextDocumentLayout::suggestion(block)) {
+ // since multiple code paths expects that we have a valid block layout after requesting the
+ // block bounding rect explicitly create that layout here
+ ensureBlockLayout(block);
+ return replacementBoundingRect(suggestion->document());
+ }
+
QRectF boundingRect = QPlainTextDocumentLayout::blockBoundingRect(block);
if (TextEditorSettings::fontSettings().relativeLineSpacing() != 100) {
@@ -722,4 +833,10 @@ void insertSorted(Parentheses &list, const Parenthesis &elem)
list.insert(it, elem);
}
+TextSuggestion::TextSuggestion()
+{
+ m_replacementDocument.setDocumentLayout(new TextDocumentLayout(&m_replacementDocument));
+ m_replacementDocument.setDocumentMargin(0);
+}
+
} // namespace TextEditor
diff --git a/src/plugins/texteditor/textdocumentlayout.h b/src/plugins/texteditor/textdocumentlayout.h
index ba31398db7b..d4020f721c2 100644
--- a/src/plugins/texteditor/textdocumentlayout.h
+++ b/src/plugins/texteditor/textdocumentlayout.h
@@ -42,6 +42,24 @@ public:
virtual ~CodeFormatterData();
};
+class TEXTEDITOR_EXPORT TextSuggestion
+{
+public:
+ TextSuggestion();
+ virtual bool apply() = 0;
+ virtual void reset() = 0;
+ virtual int position() = 0;
+
+ int currentPosition() const { return m_currentPosition; }
+ void setCurrentPosition(int position) { m_currentPosition = position; }
+
+ QTextDocument *document() { return &m_replacementDocument; }
+
+private:
+ QTextDocument m_replacementDocument;
+ int m_currentPosition = -1;
+};
+
class TEXTEDITOR_EXPORT TextBlockUserData : public QTextBlockUserData
{
public:
@@ -126,6 +144,10 @@ public:
QByteArray expectedRawStringSuffix() { return m_expectedRawStringSuffix; }
void setExpectedRawStringSuffix(const QByteArray &suffix) { m_expectedRawStringSuffix = suffix; }
+ void insertSuggestion(std::unique_ptr<TextSuggestion> &&suggestion);
+ TextSuggestion *suggestion() const;
+ void clearSuggestion();
+
private:
TextMarks m_marks;
int m_foldingIndent : 16;
@@ -139,9 +161,10 @@ private:
CodeFormatterData *m_codeFormatterData;
KSyntaxHighlighting::State m_syntaxState;
QByteArray m_expectedRawStringSuffix; // A bit C++-specific, but let's be pragmatic.
+ std::unique_ptr<QTextDocument> m_replacement;
+ std::unique_ptr<TextSuggestion> m_suggestion;
};
-
class TEXTEDITOR_EXPORT TextDocumentLayout : public QPlainTextDocumentLayout
{
Q_OBJECT
@@ -172,6 +195,12 @@ public:
static void setFolded(const QTextBlock &block, bool folded);
static void setExpectedRawStringSuffix(const QTextBlock &block, const QByteArray &suffix);
static QByteArray expectedRawStringSuffix(const QTextBlock &block);
+ static TextSuggestion *suggestion(const QTextBlock &block);
+ static void updateSuggestionFormats(const QTextBlock &block,
+ const FontSettings &fontSettings);
+ static bool updateSuggestion(const QTextBlock &block,
+ int position,
+ const FontSettings &fontSettings);
class TEXTEDITOR_EXPORT FoldValidator
{
diff --git a/src/plugins/texteditor/texteditor.cpp b/src/plugins/texteditor/texteditor.cpp
index 9089f592351..e61262094fc 100644
--- a/src/plugins/texteditor/texteditor.cpp
+++ b/src/plugins/texteditor/texteditor.cpp
@@ -819,6 +819,11 @@ public:
QStack<UndoMultiCursor> m_undoCursorStack;
QList<int> m_visualIndentCache;
int m_visualIndentOffset = 0;
+
+ void insertSuggestion(std::unique_ptr<TextSuggestion> &&suggestion);
+ void updateSuggestion();
+ void clearCurrentSuggestion();
+ QTextBlock m_suggestionBlock;
};
class TextEditorWidgetFind : public BaseTextFind
@@ -1646,6 +1651,39 @@ void TextEditorWidgetPrivate::handleMoveBlockSelection(QTextCursor::MoveOperatio
q->setMultiTextCursor(MultiTextCursor(cursors));
}
+void TextEditorWidgetPrivate::insertSuggestion(std::unique_ptr<TextSuggestion> &&suggestion)
+{
+ clearCurrentSuggestion();
+ auto cursor = q->textCursor();
+ cursor.setPosition(suggestion->position());
+ m_suggestionBlock = cursor.block();
+ m_document->insertSuggestion(std::move(suggestion));
+}
+
+void TextEditorWidgetPrivate::updateSuggestion()
+{
+ if (!m_suggestionBlock.isValid())
+ return;
+ if (m_cursors.mainCursor().block() != m_suggestionBlock) {
+ clearCurrentSuggestion();
+ } else {
+ if (!TextDocumentLayout::updateSuggestion(m_suggestionBlock,
+ m_cursors.mainCursor().position(),
+ m_document->fontSettings())) {
+ clearCurrentSuggestion();
+ }
+ }
+}
+
+void TextEditorWidgetPrivate::clearCurrentSuggestion()
+{
+ if (TextBlockUserData *userData = TextDocumentLayout::textUserData(m_suggestionBlock)) {
+ userData->clearSuggestion();
+ m_document->updateLayout();
+ }
+ m_suggestionBlock = QTextBlock();
+}
+
void TextEditorWidget::selectEncoding()
{
TextDocument *doc = d->m_document.data();
@@ -1828,6 +1866,8 @@ TextEditorWidget *TextEditorWidget::fromEditor(const IEditor *editor)
void TextEditorWidgetPrivate::editorContentsChange(int position, int charsRemoved, int charsAdded)
{
+ updateSuggestion();
+
if (m_bracketsAnimator)
m_bracketsAnimator->finish();
@@ -2308,6 +2348,11 @@ void TextEditorWidget::renameSymbolUnderCursor()
emit requestRename(textCursor());
}
+void TextEditorWidget::openCallHierarchy()
+{
+ emit requestCallHierarchy(textCursor());
+}
+
void TextEditorWidget::abortAssist()
{
d->m_codeAssistant.destroyContext();
@@ -2506,6 +2551,11 @@ void TextEditorWidget::keyPressEvent(QKeyEvent *e)
d->m_maybeFakeTooltipEvent = false;
if (e->key() == Qt::Key_Escape ) {
TextEditorWidgetFind::cancelCurrentSelectAll();
+ if (d->m_suggestionBlock.isValid()) {
+ d->clearCurrentSuggestion();
+ e->accept();
+ return;
+ }
if (d->m_snippetOverlay->isVisible()) {
e->accept();
d->m_snippetOverlay->accept();
@@ -2645,6 +2695,12 @@ void TextEditorWidget::keyPressEvent(QKeyEvent *e)
return;
}
QTextCursor cursor = textCursor();
+ if (TextSuggestion *suggestion = TextDocumentLayout::suggestion(d->m_suggestionBlock)) {
+ suggestion->apply();
+ d->clearCurrentSuggestion();
+ e->accept();
+ return;
+ }
if (d->m_skipAutoCompletedText && e->key() == Qt::Key_Tab) {
bool skippedAutoCompletedText = false;
while (!d->m_autoCompleteHighlightPos.isEmpty()
@@ -2684,6 +2740,8 @@ void TextEditorWidget::keyPressEvent(QKeyEvent *e)
| Qt::AltModifier
| Qt::MetaModifier)) == Qt::NoModifier) {
e->accept();
+ if (d->m_suggestionBlock.isValid())
+ d->clearCurrentSuggestion();
if (cursor.hasSelection()) {
cursor.removeSelectedText();
setMultiTextCursor(cursor);
@@ -2834,13 +2892,13 @@ void TextEditorWidget::keyPressEvent(QKeyEvent *e)
if (!autoText.isEmpty())
cursor.setPosition(autoText.length() == 1 ? cursor.position() : cursor.anchor());
+ setTextCursor(cursor);
+
if (doEditBlock) {
cursor.endEditBlock();
if (cursorWithinSnippet)
d->m_snippetOverlay->updateEquivalentSelections(textCursor());
}
-
- setTextCursor(cursor);
}
if (!ro && e->key() == Qt::Key_Delete && d->m_parenthesesMatchingEnabled)
@@ -3105,7 +3163,9 @@ bool TextEditorWidget::event(QEvent *e)
case QEvent::ShortcutOverride: {
auto ke = static_cast<QKeyEvent *>(e);
if (ke->key() == Qt::Key_Escape
- && (d->m_snippetOverlay->isVisible() || multiTextCursor().hasMultipleCursors())) {
+ && (d->m_snippetOverlay->isVisible()
+ || multiTextCursor().hasMultipleCursors()
+ || d->m_suggestionBlock.isValid())) {
e->accept();
} else {
// hack copied from QInputControl::isCommonTextEditShortcut
@@ -3676,7 +3736,10 @@ bool TextEditorWidget::viewportEvent(QEvent *event)
// Only handle tool tip for text cursor if mouse is within the block for the text cursor,
// and not if the mouse is e.g. in the empty space behind a short line.
if (line.isValid()) {
- if (pos.x() <= blockBoundingGeometry(block).left() + line.naturalTextRect().right()) {
+ const QRectF blockGeometry = blockBoundingGeometry(block);
+ const int width = block == d->m_suggestionBlock ? blockGeometry.width()
+ : line.naturalTextRect().right();
+ if (pos.x() <= blockGeometry.left() + width) {
d->processTooltipRequest(tc);
return true;
} else if (d->processAnnotaionTooltipRequest(block, pos)) {
@@ -3999,7 +4062,13 @@ static TextMarks availableMarks(const TextMarks &marks,
QRectF TextEditorWidgetPrivate::getLastLineLineRect(const QTextBlock &block)
{
- const QTextLayout *layout = block.layout();
+ QTextLayout *layout = nullptr;
+ if (TextSuggestion *suggestion = TextDocumentLayout::suggestion(block))
+ layout = suggestion->document()->firstBlock().layout();
+ else
+ layout = block.layout();
+
+ QTC_ASSERT(layout, layout = block.layout());
const int lineCount = layout->lineCount();
if (lineCount < 1)
return {};
@@ -4394,15 +4463,28 @@ void TextEditorWidgetPrivate::paintAdditionalVisualWhitespaces(PaintEventData &d
visualArrow);
}
if (!nextBlockIsValid) { // paint EOF symbol
- QTextLine line = layout->lineAt(lineCount-1);
+ if (TextSuggestion *suggestion = TextDocumentLayout::suggestion(data.block)) {
+ const QTextBlock lastReplacementBlock = suggestion->document()->lastBlock();
+ for (QTextBlock block = suggestion->document()->firstBlock();
+ block != lastReplacementBlock && block.isValid();
+ block = block.next()) {
+ top += suggestion->document()
+ ->documentLayout()
+ ->blockBoundingRect(block)
+ .height();
+ }
+ layout = lastReplacementBlock.layout();
+ lineCount = layout->lineCount();
+ }
+ QTextLine line = layout->lineAt(lineCount - 1);
QRectF lineRect = line.naturalTextRect().translated(data.offset.x(), top);
int h = 4;
lineRect.adjust(0, 0, -1, -1);
QPainterPath path;
- QPointF pos(lineRect.topRight() + QPointF(h+4, line.ascent()));
+ QPointF pos(lineRect.topRight() + QPointF(h + 4, line.ascent()));
path.moveTo(pos);
path.lineTo(pos + QPointF(-h, -h));
- path.lineTo(pos + QPointF(0, -2*h));
+ path.lineTo(pos + QPointF(0, -2 * h));
path.lineTo(pos + QPointF(h, -h));
path.closeSubpath();
painter.setBrush(painter.pen().color());
@@ -4649,6 +4731,21 @@ void TextEditorWidgetPrivate::setupSelections(const PaintEventData &data,
PaintEventBlockData &blockData) const
{
QVector<QTextLayout::FormatRange> prioritySelections;
+
+ int deltaPos = -1;
+ int delta = 0;
+
+ if (TextSuggestion *suggestion = TextDocumentLayout::suggestion(data.block)) {
+ deltaPos = suggestion->currentPosition() - data.block.position();
+ const QString trailingText = data.block.text().mid(deltaPos);
+ if (!trailingText.isEmpty()) {
+ const int trailingIndex
+ = suggestion->document()->firstBlock().text().indexOf(trailingText, deltaPos);
+ if (trailingIndex >= 0)
+ delta = std::max(trailingIndex - deltaPos, 0);
+ }
+ }
+
for (int i = 0; i < data.context.selections.size(); ++i) {
const QAbstractTextDocumentLayout::Selection &range = data.context.selections.at(i);
const int selStart = range.cursor.selectionStart() - blockData.position;
@@ -4659,6 +4756,22 @@ void TextEditorWidgetPrivate::setupSelections(const PaintEventData &data,
o.start = selStart;
o.length = selEnd - selStart;
o.format = range.format;
+ QTextLayout::FormatRange rest;
+ rest.start = -1;
+ if (deltaPos >= 0 && delta != 0) {
+ if (o.start >= deltaPos) {
+ o.start += delta;
+ } else if (o.start + o.length > deltaPos) {
+ // the format range starts before and ends after the position so we need to
+ // split the format into before and after the suggestion format ranges
+ rest.start = deltaPos + delta;
+ rest.length = o.length - (deltaPos - o.start);
+ rest.format = o.format;
+ o.length = deltaPos - o.start;
+ }
+ }
+
+ o.format = range.format;
if (data.textCursor.hasSelection() && data.textCursor == range.cursor
&& data.textCursor.anchor() == range.cursor.anchor()) {
const QTextCharFormat selectionFormat = data.fontSettings.toTextCharFormat(C_SELECTION);
@@ -4670,10 +4783,15 @@ void TextEditorWidgetPrivate::setupSelections(const PaintEventData &data,
|| (o.format.foreground().style() == Qt::NoBrush
&& o.format.underlineStyle() != QTextCharFormat::NoUnderline
&& o.format.background() == Qt::NoBrush)) {
- if (q->selectionVisible(data.block.blockNumber()))
+ if (q->selectionVisible(data.block.blockNumber())) {
prioritySelections.append(o);
+ if (rest.start >= 0)
+ prioritySelections.append(rest);
+ }
} else {
blockData.selections.append(o);
+ if (rest.start >= 0)
+ blockData.selections.append(rest);
}
}
}
@@ -4784,6 +4902,7 @@ void TextEditorWidget::paintEvent(QPaintEvent *e)
if (blockData.boundingRect.bottom() >= data.eventRect.top()
&& blockData.boundingRect.top() <= data.eventRect.bottom()) {
+ data.documentLayout->ensureBlockLayout(data.block);
d->setupBlockLayout(data, painter, blockData);
blockData.position = data.block.position();
blockData.length = data.block.length();
@@ -4868,6 +4987,27 @@ void TextEditorWidget::paintBlock(QPainter *painter,
const QVector<QTextLayout::FormatRange> &selections,
const QRect &clipRect) const
{
+ if (TextSuggestion *suggestion = TextDocumentLayout::suggestion(block)) {
+ QTextBlock suggestionBlock = suggestion->document()->firstBlock();
+ QPointF suggestionOffset = offset;
+ suggestionOffset.rx() += document()->documentMargin();
+ while (suggestionBlock.isValid()) {
+ const QVector<QTextLayout::FormatRange> blockSelections
+ = suggestionBlock.blockNumber() == 0 ? selections
+ : QVector<QTextLayout::FormatRange>{};
+ suggestionBlock.layout()->draw(painter,
+ suggestionOffset,
+ blockSelections,
+ clipRect);
+ suggestionOffset.ry() += suggestion->document()
+ ->documentLayout()
+ ->blockBoundingRect(suggestionBlock)
+ .height();
+ suggestionBlock = suggestionBlock.next();
+ }
+ return;
+ }
+
block.layout()->draw(painter, offset, selections, clipRect);
}
@@ -5407,6 +5547,7 @@ void TextEditorWidget::slotCursorPositionChanged()
setMultiTextCursor(cursor);
d->updateCursorSelections();
d->updateHighlights();
+ d->updateSuggestion();
}
void TextEditorWidgetPrivate::updateHighlights()
@@ -5842,8 +5983,30 @@ void TextEditorWidget::addHoverHandler(BaseHoverHandler *handler)
void TextEditorWidget::removeHoverHandler(BaseHoverHandler *handler)
{
- d->m_hoverHandlers.removeAll(handler);
- d->m_hoverHandlerRunner.handlerRemoved(handler);
+ if (d->m_hoverHandlers.removeAll(handler) > 0)
+ d->m_hoverHandlerRunner.handlerRemoved(handler);
+}
+
+void TextEditorWidget::insertSuggestion(std::unique_ptr<TextSuggestion> &&suggestion)
+{
+ d->insertSuggestion(std::move(suggestion));
+}
+
+void TextEditorWidget::clearSuggestion()
+{
+ d->clearCurrentSuggestion();
+}
+
+TextSuggestion *TextEditorWidget::currentSuggestion() const
+{
+ if (d->m_suggestionBlock.isValid())
+ return TextDocumentLayout::suggestion(d->m_suggestionBlock);
+ return nullptr;
+}
+
+bool TextEditorWidget::suggestionVisible() const
+{
+ return currentSuggestion();
}
#ifdef WITH_TESTS
@@ -6639,10 +6802,11 @@ MultiTextCursor TextEditorWidget::multiTextCursor() const
void TextEditorWidget::setMultiTextCursor(const Utils::MultiTextCursor &cursor)
{
+ if (cursor == d->m_cursors)
+ return;
+
const MultiTextCursor oldCursor = d->m_cursors;
const_cast<MultiTextCursor &>(d->m_cursors) = cursor;
- if (oldCursor == d->m_cursors)
- return;
doSetTextCursor(d->m_cursors.mainCursor(), /*keepMultiSelection*/ true);
QRect updateRect = d->cursorUpdateRect(oldCursor);
if (d->m_highlightCurrentLine)
@@ -8068,6 +8232,11 @@ void TextEditorWidget::appendStandardContextMenuActions(QMenu *menu)
if (!menu->actions().contains(findUsage))
menu->addAction(findUsage);
}
+ if (optionalActions() & TextEditorActionHandler::CallHierarchy) {
+ const auto callHierarchy = ActionManager::command(Constants::OPEN_CALL_HIERARCHY)->action();
+ if (!menu->actions().contains(callHierarchy))
+ menu->addAction(callHierarchy);
+ }
menu->addSeparator();
appendMenuActionsFromContext(menu, Constants::M_STANDARDCONTEXTMENU);
diff --git a/src/plugins/texteditor/texteditor.h b/src/plugins/texteditor/texteditor.h
index dc492eca21b..56308d796ed 100644
--- a/src/plugins/texteditor/texteditor.h
+++ b/src/plugins/texteditor/texteditor.h
@@ -42,15 +42,16 @@ class HighlightScrollBarController;
}
namespace TextEditor {
-class TextDocument;
-class TextMark;
-class BaseHoverHandler;
-class RefactorOverlay;
-class SyntaxHighlighter;
class AssistInterface;
+class BaseHoverHandler;
+class CompletionAssistProvider;
class IAssistProvider;
class ICodeStylePreferences;
-class CompletionAssistProvider;
+class RefactorOverlay;
+class SyntaxHighlighter;
+class TextDocument;
+class TextMark;
+class TextSuggestion;
using RefactorMarkers = QList<RefactorMarker>;
using TextMarks = QList<TextMark *>;
@@ -437,6 +438,7 @@ public:
virtual void findUsages();
virtual void renameSymbolUnderCursor();
+ virtual void openCallHierarchy();
/// Abort code assistant if it is running.
void abortAssist();
@@ -469,6 +471,11 @@ public:
void addHoverHandler(BaseHoverHandler *handler);
void removeHoverHandler(BaseHoverHandler *handler);
+ void insertSuggestion(std::unique_ptr<TextSuggestion> &&suggestion);
+ void clearSuggestion();
+ TextSuggestion *currentSuggestion() const;
+ bool suggestionVisible() const;
+
#ifdef WITH_TESTS
void processTooltipRequest(const QTextCursor &c);
#endif
@@ -483,6 +490,7 @@ signals:
bool resolveTarget, bool inNextSplit);
void requestUsages(const QTextCursor &cursor);
void requestRename(const QTextCursor &cursor);
+ void requestCallHierarchy(const QTextCursor &cursor);
void optionalActionMaskChanged();
void toolbarOutlineChanged(QWidget *newOutline);
diff --git a/src/plugins/texteditor/texteditor.qbs b/src/plugins/texteditor/texteditor.qbs
index 971ef31faee..1b94811a563 100644
--- a/src/plugins/texteditor/texteditor.qbs
+++ b/src/plugins/texteditor/texteditor.qbs
@@ -94,6 +94,8 @@ Project {
"linenumberfilter.h",
"marginsettings.cpp",
"marginsettings.h",
+ "markdowneditor.cpp",
+ "markdowneditor.h",
"outlinefactory.cpp",
"outlinefactory.h",
"plaintexteditorfactory.cpp",
@@ -220,9 +222,7 @@ Project {
]
}
- Group {
- name: "Tests"
- condition: qtc.testsEnabled
+ QtcTestFiles {
files: [
"texteditor_test.cpp",
]
diff --git a/src/plugins/texteditor/texteditoractionhandler.cpp b/src/plugins/texteditor/texteditoractionhandler.cpp
index 25116729c99..00ed94a5ee7 100644
--- a/src/plugins/texteditor/texteditoractionhandler.cpp
+++ b/src/plugins/texteditor/texteditoractionhandler.cpp
@@ -116,6 +116,7 @@ public:
QAction *m_followSymbolAction = nullptr;
QAction *m_followSymbolInNextSplitAction = nullptr;
QAction *m_findUsageAction = nullptr;
+ QAction *m_openCallHierarchyAction = nullptr;
QAction *m_renameSymbolAction = nullptr;
QAction *m_jumpToFileAction = nullptr;
QAction *m_jumpToFileInNextSplitAction = nullptr;
@@ -228,6 +229,8 @@ void TextEditorActionHandlerPrivate::createActions()
m_jumpToFileInNextSplitAction = registerAction(JUMP_TO_FILE_UNDER_CURSOR_IN_NEXT_SPLIT,
[] (TextEditorWidget *w) { w->openLinkUnderCursorInNextSplit(); }, true, Tr::tr("Jump to File Under Cursor in Next Split"),
QKeySequence(Utils::HostOsInfo::isMacHost() ? Tr::tr("Meta+E, F2") : Tr::tr("Ctrl+E, F2")).toString());
+ m_openCallHierarchyAction = registerAction(OPEN_CALL_HIERARCHY,
+ [] (TextEditorWidget *w) { w->openCallHierarchy(); }, true, Tr::tr("Open Call Hierarchy"));
registerAction(VIEW_PAGE_UP,
[] (TextEditorWidget *w) { w->viewPageUp(); }, true, Tr::tr("Move the View a Page Up and Keep the Cursor Position"),
@@ -484,6 +487,8 @@ void TextEditorActionHandlerPrivate::updateOptionalActions()
optionalActions & TextEditorActionHandler::UnCollapseAll);
m_renameSymbolAction->setEnabled(
optionalActions & TextEditorActionHandler::RenameSymbol);
+ m_openCallHierarchyAction->setEnabled(
+ optionalActions & TextEditorActionHandler::CallHierarchy);
bool formatEnabled = (optionalActions & TextEditorActionHandler::Format)
&& m_currentEditorWidget && !m_currentEditorWidget->isReadOnly();
diff --git a/src/plugins/texteditor/texteditoractionhandler.h b/src/plugins/texteditor/texteditoractionhandler.h
index 277c2cf66f4..2bdb8efff3e 100644
--- a/src/plugins/texteditor/texteditoractionhandler.h
+++ b/src/plugins/texteditor/texteditoractionhandler.h
@@ -36,7 +36,8 @@ public:
FollowSymbolUnderCursor = 8,
JumpToFileUnderCursor = 16,
RenameSymbol = 32,
- FindUsage = 64
+ FindUsage = 64,
+ CallHierarchy = 128
};
using TextEditorWidgetResolver = std::function<TextEditorWidget *(Core::IEditor *)>;
diff --git a/src/plugins/texteditor/texteditorconstants.h b/src/plugins/texteditor/texteditorconstants.h
index 0560dae9d82..f422630f0d2 100644
--- a/src/plugins/texteditor/texteditorconstants.h
+++ b/src/plugins/texteditor/texteditorconstants.h
@@ -208,6 +208,7 @@ const char FOLLOW_SYMBOL_UNDER_CURSOR_IN_NEXT_SPLIT[] = "TextEditor.FollowSymbol
const char FIND_USAGES[] = "TextEditor.FindUsages";
// moved from CppEditor to TextEditor avoid breaking the setting by using the old key
const char RENAME_SYMBOL[] = "CppEditor.RenameSymbolUnderCursor";
+const char OPEN_CALL_HIERARCHY[] = "TextEditor.OpenCallHierarchy";
const char JUMP_TO_FILE_UNDER_CURSOR[] = "TextEditor.JumpToFileUnderCursor";
const char JUMP_TO_FILE_UNDER_CURSOR_IN_NEXT_SPLIT[] = "TextEditor.JumpToFileUnderCursorInNextSplit";
diff --git a/src/plugins/texteditor/texteditorplugin.cpp b/src/plugins/texteditor/texteditorplugin.cpp
index e97453addae..a66ddf23c32 100644
--- a/src/plugins/texteditor/texteditorplugin.cpp
+++ b/src/plugins/texteditor/texteditorplugin.cpp
@@ -10,6 +10,7 @@
#include "highlighter.h"
#include "icodestylepreferences.h"
#include "linenumberfilter.h"
+#include "markdowneditor.h"
#include "outlinefactory.h"
#include "plaintexteditorfactory.h"
#include "snippets/snippetprovider.h"
@@ -66,6 +67,7 @@ public:
FindInOpenFiles findInOpenFilesFilter;
PlainTextEditorFactory plainTextEditorFactory;
+ MarkdownEditorFactory markdownEditorFactory;
};
static TextEditorPlugin *m_instance = nullptr;
diff --git a/src/plugins/texteditor/textmark.cpp b/src/plugins/texteditor/textmark.cpp
index 4a8320de57b..b7ded20d3fc 100644
--- a/src/plugins/texteditor/textmark.cpp
+++ b/src/plugins/texteditor/textmark.cpp
@@ -12,11 +12,13 @@
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/documentmanager.h>
#include <coreplugin/icore.h>
+#include <utils/outputformatter.h>
#include <utils/qtcassert.h>
#include <utils/tooltip/tooltip.h>
#include <utils/utilsicons.h>
#include <QAction>
+#include <QDesktopServices>
#include <QGridLayout>
#include <QPainter>
#include <QToolButton>
@@ -338,11 +340,18 @@ bool TextMark::addToolTipContent(QLayout *target) const
}
auto textLabel = new QLabel;
- textLabel->setOpenExternalLinks(true);
textLabel->setText(text);
// Differentiate between tool tips that where explicitly set and default tool tips.
textLabel->setDisabled(useDefaultToolTip);
target->addWidget(textLabel);
+ QObject::connect(textLabel, &QLabel::linkActivated, [](const QString &link) {
+ if (OutputLineParser::isLinkTarget(link)) {
+ Core::EditorManager::openEditorAt(OutputLineParser::parseLinkTarget(link), {},
+ Core::EditorManager::SwitchSplitIfAlreadyVisible);
+ } else {
+ QDesktopServices::openUrl(link);
+ }
+ });
return true;
}
diff --git a/src/plugins/todo/todoitemsprovider.cpp b/src/plugins/todo/todoitemsprovider.cpp
index 02790c64dfb..02ba71e2fcc 100644
--- a/src/plugins/todo/todoitemsprovider.cpp
+++ b/src/plugins/todo/todoitemsprovider.cpp
@@ -3,6 +3,7 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "todoitemsprovider.h"
+
#include "constants.h"
#include "cpptodoitemsscanner.h"
#include "qmljstodoitemsscanner.h"
@@ -13,9 +14,9 @@
#include <coreplugin/idocument.h>
#include <projectexplorer/projectexplorer.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/projectnodes.h>
#include <projectexplorer/projecttree.h>
-#include <projectexplorer/session.h>
#include <utils/algorithm.h>
@@ -182,8 +183,8 @@ void TodoItemsProvider::updateListTimeoutElapsed()
void TodoItemsProvider::setupStartupProjectBinding()
{
- m_startupProject = SessionManager::startupProject();
- connect(SessionManager::instance(), &SessionManager::startupProjectChanged,
+ m_startupProject = ProjectManager::startupProject();
+ connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged,
this, &TodoItemsProvider::startupProjectChanged);
connect(ProjectExplorerPlugin::instance(), &ProjectExplorerPlugin::fileListChanged,
this, &TodoItemsProvider::projectsFilesChanged);
diff --git a/src/plugins/valgrind/callgrindengine.cpp b/src/plugins/valgrind/callgrindengine.cpp
index d70c667df6f..51ec76a1305 100644
--- a/src/plugins/valgrind/callgrindengine.cpp
+++ b/src/plugins/valgrind/callgrindengine.cpp
@@ -12,6 +12,7 @@
#include <debugger/analyzer/analyzermanager.h>
#include <utils/filepath.h>
+#include <utils/filestreamermanager.h>
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
#include <utils/temporaryfile.h>
@@ -85,7 +86,7 @@ QStringList CallgrindToolRunner::toolArguments() const
arguments << "--callgrind-out-file=" + m_valgrindOutputFile.path();
- arguments << ProcessArgs::splitArgs(m_settings.callgrindArguments.value());
+ arguments << ProcessArgs::splitArgs(m_settings.callgrindArguments.value(), HostOsInfo::hostOs());
return arguments;
}
@@ -261,7 +262,9 @@ void CallgrindToolRunner::triggerParse()
showStatusMessage(Tr::tr("Parsing Profile Data..."));
m_parser.parse(m_hostOutputFile);
};
- m_valgrindOutputFile.asyncCopyFile(afterCopy, m_hostOutputFile);
+ // TODO: Store the handle and cancel on CallgrindToolRunner destructor?
+ // TODO: Should d'tor of context object cancel the running task?
+ FileStreamerManager::copy(m_valgrindOutputFile, m_hostOutputFile, this, afterCopy);
}
void CallgrindToolRunner::cleanupTempFile()
diff --git a/src/plugins/valgrind/callgrindtool.cpp b/src/plugins/valgrind/callgrindtool.cpp
index fc50b2ecf09..4daa04efcf6 100644
--- a/src/plugins/valgrind/callgrindtool.cpp
+++ b/src/plugins/valgrind/callgrindtool.cpp
@@ -47,8 +47,8 @@
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectexplorericons.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/projecttree.h>
-#include <projectexplorer/session.h>
#include <projectexplorer/taskhub.h>
#include <utils/fancymainwindow.h>
@@ -256,7 +256,7 @@ CallgrindToolPrivate::CallgrindToolPrivate()
menu->addAction(ActionManager::registerAction(action, CallgrindRemoteActionId),
Debugger::Constants::G_ANALYZER_REMOTE_TOOLS);
QObject::connect(action, &QAction::triggered, this, [this, action] {
- auto runConfig = SessionManager::startupRunConfiguration();
+ auto runConfig = ProjectManager::startupRunConfiguration();
if (!runConfig) {
showCannotStartDialog(action->text());
return;
diff --git a/src/plugins/valgrind/memcheckerrorview.cpp b/src/plugins/valgrind/memcheckerrorview.cpp
index 3bb8a66a02e..48e9afd8018 100644
--- a/src/plugins/valgrind/memcheckerrorview.cpp
+++ b/src/plugins/valgrind/memcheckerrorview.cpp
@@ -14,7 +14,7 @@
#include <coreplugin/editormanager/editormanager.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorer.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <utils/qtcassert.h>
#include <utils/icon.h>
diff --git a/src/plugins/valgrind/memchecktool.cpp b/src/plugins/valgrind/memchecktool.cpp
index d4a5e4a6499..7042419a7b5 100644
--- a/src/plugins/valgrind/memchecktool.cpp
+++ b/src/plugins/valgrind/memchecktool.cpp
@@ -30,8 +30,8 @@
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorer.h>
#include <projectexplorer/projectexplorerconstants.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/runconfiguration.h>
-#include <projectexplorer/session.h>
#include <projectexplorer/target.h>
#include <projectexplorer/taskhub.h>
#include <projectexplorer/toolchain.h>
@@ -214,7 +214,7 @@ QStringList MemcheckToolRunner::toolArguments() const
if (m_withGdb)
arguments << "--vgdb=yes" << "--vgdb-error=0";
- arguments << Utils::ProcessArgs::splitArgs(m_settings.memcheckArguments.value());
+ arguments << ProcessArgs::splitArgs(m_settings.memcheckArguments.value(), HostOsInfo::hostOs());
return arguments;
}
@@ -337,7 +337,7 @@ bool MemcheckErrorFilterProxyModel::filterAcceptsRow(int sourceRow, const QModel
// ALGORITHM: look at last five stack frames, if none of these is inside any open projects,
// assume this error was created by an external library
QSet<QString> validFolders;
- for (Project *project : SessionManager::projects()) {
+ for (Project *project : ProjectManager::projects()) {
validFolders << project->projectDirectory().toString();
const QList<Target *> targets = project->targets();
for (const Target *target : targets) {
@@ -676,7 +676,7 @@ MemcheckToolPrivate::MemcheckToolPrivate()
menu->addAction(ActionManager::registerAction(action, "Memcheck.Remote"),
Debugger::Constants::G_ANALYZER_REMOTE_TOOLS);
QObject::connect(action, &QAction::triggered, this, [this, action] {
- RunConfiguration *runConfig = SessionManager::startupRunConfiguration();
+ RunConfiguration *runConfig = ProjectManager::startupRunConfiguration();
if (!runConfig) {
showCannotStartDialog(action->text());
return;
@@ -718,7 +718,7 @@ void MemcheckToolPrivate::heobAction()
Abi abi;
bool hasLocalRc = false;
Kit *kit = nullptr;
- if (Target *target = SessionManager::startupTarget()) {
+ if (Target *target = ProjectManager::startupTarget()) {
if (RunConfiguration *rc = target->activeRunConfiguration()) {
kit = target->kit();
if (kit) {
@@ -940,7 +940,7 @@ void MemcheckToolPrivate::maybeActiveRunConfigurationChanged()
updateRunActions();
ValgrindBaseSettings *settings = nullptr;
- if (Project *project = SessionManager::startupProject())
+ if (Project *project = ProjectManager::startupProject())
if (Target *target = project->activeTarget())
if (RunConfiguration *rc = target->activeRunConfiguration())
settings = rc->currentSettings<ValgrindBaseSettings>(ANALYZER_VALGRIND_SETTINGS);
diff --git a/src/plugins/valgrind/suppressiondialog.cpp b/src/plugins/valgrind/suppressiondialog.cpp
index 08ed8ae5ff7..6273580e82d 100644
--- a/src/plugins/valgrind/suppressiondialog.cpp
+++ b/src/plugins/valgrind/suppressiondialog.cpp
@@ -12,9 +12,9 @@
#include "xmlprotocol/stack.h"
#include "xmlprotocol/frame.h"
-#include <projectexplorer/projectexplorer.h>
-#include <projectexplorer/session.h>
#include <projectexplorer/project.h>
+#include <projectexplorer/projectexplorer.h>
+#include <projectexplorer/projectmanager.h>
#include <projectexplorer/projectnodes.h>
#include <utils/algorithm.h>
@@ -182,8 +182,8 @@ void SuppressionDialog::accept()
return;
// Add file to project if there is a project containing this file on the file system.
- if (!ProjectExplorer::SessionManager::projectForFile(path)) {
- for (ProjectExplorer::Project *p : ProjectExplorer::SessionManager::projects()) {
+ if (!ProjectExplorer::ProjectManager::projectForFile(path)) {
+ for (ProjectExplorer::Project *p : ProjectExplorer::ProjectManager::projects()) {
if (path.startsWith(p->projectDirectory().toString())) {
p->rootProjectNode()->addFiles({path});
break;
diff --git a/src/plugins/valgrind/valgrind.qbs b/src/plugins/valgrind/valgrind.qbs
index f0e117ce98a..973b1d7b85a 100644
--- a/src/plugins/valgrind/valgrind.qbs
+++ b/src/plugins/valgrind/valgrind.qbs
@@ -76,9 +76,7 @@ QtcPlugin {
]
}
- Group {
- name: "Test sources"
- condition: qtc.testsEnabled
+ QtcTestFiles {
files: [
"valgrindmemcheckparsertest.cpp",
"valgrindmemcheckparsertest.h",
diff --git a/src/plugins/vcpkg/CMakeLists.txt b/src/plugins/vcpkg/CMakeLists.txt
new file mode 100644
index 00000000000..f5d65cf3937
--- /dev/null
+++ b/src/plugins/vcpkg/CMakeLists.txt
@@ -0,0 +1,16 @@
+add_qtc_plugin(Vcpkg
+ PLUGIN_DEPENDS Core ProjectExplorer
+ SOURCES
+ vcpkg.qrc
+ vcpkgconstants.h
+ vcpkgmanifesteditor.cpp vcpkgmanifesteditor.h
+ vcpkgplugin.cpp vcpkgplugin.h
+ vcpkgsearch.cpp vcpkgsearch.h
+ vcpkgsettings.cpp vcpkgsettings.h
+)
+
+extend_qtc_plugin(Vcpkg
+ CONDITION WITH_TESTS
+ SOURCES
+ vcpkg_test.cpp vcpkg_test.h
+)
diff --git a/src/plugins/vcpkg/Vcpkg.json.in b/src/plugins/vcpkg/Vcpkg.json.in
new file mode 100644
index 00000000000..7357c9e845b
--- /dev/null
+++ b/src/plugins/vcpkg/Vcpkg.json.in
@@ -0,0 +1,30 @@
+{
+ \"Name\" : \"Vcpkg\",
+ \"Version\" : \"$$QTCREATOR_VERSION\",
+ \"CompatVersion\" : \"$$QTCREATOR_COMPAT_VERSION\",
+ \"Vendor\" : \"The Qt Company Ltd\",
+ \"Copyright\" : \"(C) $$QTCREATOR_COPYRIGHT_YEAR The Qt Company Ltd\",
+ \"License\" : [ \"Commercial Usage\",
+ \"\",
+ \"Licensees holding valid Qt Commercial licenses may use this plugin in accordance with the Qt Commercial License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and The Qt Company.\",
+ \"\",
+ \"GNU General Public License Usage\",
+ \"\",
+ \"Alternatively, this plugin may be used under the terms of the GNU General Public License version 3 as published by the Free Software Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT included in the packaging of this plugin. Please review the following information to ensure the GNU General Public License requirements will be met: https://www.gnu.org/licenses/gpl-3.0.html.\"
+ ],
+ \"Experimental\" : true,
+ \"Description\" : \"vcpkg integration.\",
+ \"Url\" : \"http://www.qt.io\",
+ $$dependencyList,
+
+ \"Mimetypes\" : [
+ \"<?xml version=\'1.0\' encoding=\'UTF-8\'?>\",
+ \"<mime-info xmlns=\'http://www.freedesktop.org/standards/shared-mime-info\'>\",
+ \" <mime-type type=\'application/vcpkg.manifest+json\'>\",
+ \" <sub-class-of type=\'application/json\'/>\",
+ \" <comment>Vcpkg Manifest File</comment>\",
+ \" <glob pattern=\'vcpkg.json\' weight=\'71\'/>\",
+ \" </mime-type>\",
+ \"</mime-info>\"
+ ]
+}
diff --git a/src/plugins/vcpkg/vcpkg.qbs b/src/plugins/vcpkg/vcpkg.qbs
new file mode 100644
index 00000000000..dff796ab917
--- /dev/null
+++ b/src/plugins/vcpkg/vcpkg.qbs
@@ -0,0 +1,32 @@
+import qbs 1.0
+
+QtcPlugin {
+ name: "Vcpkg"
+
+ Depends { name: "Qt.widgets" }
+ Depends { name: "Utils" }
+
+ Depends { name: "Core" }
+ Depends { name: "ProjectExplorer" }
+ Depends { name: "TextEditor" }
+
+ files: [
+ "vcpkg.qrc",
+ "vcpkgconstants.h",
+ "vcpkgmanifesteditor.cpp",
+ "vcpkgmanifesteditor.h",
+ "vcpkgplugin.cpp",
+ "vcpkgplugin.h",
+ "vcpkgsearch.cpp",
+ "vcpkgsearch.h",
+ "vcpkgsettings.cpp",
+ "vcpkgsettings.h",
+ ]
+
+ QtcTestFiles {
+ files: [
+ "vcpkg_test.h",
+ "vcpkg_test.cpp",
+ ]
+ }
+}
diff --git a/src/plugins/vcpkg/vcpkg.qrc b/src/plugins/vcpkg/vcpkg.qrc
new file mode 100644
index 00000000000..a377b736db3
--- /dev/null
+++ b/src/plugins/vcpkg/vcpkg.qrc
@@ -0,0 +1,6 @@
+<RCC>
+ <qresource prefix="/vcpkg">
+ <file>wizards/manifest/vcpkg.json.tpl</file>
+ <file>wizards/manifest/wizard.json</file>
+ </qresource>
+</RCC>
diff --git a/src/plugins/vcpkg/vcpkg_test.cpp b/src/plugins/vcpkg/vcpkg_test.cpp
new file mode 100644
index 00000000000..65079b96a4e
--- /dev/null
+++ b/src/plugins/vcpkg/vcpkg_test.cpp
@@ -0,0 +1,110 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "vcpkg_test.h"
+
+#include "vcpkgsearch.h"
+
+#include <QTest>
+
+namespace Vcpkg::Internal {
+
+VcpkgSearchTest::VcpkgSearchTest(QObject *parent)
+ : QObject(parent)
+{ }
+
+VcpkgSearchTest::~VcpkgSearchTest() = default;
+
+void VcpkgSearchTest::testVcpkgJsonParser_data()
+{
+ QTest::addColumn<QString>("vcpkgManifestJsonData");
+ QTest::addColumn<QString>("name");
+ QTest::addColumn<QString>("version");
+ QTest::addColumn<QString>("license");
+ QTest::addColumn<QString>("shortDescription");
+ QTest::addColumn<QStringList>("description");
+ QTest::addColumn<QUrl>("homepage");
+ QTest::addColumn<bool>("success");
+
+ QTest::newRow("cimg, version, short description")
+ << R"({
+ "name": "cimg",
+ "version": "2.9.9",
+ "description": "The CImg Library is a small, open-source, and modern C++ toolkit for image processing",
+ "homepage": "https://github.com/dtschump/CImg",
+ "dependencies": [
+ {
+ "name": "vcpkg-cmake",
+ "host": true
+ }
+ ]
+ })"
+ << "cimg"
+ << "2.9.9"
+ << ""
+ << "The CImg Library is a small, open-source, and modern C++ toolkit for image processing"
+ << QStringList()
+ << QUrl::fromUserInput("https://github.com/dtschump/CImg")
+ << true;
+
+ QTest::newRow("catch-classic, version-string, complete description")
+ << R"({
+ "name": "catch-classic",
+ "version-string": "1.12.2",
+ "port-version": 1,
+ "description": [
+ "A modern, header-only test framework for unit tests",
+ "This is specifically the legacy 1.x branch provided for compatibility",
+ "with older compilers."
+ ],
+ "homepage": "https://github.com/catchorg/Catch2"
+ })"
+ << "catch-classic"
+ << "1.12.2"
+ << ""
+ << "A modern, header-only test framework for unit tests"
+ << QStringList({"This is specifically the legacy 1.x branch provided for compatibility",
+ "with older compilers."})
+ << QUrl::fromUserInput("https://github.com/catchorg/Catch2")
+ << true;
+
+ QTest::newRow("Incomplete")
+ << R"({
+ "version-semver": "1.0",
+ "description": "foo",
+ "license": "WTFPL"
+ })"
+ << ""
+ << "1.0"
+ << "WTFPL"
+ << "foo"
+ << QStringList()
+ << QUrl()
+ << false;
+}
+
+void VcpkgSearchTest::testVcpkgJsonParser()
+{
+ QFETCH(QString, vcpkgManifestJsonData);
+ QFETCH(QString, name);
+ QFETCH(QString, version);
+ QFETCH(QString, license);
+ QFETCH(QString, shortDescription);
+ QFETCH(QStringList, description);
+ QFETCH(QUrl, homepage);
+ QFETCH(bool, success);
+
+ bool ok = false;
+ const Search::VcpkgManifest mf =
+ Search::parseVcpkgManifest(vcpkgManifestJsonData.toUtf8(), &ok);
+
+ QCOMPARE(mf.name, name);
+ QCOMPARE(mf.version, version);
+ QCOMPARE(mf.license, license);
+ QCOMPARE(mf.shortDescription, shortDescription);
+ QCOMPARE(mf.description, description);
+ QCOMPARE(mf.homepage, homepage);
+ QCOMPARE(ok, success);
+}
+
+} // namespace Vcpkg::Internal
diff --git a/src/plugins/vcpkg/vcpkg_test.h b/src/plugins/vcpkg/vcpkg_test.h
new file mode 100644
index 00000000000..8175e28d594
--- /dev/null
+++ b/src/plugins/vcpkg/vcpkg_test.h
@@ -0,0 +1,24 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <coreplugin/dialogs/ioptionspage.h>
+#include <utils/aspects.h>
+
+namespace Vcpkg::Internal {
+
+class VcpkgSearchTest : public QObject
+{
+ Q_OBJECT
+
+public:
+ VcpkgSearchTest(QObject *parent = nullptr);
+ ~VcpkgSearchTest();
+
+private slots:
+ void testVcpkgJsonParser_data();
+ void testVcpkgJsonParser();
+};
+
+} // namespace Vcpkg::Internal
diff --git a/src/plugins/vcpkg/vcpkgconstants.h b/src/plugins/vcpkg/vcpkgconstants.h
new file mode 100644
index 00000000000..644dfab95f9
--- /dev/null
+++ b/src/plugins/vcpkg/vcpkgconstants.h
@@ -0,0 +1,14 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+namespace Vcpkg::Constants {
+
+const char TOOLSSETTINGSPAGE_ID[] = "Vcpkg.VcpkgSettings";
+const char WEBSITE_URL[] = "https://vcpkg.io/";
+const char ENVVAR_VCPKG_ROOT[] = "VCPKG_ROOT";
+const char VCPKGMANIFEST_EDITOR_ID[] = "Vcpkg.VcpkgManifestEditor";
+const char VCPKGMANIFEST_MIMETYPE[] = "application/vcpkg.manifest+json";
+
+} // namespace Vcpkg::Constants
diff --git a/src/plugins/vcpkg/vcpkgmanifesteditor.cpp b/src/plugins/vcpkg/vcpkgmanifesteditor.cpp
new file mode 100644
index 00000000000..fa7c6ebfaf3
--- /dev/null
+++ b/src/plugins/vcpkg/vcpkgmanifesteditor.cpp
@@ -0,0 +1,71 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "vcpkgmanifesteditor.h"
+
+#include "vcpkgconstants.h"
+#include "vcpkgsearch.h"
+#include "vcpkgsettings.h"
+#include "vcpkgtr.h"
+
+#include <coreplugin/icore.h>
+
+#include <utils/utilsicons.h>
+
+#include <texteditor/textdocument.h>
+
+#include <QToolBar>
+
+namespace Vcpkg::Internal {
+
+class VcpkgManifestEditorWidget : public TextEditor::TextEditorWidget
+{
+public:
+ VcpkgManifestEditorWidget()
+ {
+ m_searchPkgAction = toolBar()->addAction(Utils::Icons::ZOOM_TOOLBAR.icon(),
+ Tr::tr("Search package..."));
+ connect(m_searchPkgAction, &QAction::triggered, this, [this] {
+ const Search::VcpkgManifest package = Search::showVcpkgPackageSearchDialog();
+ if (!package.name.isEmpty())
+ textCursor().insertText(package.name);
+ });
+ updateToolBar();
+
+ QAction *optionsAction = toolBar()->addAction(Utils::Icons::SETTINGS_TOOLBAR.icon(),
+ Core::ICore::msgShowOptionsDialog());
+ connect(optionsAction, &QAction::triggered, [] {
+ Core::ICore::showOptionsDialog(Constants::TOOLSSETTINGSPAGE_ID);
+ });
+
+ connect(&VcpkgSettings::instance()->vcpkgRoot, &Utils::BaseAspect::changed,
+ this, &VcpkgManifestEditorWidget::updateToolBar);
+ }
+
+ void updateToolBar()
+ {
+ m_searchPkgAction->setEnabled(VcpkgSettings::instance()->vcpkgRootValid());
+ }
+
+private:
+ QAction *m_searchPkgAction;
+};
+
+static TextEditor::TextDocument *createVcpkgManifestDocument()
+{
+ auto doc = new TextEditor::TextDocument;
+ doc->setId(Constants::VCPKGMANIFEST_EDITOR_ID);
+ return doc;
+}
+
+VcpkgManifestEditorFactory::VcpkgManifestEditorFactory()
+{
+ setId(Constants::VCPKGMANIFEST_EDITOR_ID);
+ setDisplayName(Tr::tr("Vcpkg Manifest Editor"));
+ addMimeType(Constants::VCPKGMANIFEST_MIMETYPE);
+ setDocumentCreator(createVcpkgManifestDocument);
+ setEditorWidgetCreator([] { return new VcpkgManifestEditorWidget; });
+ setUseGenericHighlighter(true);
+}
+
+} // namespace Vcpkg::Internal
diff --git a/src/plugins/vcpkg/vcpkgmanifesteditor.h b/src/plugins/vcpkg/vcpkgmanifesteditor.h
new file mode 100644
index 00000000000..c7762d69df9
--- /dev/null
+++ b/src/plugins/vcpkg/vcpkgmanifesteditor.h
@@ -0,0 +1,16 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <texteditor/texteditor.h>
+
+namespace Vcpkg::Internal {
+
+class VcpkgManifestEditorFactory : public TextEditor::TextEditorFactory
+{
+public:
+ VcpkgManifestEditorFactory();
+};
+
+} // namespace Vcpkg::Internal
diff --git a/src/plugins/vcpkg/vcpkgplugin.cpp b/src/plugins/vcpkg/vcpkgplugin.cpp
new file mode 100644
index 00000000000..883b6896a72
--- /dev/null
+++ b/src/plugins/vcpkg/vcpkgplugin.cpp
@@ -0,0 +1,38 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "vcpkgplugin.h"
+
+#ifdef WITH_TESTS
+#include "vcpkg_test.h"
+#endif // WITH_TESTS
+#include "vcpkgmanifesteditor.h"
+#include "vcpkgsettings.h"
+
+#include <projectexplorer/jsonwizard/jsonwizardfactory.h>
+
+namespace Vcpkg::Internal {
+
+class VcpkgPluginPrivate
+{
+public:
+ VcpkgManifestEditorFactory manifestEditorFactory;
+ VcpkgSettingsPage settingsPage;
+};
+
+VcpkgPlugin::~VcpkgPlugin()
+{
+ delete d;
+}
+
+void VcpkgPlugin::initialize()
+{
+ d = new VcpkgPluginPrivate;
+ ProjectExplorer::JsonWizardFactory::addWizardPath(":/vcpkg/wizards/");
+
+#ifdef WITH_TESTS
+ addTest<VcpkgSearchTest>();
+#endif
+}
+
+} // namespace Vcpkg::Internal
diff --git a/src/plugins/vcpkg/vcpkgplugin.h b/src/plugins/vcpkg/vcpkgplugin.h
new file mode 100644
index 00000000000..797083ea956
--- /dev/null
+++ b/src/plugins/vcpkg/vcpkgplugin.h
@@ -0,0 +1,26 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <extensionsystem/iplugin.h>
+
+namespace ProjectExplorer { class Project; }
+
+namespace Vcpkg::Internal {
+
+class VcpkgPlugin final : public ExtensionSystem::IPlugin
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QtCreatorPlugin" FILE "Vcpkg.json")
+
+public:
+ ~VcpkgPlugin();
+
+ void initialize() final;
+
+private:
+ class VcpkgPluginPrivate *d = nullptr;
+};
+
+} // namespace Vcpkg::Internal
diff --git a/src/plugins/vcpkg/vcpkgsearch.cpp b/src/plugins/vcpkg/vcpkgsearch.cpp
new file mode 100644
index 00000000000..dc3fe7a0c6c
--- /dev/null
+++ b/src/plugins/vcpkg/vcpkgsearch.cpp
@@ -0,0 +1,214 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "vcpkgsearch.h"
+
+#include "qpushbutton.h"
+#include "vcpkgsettings.h"
+#include "vcpkgtr.h"
+
+#include <utils/algorithm.h>
+#include <utils/fancylineedit.h>
+#include <utils/fileutils.h>
+#include <utils/itemviews.h>
+#include <utils/layoutbuilder.h>
+
+#include <coreplugin/icore.h>
+
+#include <QDialogButtonBox>
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QTextBrowser>
+
+using namespace Utils;
+
+namespace Vcpkg::Internal::Search {
+
+class VcpkgPackageSearchDialog : public QDialog
+{
+public:
+ explicit VcpkgPackageSearchDialog(QWidget *parent);
+
+ VcpkgManifest selectedPackage() const;
+
+private:
+ void listPackages(const QString &filter);
+ void showPackageDetails(const QString &packageName);
+
+ VcpkgManifests m_allPackages;
+ VcpkgManifest m_selectedPackage;
+
+ FancyLineEdit *m_packagesFilter;
+ ListWidget *m_packagesList;
+ QLineEdit *m_vcpkgName;
+ QLabel *m_vcpkgVersion;
+ QLabel *m_vcpkgLicense;
+ QTextBrowser *m_vcpkgDescription;
+ QLabel *m_vcpkgHomepage;
+ QDialogButtonBox *m_buttonBox;
+};
+
+VcpkgPackageSearchDialog::VcpkgPackageSearchDialog(QWidget *parent)
+ : QDialog(parent)
+{
+ resize(920, 400);
+
+ m_packagesFilter = new FancyLineEdit;
+ m_packagesFilter->setFiltering(true);
+ m_packagesFilter->setFocus();
+ m_packagesFilter->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred);
+
+ m_packagesList = new ListWidget;
+ m_packagesList->setMaximumWidth(300);
+
+ m_vcpkgName = new QLineEdit;
+ m_vcpkgName->setReadOnly(true);
+
+ m_vcpkgVersion = new QLabel;
+ m_vcpkgLicense = new QLabel;
+ m_vcpkgDescription = new QTextBrowser;
+
+ m_vcpkgHomepage = new QLabel;
+ m_vcpkgHomepage->setOpenExternalLinks(true);
+ m_vcpkgHomepage->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
+ m_vcpkgHomepage->setTextInteractionFlags(Qt::TextBrowserInteraction);
+
+ m_buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Close);
+
+ using namespace Utils::Layouting;
+ Column {
+ Row {
+ Column {
+ m_packagesFilter,
+ m_packagesList,
+ },
+ Form {
+ Tr::tr("Name:"), m_vcpkgName, br,
+ Tr::tr("Version:"), m_vcpkgVersion, br,
+ Tr::tr("License:"), m_vcpkgLicense, br,
+ Tr::tr("Description:"), m_vcpkgDescription, br,
+ Tr::tr("Homepage:"), m_vcpkgHomepage, br,
+ },
+ },
+ m_buttonBox,
+ }.attachTo(this);
+
+ m_allPackages = vcpkgManifests(VcpkgSettings::instance()->vcpkgRoot.filePath());
+
+ listPackages({});
+
+ connect(m_packagesFilter, &FancyLineEdit::filterChanged,
+ this, &VcpkgPackageSearchDialog::listPackages);
+ connect(m_packagesList, &ListWidget::currentTextChanged,
+ this, &VcpkgPackageSearchDialog::showPackageDetails);
+ connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
+ connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
+}
+
+VcpkgManifest VcpkgPackageSearchDialog::selectedPackage() const
+{
+ return m_selectedPackage;
+}
+
+void VcpkgPackageSearchDialog::listPackages(const QString &filter)
+{
+ const VcpkgManifests filteredPackages = filtered(m_allPackages,
+ [&filter] (const VcpkgManifest &package) {
+ return filter.isEmpty()
+ || package.name.contains(filter, Qt::CaseInsensitive)
+ || package.shortDescription.contains(filter, Qt::CaseInsensitive)
+ || package.description.contains(filter, Qt::CaseInsensitive);
+ });
+ QStringList names = transform(filteredPackages, [] (const VcpkgManifest &package) {
+ return package.name;
+ });
+ names.sort();
+ m_packagesList->clear();
+ m_packagesList->addItems(names);
+}
+
+void VcpkgPackageSearchDialog::showPackageDetails(const QString &packageName)
+{
+ const VcpkgManifest manifest = findOrDefault(m_allPackages,
+ [&packageName] (const VcpkgManifest &m) {
+ return m.name == packageName;
+ });
+
+ m_vcpkgName->setText(manifest.name);
+ m_vcpkgVersion->setText(manifest.version);
+ m_vcpkgLicense->setText(manifest.license);
+ QString description = manifest.shortDescription;
+ if (!manifest.description.isEmpty())
+ description.append("<p>" + manifest.description.join("</p><p>") + "</p>");
+ m_vcpkgDescription->setText(description);
+ m_vcpkgHomepage->setText(QString::fromLatin1("<a href=\"%1\">%1</a>")
+ .arg(manifest.homepage.toDisplayString()));
+
+ m_selectedPackage = manifest;
+ m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!manifest.name.isEmpty());
+}
+
+VcpkgManifest parseVcpkgManifest(const QByteArray &vcpkgManifestJsonData, bool *ok)
+{
+ // https://learn.microsoft.com/en-us/vcpkg/reference/vcpkg-json
+ VcpkgManifest result;
+ const QJsonObject jsonObject = QJsonDocument::fromJson(vcpkgManifestJsonData).object();
+ if (const QJsonValue name = jsonObject.value("name"); !name.isUndefined())
+ result.name = name.toString();
+ for (const char *key : {"version", "version-semver", "version-date", "version-string"} ) {
+ if (const QJsonValue ver = jsonObject.value(QLatin1String(key)); !ver.isUndefined()) {
+ result.version = ver.toString();
+ break;
+ }
+ }
+ if (const QJsonValue license = jsonObject.value("license"); !license.isUndefined())
+ result.license = license.toString();
+ if (const QJsonValue description = jsonObject.value("description"); !description.isUndefined()) {
+ if (description.isArray()) {
+ const QJsonArray descriptionLines = description.toArray();
+ for (const QJsonValue &val : descriptionLines) {
+ const QString line = val.toString();
+ if (result.shortDescription.isEmpty()) {
+ result.shortDescription = line;
+ continue;
+ }
+ result.description.append(line);
+ }
+ } else {
+ result.shortDescription = description.toString();
+ }
+ }
+ if (const QJsonValue homepage = jsonObject.value("homepage"); !homepage.isUndefined())
+ result.homepage = QUrl::fromUserInput(homepage.toString());
+
+ if (ok)
+ *ok = !(result.name.isEmpty() || result.version.isEmpty());
+
+ return result;
+}
+
+VcpkgManifests vcpkgManifests(const FilePath &vcpkgRoot)
+{
+ const FilePath portsDir = vcpkgRoot / "ports";
+ VcpkgManifests result;
+ const FilePaths manifestFiles =
+ portsDir.dirEntries({{"vcpkg.json"}, QDir::Files, QDirIterator::Subdirectories});
+ for (const FilePath &manifestFile : manifestFiles) {
+ FileReader reader;
+ if (reader.fetch(manifestFile)) {
+ const QByteArray &manifestData = reader.data();
+ const VcpkgManifest manifest = parseVcpkgManifest(manifestData);
+ result.append(manifest);
+ }
+ }
+ return result;
+}
+
+VcpkgManifest showVcpkgPackageSearchDialog(QWidget *parent)
+{
+ VcpkgPackageSearchDialog dlg(parent ? parent : Core::ICore::dialogParent());
+ return (dlg.exec() == QDialog::Accepted) ? dlg.selectedPackage() : VcpkgManifest();
+}
+
+} // namespace Vcpkg::Internal::Search
diff --git a/src/plugins/vcpkg/vcpkgsearch.h b/src/plugins/vcpkg/vcpkgsearch.h
new file mode 100644
index 00000000000..bb2d568a00c
--- /dev/null
+++ b/src/plugins/vcpkg/vcpkgsearch.h
@@ -0,0 +1,29 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <coreplugin/dialogs/ioptionspage.h>
+#include <utils/aspects.h>
+
+#include <QUrl>
+
+namespace Vcpkg::Internal::Search {
+
+struct VcpkgManifest
+{
+ QString name;
+ QString version;
+ QString license;
+ QString shortDescription;
+ QStringList description;
+ QUrl homepage;
+};
+
+using VcpkgManifests = QList<VcpkgManifest>;
+
+VcpkgManifest parseVcpkgManifest(const QByteArray &vcpkgManifestJsonData, bool *ok = nullptr);
+VcpkgManifests vcpkgManifests(const Utils::FilePath &vcpkgRoot);
+VcpkgManifest showVcpkgPackageSearchDialog(QWidget *parent = nullptr);
+
+} // namespace Vcpkg::Internal::Search
diff --git a/src/plugins/vcpkg/vcpkgsettings.cpp b/src/plugins/vcpkg/vcpkgsettings.cpp
new file mode 100644
index 00000000000..bea6c8ed6c7
--- /dev/null
+++ b/src/plugins/vcpkg/vcpkgsettings.cpp
@@ -0,0 +1,80 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "vcpkgsettings.h"
+
+#include "vcpkgconstants.h"
+
+#include <coreplugin/icore.h>
+
+#include <cmakeprojectmanager/cmakeprojectconstants.h>
+
+#include <utils/aspects.h>
+#include <utils/environment.h>
+#include <utils/layoutbuilder.h>
+#include <utils/utilsicons.h>
+
+#include <QDesktopServices>
+#include <QToolButton>
+
+namespace Vcpkg::Internal {
+
+VcpkgSettings::VcpkgSettings()
+{
+ setSettingsGroup("Vcpkg");
+
+ registerAspect(&vcpkgRoot);
+ vcpkgRoot.setSettingsKey("VcpkgRoot");
+ vcpkgRoot.setDisplayStyle(Utils::StringAspect::PathChooserDisplay);
+ vcpkgRoot.setExpectedKind(Utils::PathChooser::ExistingDirectory);
+ vcpkgRoot.setDefaultValue(Utils::qtcEnvironmentVariable(Constants::ENVVAR_VCPKG_ROOT));
+
+ readSettings(Core::ICore::settings());
+}
+
+VcpkgSettings *VcpkgSettings::instance()
+{
+ static VcpkgSettings s;
+ return &s;
+}
+
+bool VcpkgSettings::vcpkgRootValid() const
+{
+ return (vcpkgRoot.filePath() / "vcpkg").withExecutableSuffix().isExecutableFile();
+}
+
+VcpkgSettingsPage::VcpkgSettingsPage()
+{
+ setId(Constants::TOOLSSETTINGSPAGE_ID);
+ setDisplayName("Vcpkg");
+ setCategory(CMakeProjectManager::Constants::Settings::CATEGORY);
+
+ setLayouter([] (QWidget *widget) {
+ auto websiteButton = new QToolButton;
+ websiteButton->setIcon(Utils::Icons::ONLINE.icon());
+ websiteButton->setToolTip(Constants::WEBSITE_URL);
+
+ using namespace Utils::Layouting;
+ Column {
+ Group {
+ title(tr("Vcpkg installation")),
+ Form {
+ Utils::PathChooser::label(),
+ Span{ 2, Row{ VcpkgSettings::instance()->vcpkgRoot, websiteButton} },
+ },
+ },
+ st,
+ }.attachTo(widget);
+
+ connect(websiteButton, &QAbstractButton::clicked, [] {
+ QDesktopServices::openUrl(QUrl::fromUserInput(Constants::WEBSITE_URL));
+ });
+ });
+}
+
+void VcpkgSettingsPage::apply()
+{
+ VcpkgSettings::instance()->writeSettings(Core::ICore::settings());
+}
+
+} // namespace Vcpkg::Internal
diff --git a/src/plugins/vcpkg/vcpkgsettings.h b/src/plugins/vcpkg/vcpkgsettings.h
new file mode 100644
index 00000000000..a0a7973cc01
--- /dev/null
+++ b/src/plugins/vcpkg/vcpkgsettings.h
@@ -0,0 +1,30 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <coreplugin/dialogs/ioptionspage.h>
+#include <utils/aspects.h>
+
+namespace Vcpkg::Internal {
+
+class VcpkgSettings : public Utils::AspectContainer
+{
+public:
+ VcpkgSettings();
+
+ static VcpkgSettings *instance();
+ bool vcpkgRootValid() const;
+
+ Utils::StringAspect vcpkgRoot;
+};
+
+class VcpkgSettingsPage final : public Core::IOptionsPage
+{
+public:
+ VcpkgSettingsPage();
+
+ void apply() override;
+};
+
+} // namespace Vcpkg::Internal
diff --git a/src/plugins/vcpkg/vcpkgtr.h b/src/plugins/vcpkg/vcpkgtr.h
new file mode 100644
index 00000000000..0914ce6444d
--- /dev/null
+++ b/src/plugins/vcpkg/vcpkgtr.h
@@ -0,0 +1,15 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <QCoreApplication>
+
+namespace Vcpkg {
+
+struct Tr
+{
+ Q_DECLARE_TR_FUNCTIONS(Vcpkg)
+};
+
+} // namespace Vcpkg
diff --git a/src/plugins/vcpkg/wizards/manifest/vcpkg.json.tpl b/src/plugins/vcpkg/wizards/manifest/vcpkg.json.tpl
new file mode 100644
index 00000000000..1519949d2ea
--- /dev/null
+++ b/src/plugins/vcpkg/wizards/manifest/vcpkg.json.tpl
@@ -0,0 +1,6 @@
+{
+ "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json",
+ "name": "%{Name}",
+ "version-string": "%{VersionString}",
+%{Dependencies}
+}
diff --git a/src/plugins/vcpkg/wizards/manifest/wizard.json b/src/plugins/vcpkg/wizards/manifest/wizard.json
new file mode 100644
index 00000000000..80e8b0fcc52
--- /dev/null
+++ b/src/plugins/vcpkg/wizards/manifest/wizard.json
@@ -0,0 +1,80 @@
+{
+ "version": 1,
+ "supportedProjectTypes": [ ],
+ "id": "VcpkgManifest.Json",
+ "category": "U.VcpkgManifest",
+ "trDescription": "Creates a vcpkg.json manifest file.",
+ "trDisplayName": "vcpkg.json Manifest File",
+ "trDisplayCategory": "vcpkg",
+ "iconText": "json",
+
+ "options": [
+ { "key": "InitialFileName", "value": "vcpkg.json" },
+ { "key": "TargetPath", "value": "%{Path}" }
+ ],
+
+ "pages":
+ [
+ {
+ "trDisplayName": "Location",
+ "trShortTitle": "Location",
+ "typeId": "File"
+ },
+ {
+ "trDisplayName": "vcpkg.json Manifest File",
+ "trShortTitle": "Manifest fields",
+ "typeId": "Fields",
+ "data":
+ [
+ {
+ "name": "Name",
+ "trDisplayName": "Name:",
+ "mandatory": true,
+ "type": "LineEdit",
+ "data":
+ {
+ "trText": "mypackage",
+ "validator": "^[a-z_0-9]+$"
+ }
+ },
+ {
+ "name": "VersionString",
+ "trDisplayName": "Version string:",
+ "mandatory": true,
+ "type": "LineEdit",
+ "data":
+ {
+ "trText": "0.0.1"
+ }
+ },
+ {
+ "name": "Dependencies",
+ "trDisplayName": "Dependencies:",
+ "mandatory": false,
+ "type": "TextEdit",
+ "data":
+ {
+ "trText": " \"dependencies\": [\n \"fmt\"\n ]"
+ }
+ }
+ ]
+ },
+ {
+ "trDisplayName": "Project Management",
+ "trShortTitle": "Summary",
+ "typeId": "Summary"
+ }
+ ],
+ "generators":
+ [
+ {
+ "typeId": "File",
+ "data":
+ {
+ "source": "vcpkg.json.tpl",
+ "target": "%{Path}/vcpkg.json",
+ "openInEditor": true
+ }
+ }
+ ]
+}
diff --git a/src/plugins/vcsbase/cleandialog.cpp b/src/plugins/vcsbase/cleandialog.cpp
index bc5ddafe66d..487c7c7e1eb 100644
--- a/src/plugins/vcsbase/cleandialog.cpp
+++ b/src/plugins/vcsbase/cleandialog.cpp
@@ -9,8 +9,8 @@
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/progressmanager/progressmanager.h>
+#include <utils/asynctask.h>
#include <utils/layoutbuilder.h>
-#include <utils/runextensions.h>
#include <QApplication>
#include <QCheckBox>
@@ -38,10 +38,10 @@ enum { nameColumn, columnCount };
enum { fileNameRole = Qt::UserRole, isDirectoryRole = Qt::UserRole + 1 };
// Helper for recursively removing files.
-static void removeFileRecursion(QFutureInterface<void> &futureInterface,
- const QFileInfo &f, QString *errorMessage)
+static void removeFileRecursion(QPromise<void> &promise, const QFileInfo &f,
+ QString *errorMessage)
{
- if (futureInterface.isCanceled())
+ if (promise.isCanceled())
return;
// The version control system might list files/directory in arbitrary
// order, causing files to be removed from parent directories.
@@ -51,7 +51,7 @@ static void removeFileRecursion(QFutureInterface<void> &futureInterface,
const QDir dir(f.absoluteFilePath());
const QList<QFileInfo> infos = dir.entryInfoList(QDir::AllEntries|QDir::NoDotAndDotDot|QDir::Hidden);
for (const QFileInfo &fi : infos)
- removeFileRecursion(futureInterface, fi, errorMessage);
+ removeFileRecursion(promise, fi, errorMessage);
QDir parent = f.absoluteDir();
if (!parent.rmdir(f.fileName()))
errorMessage->append(Tr::tr("The directory %1 could not be deleted.")
@@ -67,18 +67,19 @@ static void removeFileRecursion(QFutureInterface<void> &futureInterface,
}
// Cleaning files in the background
-static void runCleanFiles(QFutureInterface<void> &futureInterface,
- const FilePath &repository, const QStringList &files,
+static void runCleanFiles(QPromise<void> &promise, const FilePath &repository,
+ const QStringList &files,
const std::function<void(const QString&)> &errorHandler)
{
QString errorMessage;
- futureInterface.setProgressRange(0, files.size());
- futureInterface.setProgressValue(0);
+ promise.setProgressRange(0, files.size());
+ promise.setProgressValue(0);
+ int fileIndex = 0;
for (const QString &name : files) {
- removeFileRecursion(futureInterface, QFileInfo(name), &errorMessage);
- if (futureInterface.isCanceled())
+ removeFileRecursion(promise, QFileInfo(name), &errorMessage);
+ if (promise.isCanceled())
break;
- futureInterface.setProgressValue(futureInterface.progressValue() + 1);
+ promise.setProgressValue(++fileIndex);
}
if (!errorMessage.isEmpty()) {
// Format and emit error.
@@ -258,8 +259,8 @@ bool CleanDialog::promptToDelete()
return false;
// Remove in background
- QFuture<void> task = runAsync(Internal::runCleanFiles, d->m_workingDirectory,
- selectedFiles, Internal::handleError);
+ QFuture<void> task = Utils::asyncRun(Internal::runCleanFiles, d->m_workingDirectory,
+ selectedFiles, Internal::handleError);
const QString taskName = Tr::tr("Cleaning \"%1\"").arg(d->m_workingDirectory.toUserOutput());
Core::ProgressManager::addTask(task, taskName, "VcsBase.cleanRepository");
diff --git a/src/plugins/vcsbase/commonvcssettings.cpp b/src/plugins/vcsbase/commonvcssettings.cpp
index 160095d38dc..2f970ccbb83 100644
--- a/src/plugins/vcsbase/commonvcssettings.cpp
+++ b/src/plugins/vcsbase/commonvcssettings.cpp
@@ -135,9 +135,9 @@ CommonSettingsWidget::CommonSettingsWidget(CommonOptionsPage *page)
void CommonSettingsWidget::updatePath()
{
- EnvironmentChange change;
- change.addAppendToPath(Core::VcsManager::additionalToolsPath());
- m_page->settings().sshPasswordPrompt.setEnvironmentChange(change);
+ Environment env;
+ env.appendToPath(Core::VcsManager::additionalToolsPath());
+ m_page->settings().sshPasswordPrompt.setEnvironment(env);
}
void CommonSettingsWidget::apply()
diff --git a/src/plugins/vcsbase/vcsbaseclient.cpp b/src/plugins/vcsbase/vcsbaseclient.cpp
index c51b1292f9a..a73758b15ea 100644
--- a/src/plugins/vcsbase/vcsbaseclient.cpp
+++ b/src/plugins/vcsbase/vcsbaseclient.cpp
@@ -22,6 +22,7 @@
#include <utils/commandline.h>
#include <utils/environment.h>
#include <utils/qtcassert.h>
+#include <utils/qtcprocess.h>
#include <QDebug>
#include <QStringList>
@@ -89,6 +90,16 @@ VcsCommand *VcsBaseClientImpl::createCommand(const FilePath &workingDirectory,
return cmd;
}
+void VcsBaseClientImpl::setupCommand(Utils::QtcProcess &process,
+ const FilePath &workingDirectory,
+ const QStringList &args) const
+{
+ process.setEnvironment(processEnvironment());
+ process.setWorkingDirectory(workingDirectory);
+ process.setCommand({vcsBinary(), args});
+ process.setUseCtrlCStub(true);
+}
+
void VcsBaseClientImpl::enqueueJob(VcsCommand *cmd, const QStringList &args,
const ExitCodeInterpreter &interpreter) const
{
diff --git a/src/plugins/vcsbase/vcsbaseclient.h b/src/plugins/vcsbase/vcsbaseclient.h
index 6703495c37f..4e5bf91d5ff 100644
--- a/src/plugins/vcsbase/vcsbaseclient.h
+++ b/src/plugins/vcsbase/vcsbaseclient.h
@@ -23,6 +23,10 @@ class QTextCodec;
class QToolBar;
QT_END_NAMESPACE
+namespace Utils {
+class QtcProcess;
+}
+
namespace VcsBase {
class CommandResult;
@@ -56,6 +60,10 @@ public:
VcsCommand *createCommand(const Utils::FilePath &workingDirectory,
VcsBaseEditorWidget *editor = nullptr) const;
+ void setupCommand(Utils::QtcProcess &process,
+ const Utils::FilePath &workingDirectory,
+ const QStringList &args) const;
+
void enqueueJob(VcsCommand *cmd, const QStringList &args,
const Utils::ExitCodeInterpreter &interpreter = {}) const;
diff --git a/src/plugins/vcsbase/vcsbasediffeditorcontroller.cpp b/src/plugins/vcsbase/vcsbasediffeditorcontroller.cpp
index c0d31775af9..65e425cf64b 100644
--- a/src/plugins/vcsbase/vcsbasediffeditorcontroller.cpp
+++ b/src/plugins/vcsbase/vcsbasediffeditorcontroller.cpp
@@ -15,15 +15,6 @@ using namespace Utils;
namespace VcsBase {
-static void readPatch(QFutureInterface<QList<FileData>> &futureInterface, const QString &patch)
-{
- bool ok;
- const QList<FileData> &fileDataList = DiffUtils::readPatch(patch, &ok, &futureInterface);
- futureInterface.reportResult(fileDataList);
-}
-
-/////////////////////
-
class VcsBaseDiffEditorControllerPrivate
{
public:
@@ -61,11 +52,11 @@ Tasking::TaskItem VcsBaseDiffEditorController::postProcessTask()
QTC_ASSERT(storage, qWarning("Using postProcessTask() requires putting inputStorage() "
"into task tree's root group."));
const QString inputData = storage ? *storage : QString();
- async.setAsyncCallData(readPatch, inputData);
+ async.setConcurrentCallData(&DiffUtils::readPatchWithPromise, inputData);
async.setFutureSynchronizer(Internal::VcsPlugin::futureSynchronizer());
};
const auto onDiffProcessorDone = [this](const AsyncTask<QList<FileData>> &async) {
- setDiffFiles(async.result());
+ setDiffFiles(async.isResultAvailable() ? async.result() : QList<FileData>());
};
const auto onDiffProcessorError = [this](const AsyncTask<QList<FileData>> &) {
setDiffFiles({});
diff --git a/src/plugins/vcsbase/vcsbaseeditor.cpp b/src/plugins/vcsbase/vcsbaseeditor.cpp
index c73c14f9f66..26c19c7f91e 100644
--- a/src/plugins/vcsbase/vcsbaseeditor.cpp
+++ b/src/plugins/vcsbase/vcsbaseeditor.cpp
@@ -24,7 +24,7 @@
#include <projectexplorer/editorconfiguration.h>
#include <projectexplorer/project.h>
#include <projectexplorer/projectexplorer.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <texteditor/textdocument.h>
#include <texteditor/textdocumentlayout.h>
@@ -1234,7 +1234,7 @@ static QTextCodec *findProjectCodec(const FilePath &dirPath)
{
typedef QList<ProjectExplorer::Project*> ProjectList;
// Try to find a project under which file tree the file is.
- const ProjectList projects = ProjectExplorer::SessionManager::projects();
+ const ProjectList projects = ProjectExplorer::ProjectManager::projects();
const ProjectExplorer::Project *p
= findOrDefault(projects, equal(&ProjectExplorer::Project::projectDirectory, dirPath));
return p ? p->editorConfiguration()->textCodec() : nullptr;
diff --git a/src/plugins/vcsbase/vcsbaseplugin.cpp b/src/plugins/vcsbase/vcsbaseplugin.cpp
index b277a2d0680..dfd89842c52 100644
--- a/src/plugins/vcsbase/vcsbaseplugin.cpp
+++ b/src/plugins/vcsbase/vcsbaseplugin.cpp
@@ -9,12 +9,14 @@
#include "vcsplugin.h"
#include <coreplugin/documentmanager.h>
+#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/icore.h>
#include <coreplugin/idocument.h>
-#include <coreplugin/editormanager/editormanager.h>
-#include <projectexplorer/projecttree.h>
+
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
+#include <projectexplorer/projecttree.h>
+
#include <utils/qtcassert.h>
#include <utils/qtcprocess.h>
@@ -195,7 +197,7 @@ StateListener::StateListener(QObject *parent) : QObject(parent)
connect(ProjectTree::instance(), &ProjectTree::currentProjectChanged,
this, &StateListener::slotStateChanged);
- connect(SessionManager::instance(), &SessionManager::startupProjectChanged,
+ connect(ProjectManager::instance(), &ProjectManager::startupProjectChanged,
this, &StateListener::slotStateChanged);
EditorManager::setWindowTitleVcsTopicHandler(&StateListener::windowTitleVcsTopic);
@@ -213,7 +215,7 @@ QString StateListener::windowTitleVcsTopic(const FilePath &filePath)
searchPath = filePath.absolutePath();
} else {
// use single project's information if there is only one loaded.
- const QList<Project *> projects = SessionManager::projects();
+ const QList<Project *> projects = ProjectManager::projects();
if (projects.size() == 1)
searchPath = projects.first()->projectDirectory();
}
@@ -282,7 +284,7 @@ void StateListener::slotStateChanged()
IVersionControl *projectControl = nullptr;
Project *currentProject = ProjectTree::currentProject();
if (!currentProject)
- currentProject = SessionManager::startupProject();
+ currentProject = ProjectManager::startupProject();
if (currentProject) {
state.currentProjectPath = currentProject->projectDirectory();
diff --git a/src/plugins/vcsbase/vcsbasesubmiteditor.cpp b/src/plugins/vcsbase/vcsbasesubmiteditor.cpp
index eaa825adb10..23a41f363ea 100644
--- a/src/plugins/vcsbase/vcsbasesubmiteditor.cpp
+++ b/src/plugins/vcsbase/vcsbasesubmiteditor.cpp
@@ -37,7 +37,7 @@
#include <texteditor/texteditorsettings.h>
#include <projectexplorer/project.h>
-#include <projectexplorer/session.h>
+#include <projectexplorer/projectmanager.h>
#include <QDir>
#include <QFileInfo>
@@ -606,7 +606,7 @@ void VcsBaseSubmitEditor::filterUntrackedFilesOfProject(const FilePath &reposito
{
for (QStringList::iterator it = untrackedFiles->begin(); it != untrackedFiles->end(); ) {
const FilePath path = repositoryDirectory.resolvePath(*it).absoluteFilePath();
- if (ProjectExplorer::SessionManager::projectForFile(path))
+ if (ProjectExplorer::ProjectManager::projectForFile(path))
++it;
else
it = untrackedFiles->erase(it);
diff --git a/src/plugins/webassembly/webassembly.qbs b/src/plugins/webassembly/webassembly.qbs
index 9c6e95f99f7..b859ea538a2 100644
--- a/src/plugins/webassembly/webassembly.qbs
+++ b/src/plugins/webassembly/webassembly.qbs
@@ -33,9 +33,7 @@ QtcPlugin {
"webassemblytoolchain.h",
]
- Group {
- name: "Unit tests"
- condition: qtc.testsEnabled
+ QtcTestFiles {
files: [
"webassembly_test.cpp",
"webassembly_test.h",
diff --git a/src/plugins/webassembly/webassemblydevice.h b/src/plugins/webassembly/webassemblydevice.h
index 6ff4a2481e7..520d0fb5355 100644
--- a/src/plugins/webassembly/webassemblydevice.h
+++ b/src/plugins/webassembly/webassemblydevice.h
@@ -4,6 +4,7 @@
#pragma once
#include <projectexplorer/devicesupport/desktopdevice.h>
+#include <projectexplorer/devicesupport/idevicefactory.h>
namespace WebAssembly {
namespace Internal {
diff --git a/src/shared/qtsingleapplication/qtlocalpeer.h b/src/shared/qtsingleapplication/qtlocalpeer.h
index bd7f6e9dce0..67a0d42e2c9 100644
--- a/src/shared/qtsingleapplication/qtlocalpeer.h
+++ b/src/shared/qtsingleapplication/qtlocalpeer.h
@@ -1,6 +1,8 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#pragma once
+
#include <qtlockedfile.h>
#include <QLocalServer>
diff --git a/src/shared/qtsingleapplication/qtsingleapplication.h b/src/shared/qtsingleapplication/qtsingleapplication.h
index 3d8e140bb4b..2adbe185426 100644
--- a/src/shared/qtsingleapplication/qtsingleapplication.h
+++ b/src/shared/qtsingleapplication/qtsingleapplication.h
@@ -1,6 +1,8 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+#pragma once
+
#include <QApplication>
QT_FORWARD_DECLARE_CLASS(QSharedMemory)
diff --git a/src/shared/registryaccess/CMakeLists.txt b/src/shared/registryaccess/CMakeLists.txt
index 135df2c501f..634c8c19e60 100644
--- a/src/shared/registryaccess/CMakeLists.txt
+++ b/src/shared/registryaccess/CMakeLists.txt
@@ -3,10 +3,10 @@ if (WIN32)
target_link_libraries(registryaccess PUBLIC advapi32 ole32 shell32 Qt::Widgets)
target_compile_definitions(registryaccess PRIVATE _UNICODE UNICODE)
target_include_directories(registryaccess PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
+ if (WITH_SANITIZE)
+ qtc_enable_sanitize(registryaccess ${SANITIZE_FLAGS})
+ endif()
else()
add_library(registryaccess INTERFACE)
endif()
-if (WITH_SANITIZE)
- qtc_enable_sanitize(registryaccess ${SANITIZE_FLAGS})
-endif()
diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt
index 1bee7cccfbc..2204ac3c5f9 100644
--- a/src/tools/CMakeLists.txt
+++ b/src/tools/CMakeLists.txt
@@ -1,10 +1,9 @@
add_subdirectory(3rdparty)
add_subdirectory(buildoutputparser)
-option(BUILD_CPLUSPLUS_TOOLS "Build CPlusPlus tools" OFF)
-
function(add_qtc_cpp_tool name)
add_qtc_executable(${name}
+ SKIP_INSTALL
DEFINES
PATH_PREPROCESSOR_CONFIG=\"${CMAKE_CURRENT_SOURCE_DIR}/cplusplus-shared/pp-configuration.inc\"
${ARGN}
@@ -17,12 +16,10 @@ function(add_qtc_cpp_tool name)
)
endfunction()
-if (BUILD_CPLUSPLUS_TOOLS)
- add_qtc_cpp_tool(cplusplus-ast2png "")
- add_qtc_cpp_tool(cplusplus-frontend "")
- add_qtc_cpp_tool(cplusplus-mkvisitor PATH_AST_H=\"${CMAKE_CURRENT_SOURCE_DIR}/../libs/3rdparty/cplusplus/AST.h\")
- add_qtc_cpp_tool(cplusplus-update-frontend PATH_CPP_FRONTEND=\"${CMAKE_CURRENT_SOURCE_DIR}/../libs/3rdparty/cplusplus\" PATH_DUMPERS_FILE=\"${CMAKE_CURRENT_SOURCE_DIR}/cplusplus-ast2png/dumpers.inc\")
-endif()
+add_qtc_cpp_tool(cplusplus-ast2png "")
+add_qtc_cpp_tool(cplusplus-frontend "")
+add_qtc_cpp_tool(cplusplus-mkvisitor PATH_AST_H=\"${CMAKE_CURRENT_SOURCE_DIR}/../libs/3rdparty/cplusplus/AST.h\")
+add_qtc_cpp_tool(cplusplus-update-frontend PATH_CPP_FRONTEND=\"${CMAKE_CURRENT_SOURCE_DIR}/../libs/3rdparty/cplusplus\" PATH_DUMPERS_FILE=\"${CMAKE_CURRENT_SOURCE_DIR}/cplusplus-ast2png/dumpers.inc\")
if (APPLE)
add_subdirectory(disclaim)
@@ -44,3 +41,5 @@ add_subdirectory(wininterrupt) ## windows only
if (EXISTS ${CMAKE_CURRENT_LIST_DIR}/perfparser/CMakeLists.txt)
add_subdirectory(perfparser)
endif()
+
+add_subdirectory(process_stub)
diff --git a/src/tools/cplusplus-shared/CPlusPlusTool.qbs b/src/tools/cplusplus-shared/CPlusPlusTool.qbs
index 55c4aff9409..63313e2462f 100644
--- a/src/tools/cplusplus-shared/CPlusPlusTool.qbs
+++ b/src/tools/cplusplus-shared/CPlusPlusTool.qbs
@@ -1,6 +1,7 @@
import qbs 1.0
QtcTool {
+ install: false
Depends { name: "Qt"; submodules: ["core", "widgets"]; }
Depends { name: "CPlusPlus" }
Depends { name: "Utils" }
diff --git a/src/tools/cplusplustools.qbs b/src/tools/cplusplustools.qbs
index d67d5583309..ab3aa517072 100644
--- a/src/tools/cplusplustools.qbs
+++ b/src/tools/cplusplustools.qbs
@@ -3,7 +3,6 @@ import qbs.Environment
Project {
name: "CPlusPlus Tools"
- condition: Environment.getEnv("BUILD_CPLUSPLUS_TOOLS")
references: [
"3rdparty/cplusplus-keywordgen/cplusplus-keywordgen.qbs",
"cplusplus-ast2png/cplusplus-ast2png.qbs",
diff --git a/src/tools/icons/qtcreatoricons.svg b/src/tools/icons/qtcreatoricons.svg
index 8e6a6baa2d9..015498217bf 100644
--- a/src/tools/icons/qtcreatoricons.svg
+++ b/src/tools/icons/qtcreatoricons.svg
@@ -653,6 +653,14 @@
width="100%"
height="100%"
transform="matrix(1.5,0,0,1.5,0,-242)" />
+ <use
+ x="0"
+ y="0"
+ xlink:href="#backgroundRect"
+ id="backgroundRect_12"
+ width="100%"
+ height="100%"
+ transform="matrix(0.75,0,0,0.75,-24,97)" />
<rect
y="452"
x="-16"
@@ -2965,7 +2973,7 @@
<use
height="100%"
width="100%"
- transform="matrix(-1,0,0,1,2336,0)"
+ transform="matrix(-1,0,0,1,2338,0)"
id="use6797"
xlink:href="#g6795"
y="0"
@@ -3148,6 +3156,64 @@
sodipodi:nodetypes="cccccc" />
</g>
<g
+ id="src/plugins/terminal/images/settingscategory_terminal">
+ <use
+ x="0"
+ y="0"
+ xlink:href="#backgroundRect_24"
+ id="use4838"
+ width="100%"
+ height="100%"
+ transform="translate(1115,64)"
+ style="display:inline;fill:none" />
+ <rect
+ style="fill:none;stroke:#000000;stroke-opacity:1.0"
+ id="rect2040"
+ width="15"
+ height="12"
+ x="1095.5"
+ y="481.5" />
+ <path
+ id="path5004"
+ style="fill:none;stroke:#000000;stroke-opacity:1.0"
+ d="m 1100,488.5 h 3 m -5.75,-5.25 2.25,2.25 -2.25,2.25" />
+ </g>
+ <g
+ id="src/plugins/copilot/images/settingscategory_copilot">
+ <use
+ x="0"
+ y="0"
+ xlink:href="#backgroundRect_24"
+ id="use4838-1"
+ width="100%"
+ height="100%"
+ transform="translate(1141,64)" />
+ <path
+ id="path3678"
+ d="m 1133.4931,482.92349 c 1.2309,-7.2e-4 2.6907,0.20509 3.4049,1.34287 0.832,1.39305 0.4718,3.51373 -1.0108,4.32399 -1.1526,0.68867 -2.5252,0.56507 -3.8119,0.57709 0,1.32708 0,2.65416 0,3.98124 -0.4296,0 -0.8592,0 -1.2888,0 0,-3.4084 0,-6.81679 0,-10.22519 0.9022,0 1.8044,0 2.7066,0 z m -0.1145,1.10271 c -0.4344,0 -0.8689,0 -1.3033,0 0,1.34618 0,2.69235 0,4.03853 1.1413,-0.0225 2.414,0.13991 3.3757,-0.60104 0.6576,-0.63013 0.6991,-1.73406 0.3022,-2.5149 -0.4777,-0.81039 -1.5253,-0.91406 -2.3746,-0.92259 z m -7.2608,-0.11456 c -1.2534,-0.0527 -2.5094,0.63954 -3.049,1.78756 -0.6219,1.30647 -0.619,2.86103 -0.2119,4.23045 0.3167,1.06743 1.2059,1.9627 2.3249,2.14365 1.1265,0.21498 2.2861,0.0103 3.3706,-0.31376 0,0.37235 0,0.74469 0,1.11704 -1.2924,0.46392 -2.7269,0.53819 -4.0672,0.2542 -1.5087,-0.32824 -2.6766,-1.61705 -3.0215,-3.09988 -0.4203,-1.74943 -0.3686,-3.7411 0.6344,-5.29133 0.8809,-1.36394 2.5502,-2.02668 4.1347,-1.9587 0.9342,0.01 1.8806,0.17957 2.7206,0.60089 -0.1719,0.3628 -0.3437,0.7256 -0.5156,1.0884 -0.7312,-0.3223 -1.5123,-0.57452 -2.32,-0.55852 z" />
+ </g>
+ <g
+ id="src/plugins/haskell/images/settingscategory_haskell">
+ <use
+ x="0"
+ y="0"
+ xlink:href="#backgroundRect_24"
+ id="use5402"
+ width="100%"
+ height="100%"
+ transform="translate(1166,64)" />
+ <path
+ style="fill:#000000;fill-opacity:1"
+ d="m 1150,482 h 2.5 l 8.67,13 h -2.5 l -3.085,-4.625 L 1152.51,495 h -2.5 l 4.335,-6.5 z"
+ id="path7771"
+ sodipodi:nodetypes="ccccccccc" />
+ <path
+ id="path10150-5"
+ style="fill:#606060;fill-opacity:1"
+ d="m 1160,491 h 2 v -2 h -3.335 z m -2,-3 h 4 v -2 h -5.335 z m -9.5,-6 4.335,6.5 -4.335,6.5 h -2.5 l 4.335,-6.5 L 1146,482 Z"
+ sodipodi:nodetypes="ccccccccccccccccc" />
+ </g>
+ <g
id="src/plugins/valgrind/images/kcachegrind"
transform="translate(112)">
<use
@@ -3484,6 +3550,22 @@
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
</g>
+ <g
+ id="src/plugins/terminal/images/terminal">
+ <use
+ style="display:inline"
+ transform="translate(1948,132)"
+ height="100%"
+ width="100%"
+ id="use1987"
+ xlink:href="#backgroundRect"
+ y="0"
+ x="0" />
+ <path
+ id="path5004-3"
+ style="fill:none;stroke:#000000"
+ d="m 1936.25,571.25 2.25,2.25 -2.25,2.25 m -2.75,-6.25 h 8 v 8 h -8 z" />
+ </g>
</g>
<g
inkscape:groupmode="layer"
@@ -6453,6 +6535,27 @@
d="m 347,597.33936 0.60019,0.60645 3.49128,-3.49127 3.09695,3.09695 c 0.29088,-0.96173 0.20375,-2.0863 -0.1875,-3.07155 l 3.68179,-3.68179 0.55968,0.52841 0.75761,-0.75762 -4.51473,-4.51475 -0.80193,0.80194 0.53076,0.51509 -3.60205,3.60206 c -1.04768,-0.37523 -2.22553,-0.48941 -3.20237,-0.15625 l 3.0615,3.0615 z"
style="fill:#000000" />
</g>
+ <g
+ id="src/libs/utils/images/pinned_small">
+ <use
+ style="display:inline"
+ x="0"
+ y="0"
+ xlink:href="#backgroundRect_12"
+ id="use1988"
+ width="100%"
+ height="100%"
+ transform="translate(397,160)" />
+ <g
+ id="g6007"
+ transform="rotate(45,366.14645,589.64645)">
+ <path
+ style="fill:#000000;fill-opacity:1"
+ d="m 369.5,585 v 1 h -1 l 0.5,3 h 1 v 1 h -2.5 l -0.2,5 h -0.6 l -0.2,-5 H 364 v -1 h 1 l 0.5,-3 h -1 v -1 z"
+ id="path5401"
+ sodipodi:nodetypes="ccccccccccccccccc" />
+ </g>
+ </g>
</g>
<g
inkscape:groupmode="layer"
@@ -9251,6 +9354,20 @@
sodipodi:nodetypes="ccccccccccccc" />
</g>
<g
+ id="src/libs/utils/images/iconoverlay_close_small">
+ <rect
+ id="rect5336"
+ height="16"
+ width="16"
+ y="363"
+ x="649"
+ style="fill:#ffffff" />
+ <path
+ id="path4779-7"
+ style="stroke:#000000;stroke-width:2"
+ d="m 656.5,370.5 7,7 m -7,0 7,-7" />
+ </g>
+ <g
id="src/libs/utils/images/iconoverlay_add"
transform="translate(112)">
<rect
diff --git a/src/tools/perfparser b/src/tools/perfparser
-Subproject ac05977da7134f78d9b172271c524a32c317646
+Subproject 5444f96207616f922f3093e9d64bd6000f168c5
diff --git a/src/tools/process_stub/CMakeLists.txt b/src/tools/process_stub/CMakeLists.txt
new file mode 100644
index 00000000000..b8181ad8361
--- /dev/null
+++ b/src/tools/process_stub/CMakeLists.txt
@@ -0,0 +1,12 @@
+
+add_qtc_executable(qtcreator_process_stub
+ DEPENDS Qt::Core Qt::Network
+ SOURCES
+ main.cpp
+)
+
+if (WIN32)
+ extend_qtc_executable(qtcreator_process_stub
+ DEFINES _UNICODE UNICODE _CRT_SECURE_NO_WARNINGS
+ )
+endif()
diff --git a/src/tools/process_stub/main.cpp b/src/tools/process_stub/main.cpp
new file mode 100644
index 00000000000..45ac9c41f9b
--- /dev/null
+++ b/src/tools/process_stub/main.cpp
@@ -0,0 +1,551 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include <QCommandLineParser>
+#include <QCoreApplication>
+#include <QDir>
+#include <QLocalSocket>
+#include <QLoggingCategory>
+#include <QMutex>
+#include <QProcess>
+#include <QSocketNotifier>
+#include <QThread>
+#include <QTimer>
+#include <QWinEventNotifier>
+
+#ifdef Q_OS_WIN
+#include <windows.h>
+#else
+#include <optional>
+#include <signal.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#endif
+
+#ifdef Q_OS_LINUX
+#include <sys/ptrace.h>
+#endif
+
+#include <iostream>
+
+Q_LOGGING_CATEGORY(log, "qtc.process_stub", QtWarningMsg);
+
+// Global variables
+
+QCommandLineParser commandLineParser;
+
+// The inferior command and arguments
+QStringList inferiorCmdAndArguments;
+// Whether to Suspend the inferior process on startup (to allow a debugger to attach)
+bool debugMode = false;
+// Whether to run in test mode (i.e. to start manually from the command line)
+bool testMode = false;
+// The control socket used to communicate with Qt Creator
+QLocalSocket controlSocket;
+// Environment variables to set for the inferior process
+std::optional<QStringList> environmentVariables;
+
+QProcess inferiorProcess;
+int inferiorId{0};
+
+#ifndef Q_OS_WIN
+
+#ifdef Q_OS_DARWIN
+// A memory mapped helper to retrieve the pid of the inferior process in debugMode
+static int *shared_child_pid = nullptr;
+#endif
+
+using OSSocketNotifier = QSocketNotifier;
+#else
+Q_PROCESS_INFORMATION *win_process_information = nullptr;
+using OSSocketNotifier = QWinEventNotifier;
+#endif
+// Helper to read a single character from stdin in testMode
+OSSocketNotifier *stdInNotifier;
+
+QThread processThread;
+
+// Helper to create the shared memory mapped segment
+void setupSharedPid();
+// Parses the command line, returns a status code in case of error
+std::optional<int> tryParseCommandLine(QCoreApplication &app);
+// Sets the working directory, returns a status code in case of error
+std::optional<int> trySetWorkingDir();
+// Reads the environment variables from the env file, returns a status code in case of error
+std::optional<int> readEnvFile();
+
+void setupControlSocket();
+void setupSignalHandlers();
+void startProcess(const QString &program, const QStringList &arguments, const QString &workingDir);
+void readKey();
+void sendSelfPid();
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication a(argc, argv);
+
+ setupSharedPid();
+
+ auto error = tryParseCommandLine(a);
+ if (error)
+ return error.value();
+
+ qCInfo(log) << "Debug helper started: ";
+ qCInfo(log) << "Socket:" << commandLineParser.value("socket");
+ qCInfo(log) << "Inferior:" << inferiorCmdAndArguments.join(QChar::Space);
+ qCInfo(log) << "Working Directory" << commandLineParser.value("workingDir");
+ qCInfo(log) << "Env file:" << commandLineParser.value("envfile");
+ qCInfo(log) << "Mode:"
+ << QLatin1String(testMode ? "test | " : "")
+ + QLatin1String(debugMode ? "debug" : "run");
+
+ error = trySetWorkingDir();
+ if (error)
+ return error.value();
+
+ error = readEnvFile();
+ if (error)
+ return error.value();
+
+ if (testMode) {
+ sendSelfPid();
+ setupSignalHandlers();
+
+ startProcess(inferiorCmdAndArguments[0],
+ inferiorCmdAndArguments.mid(1),
+ commandLineParser.value("workingDir"));
+
+ if (debugMode) {
+ qDebug() << "Press 'c' to continue or 'k' to kill, followed by 'enter'";
+ readKey();
+ }
+
+ return a.exec();
+ }
+
+ setupControlSocket();
+
+ return a.exec();
+}
+
+void sendMsg(const QByteArray &msg)
+{
+ if (controlSocket.state() == QLocalSocket::ConnectedState) {
+ controlSocket.write(msg);
+ } else {
+ qDebug() << "MSG:" << msg;
+ }
+}
+
+void sendPid(int inferiorPid)
+{
+ sendMsg(QString("pid %1\n").arg(inferiorPid).toUtf8());
+}
+
+void sendThreadId(int inferiorThreadPid)
+{
+ sendMsg(QString("thread %1\n").arg(inferiorThreadPid).toUtf8());
+}
+
+void sendSelfPid()
+{
+ sendMsg(QString("spid %1\n").arg(QCoreApplication::applicationPid()).toUtf8());
+}
+
+void sendExit(int exitCode)
+{
+ sendMsg(QString("exit %1\n").arg(exitCode).toUtf8());
+}
+
+void sendCrash(int exitCode)
+{
+ sendMsg(QString("crash %1\n").arg(exitCode).toUtf8());
+}
+
+void sendErrChDir()
+{
+ sendMsg(QString("err:chdir %1\n").arg(errno).toUtf8());
+}
+
+void doExit(int exitCode)
+{
+ if (controlSocket.state() == QLocalSocket::ConnectedState && controlSocket.bytesToWrite())
+ controlSocket.waitForBytesWritten(1000);
+
+ if (!commandLineParser.value("wait").isEmpty()) {
+ std::cout << commandLineParser.value("wait").toStdString();
+ std::cin.get();
+ }
+
+ exit(exitCode);
+}
+
+void onInferiorFinished(int exitCode, QProcess::ExitStatus status)
+{
+ qCInfo(log) << "Inferior finished";
+
+ if (status == QProcess::CrashExit) {
+ sendCrash(exitCode);
+ doExit(exitCode);
+ } else {
+ sendExit(exitCode);
+ doExit(exitCode);
+ }
+}
+
+void onInferiorErrorOccurered(QProcess::ProcessError error)
+{
+ qCInfo(log) << "Inferior error: " << error << inferiorProcess.errorString();
+ sendCrash(inferiorProcess.exitCode());
+ doExit(1);
+}
+
+void onInferiorStarted()
+{
+ inferiorId = inferiorProcess.processId();
+ qCInfo(log) << "Inferior started ( pid:" << inferiorId << ")";
+#ifdef Q_OS_WIN
+ sendThreadId(win_process_information->dwThreadId);
+ sendPid(inferiorId);
+#elif defined(Q_OS_DARWIN)
+ // In debug mode we use the poll timer to send the pid.
+ if (!debugMode)
+ sendPid(inferiorId);
+#else
+ ptrace(PTRACE_DETACH, inferiorId, 0, SIGSTOP);
+ sendPid(inferiorId);
+#endif
+}
+
+void setupUnixInferior()
+{
+#ifndef Q_OS_WIN
+ if (debugMode) {
+ qCInfo(log) << "Debug mode enabled";
+#ifdef Q_OS_DARWIN
+ // We are using raise(SIGSTOP) to stop the child process, macOS does not support ptrace(...)
+ inferiorProcess.setChildProcessModifier([] {
+ // Let the parent know our pid ...
+ *shared_child_pid = getpid();
+ // Suspend ourselves ...
+ raise(SIGSTOP);
+ });
+#else
+ // PTRACE_TRACEME will stop execution of the child process as soon as execve is called.
+ inferiorProcess.setChildProcessModifier([] { ptrace(PTRACE_TRACEME, 0, 0, 0); });
+#endif
+ }
+#endif
+}
+
+void setupWindowsInferior()
+{
+#ifdef Q_OS_WIN
+ inferiorProcess.setCreateProcessArgumentsModifier([](QProcess::CreateProcessArguments *args) {
+ if (debugMode)
+ args->flags |= CREATE_SUSPENDED;
+ win_process_information = args->processInformation;
+ });
+#endif
+}
+
+void setupPidPollTimer()
+{
+#ifdef Q_OS_DARWIN
+ if (!debugMode)
+ return;
+
+ static QTimer pollPidTimer;
+
+ pollPidTimer.setInterval(1);
+ pollPidTimer.setSingleShot(false);
+ QObject::connect(&pollPidTimer, &QTimer::timeout, &pollPidTimer, [&] {
+ if (*shared_child_pid) {
+ qCInfo(log) << "Received pid during polling:" << *shared_child_pid;
+ inferiorId = *shared_child_pid;
+ sendPid(inferiorId);
+ pollPidTimer.stop();
+ munmap(shared_child_pid, sizeof(int));
+ } else {
+ qCDebug(log) << "Waiting for inferior to start...";
+ }
+ });
+ pollPidTimer.start();
+#endif
+}
+void startProcess(const QString &executable, const QStringList &arguments, const QString &workingDir)
+{
+ setupPidPollTimer();
+
+ qCInfo(log) << "Starting Inferior";
+
+ QObject::connect(&inferiorProcess,
+ &QProcess::finished,
+ QCoreApplication::instance(),
+ &onInferiorFinished);
+ QObject::connect(&inferiorProcess,
+ &QProcess::errorOccurred,
+ QCoreApplication::instance(),
+ &onInferiorErrorOccurered);
+ QObject::connect(&inferiorProcess,
+ &QProcess::started,
+ QCoreApplication::instance(),
+ &onInferiorStarted);
+
+ inferiorProcess.setProcessChannelMode(QProcess::ForwardedChannels);
+ if (!(testMode && debugMode))
+ inferiorProcess.setInputChannelMode(QProcess::ForwardedInputChannel);
+ inferiorProcess.setWorkingDirectory(workingDir);
+ inferiorProcess.setProgram(executable);
+ inferiorProcess.setArguments(arguments);
+
+ if (environmentVariables)
+ inferiorProcess.setEnvironment(*environmentVariables);
+
+ setupWindowsInferior();
+ setupUnixInferior();
+
+ inferiorProcess.start();
+}
+
+std::optional<int> readEnvFile()
+{
+ if (!commandLineParser.isSet("envfile"))
+ return std::nullopt;
+
+ const QString path = commandLineParser.value("envfile");
+ qCInfo(log) << "Reading env file: " << path << "...";
+ QFile file(path);
+ if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ qCWarning(log) << "Failed to open env file: " << path;
+ return 1;
+ }
+
+ environmentVariables = QStringList{};
+
+ while (!file.atEnd()) {
+ QByteArray data = file.readAll();
+ if (!data.isEmpty()) {
+ for (const auto &line : data.split('\0')) {
+ if (!line.isEmpty())
+ *environmentVariables << QString::fromUtf8(line);
+ }
+ }
+ }
+
+ qCDebug(log) << "Env: ";
+ for (const auto &env : *environmentVariables)
+ qCDebug(log) << env;
+
+ return std::nullopt;
+}
+
+#ifndef Q_OS_WIN
+void forwardSignal(int signum)
+{
+ qCDebug(log) << "SIGTERM received, terminating inferior...";
+ kill(inferiorId, signum);
+}
+#else
+static BOOL WINAPI ctrlHandler(DWORD dwCtrlType)
+{
+ if (dwCtrlType == CTRL_C_EVENT || dwCtrlType == CTRL_BREAK_EVENT) {
+ qCDebug(log) << "Terminate inferior...";
+ inferiorProcess.terminate();
+ return TRUE;
+ }
+ return FALSE;
+}
+#endif
+
+void setupSignalHandlers()
+{
+#ifdef Q_OS_WIN
+ SetConsoleCtrlHandler(ctrlHandler, TRUE);
+#else
+ struct sigaction act;
+ memset(&act, 0, sizeof(act));
+
+ act.sa_handler = SIG_IGN;
+ if (sigaction(SIGTTOU, &act, NULL)) {
+ qCWarning(log) << "sigaction SIGTTOU: " << strerror(errno);
+ doExit(3);
+ }
+
+ act.sa_handler = forwardSignal;
+ if (sigaction(SIGTERM, &act, NULL)) {
+ qCWarning(log) << "sigaction SIGTERM: " << strerror(errno);
+ doExit(3);
+ }
+
+ if (sigaction(SIGINT, &act, NULL)) {
+ qCWarning(log) << "sigaction SIGINT: " << strerror(errno);
+ doExit(3);
+ }
+
+ qCDebug(log) << "Signals set up";
+#endif
+}
+
+std::optional<int> tryParseCommandLine(QCoreApplication &app)
+{
+ commandLineParser.setApplicationDescription("Debug helper for QtCreator");
+ commandLineParser.addHelpOption();
+ commandLineParser.addOption(QCommandLineOption({"d", "debug"}, "Start inferior in debug mode"));
+ commandLineParser.addOption(QCommandLineOption({"t", "test"}, "Don't start the control socket"));
+ commandLineParser.addOption(
+ QCommandLineOption({"s", "socket"}, "Path to the unix socket", "socket"));
+ commandLineParser.addOption(
+ QCommandLineOption({"w", "workingDir"}, "Working directory for inferior", "workingDir"));
+ commandLineParser.addOption(QCommandLineOption({"v", "verbose"}, "Print debug messages"));
+ commandLineParser.addOption(QCommandLineOption({"e", "envfile"}, "Path to env file", "envfile"));
+ commandLineParser.addOption(
+ QCommandLineOption("wait",
+ "Message to display to the user while waiting for key press",
+ "waitmessage",
+ "Press enter to continue ..."));
+
+ commandLineParser.process(app);
+
+ inferiorCmdAndArguments = commandLineParser.positionalArguments();
+ debugMode = commandLineParser.isSet("debug");
+ testMode = commandLineParser.isSet("test");
+
+ if (!(commandLineParser.isSet("socket") || testMode) || inferiorCmdAndArguments.isEmpty()) {
+ commandLineParser.showHelp(1);
+ return 1;
+ }
+
+ if (commandLineParser.isSet("verbose"))
+ QLoggingCategory::setFilterRules("qtc.process_stub=true");
+
+ return std::nullopt;
+}
+
+std::optional<int> trySetWorkingDir()
+{
+ if (commandLineParser.isSet("workingDir")) {
+ if (!QDir::setCurrent(commandLineParser.value("workingDir"))) {
+ qCWarning(log) << "Failed to change working directory to: "
+ << commandLineParser.value("workingDir");
+ sendErrChDir();
+ return 1;
+ }
+ }
+
+ return std::nullopt;
+}
+
+void setupSharedPid()
+{
+#ifdef Q_OS_DARWIN
+ shared_child_pid = (int *) mmap(NULL,
+ sizeof *shared_child_pid,
+ PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANONYMOUS,
+ -1,
+ 0);
+ *shared_child_pid = 0;
+#endif
+}
+
+void onControlSocketConnected()
+{
+ qCInfo(log) << "Connected to control socket";
+
+ sendSelfPid();
+ setupSignalHandlers();
+
+ startProcess(inferiorCmdAndArguments[0],
+ inferiorCmdAndArguments.mid(1),
+ commandLineParser.value("workingDir"));
+}
+
+void resumeInferior()
+{
+ qCDebug(log) << "Continuing inferior... (" << inferiorId << ")";
+#ifdef Q_OS_WIN
+ ResumeThread(win_process_information->hThread);
+#else
+ kill(inferiorId, SIGCONT);
+#endif
+}
+
+void killInferior()
+{
+#ifdef Q_OS_WIN
+ inferiorProcess.kill();
+#else
+ kill(inferiorId, SIGKILL);
+#endif
+}
+
+void onControlSocketReadyRead()
+{
+ //k = kill, i = interrupt, c = continue, s = shutdown
+ QByteArray data = controlSocket.readAll();
+ for (auto ch : data) {
+ qCDebug(log) << "Received:" << ch;
+
+ switch (ch) {
+ case 'k': {
+ qCDebug(log) << "Killing inferior...";
+ killInferior();
+ break;
+ }
+#ifndef Q_OS_WIN
+ case 'i': {
+ qCDebug(log) << "Interrupting inferior...";
+ kill(inferiorId, SIGINT);
+ break;
+ }
+#endif
+ case 'c': {
+ resumeInferior();
+ break;
+ }
+ case 's': {
+ qCDebug(log) << "Shutting down...";
+ doExit(0);
+ break;
+ }
+ }
+ }
+}
+
+void onControlSocketErrorOccurred(QLocalSocket::LocalSocketError socketError)
+{
+ qCWarning(log) << "Control socket error:" << socketError;
+ doExit(1);
+}
+
+void setupControlSocket()
+{
+ QObject::connect(&controlSocket, &QLocalSocket::connected, &onControlSocketConnected);
+ QObject::connect(&controlSocket, &QLocalSocket::readyRead, &onControlSocketReadyRead);
+ QObject::connect(&controlSocket, &QLocalSocket::errorOccurred, &onControlSocketErrorOccurred);
+
+ qCInfo(log) << "Waiting for connection...";
+ controlSocket.connectToServer(commandLineParser.value("socket"));
+}
+
+void onStdInReadyRead()
+{
+ char ch;
+ std::cin >> ch;
+ if (ch == 'k') {
+ killInferior();
+ } else {
+ resumeInferior();
+ }
+}
+
+void readKey()
+{
+#ifdef Q_OS_WIN
+ stdInNotifier = new QWinEventNotifier(GetStdHandle(STD_INPUT_HANDLE));
+#else
+ stdInNotifier = new QSocketNotifier(fileno(stdin), QSocketNotifier::Read);
+#endif
+ QObject::connect(stdInNotifier, &OSSocketNotifier::activated, &onStdInReadyRead);
+}
diff --git a/src/tools/process_stub/process_stub.qbs b/src/tools/process_stub/process_stub.qbs
new file mode 100644
index 00000000000..1fa1bc32a3f
--- /dev/null
+++ b/src/tools/process_stub/process_stub.qbs
@@ -0,0 +1,10 @@
+import qbs 1.0
+
+QtcTool {
+ name: "qtcreator_process_stub"
+ consoleApplication: true
+
+ Depends { name: "Qt"; submodules: ["core", "network"]; }
+
+ files: [ "main.cpp" ]
+}
diff --git a/src/tools/processlauncher/launchersockethandler.cpp b/src/tools/processlauncher/launchersockethandler.cpp
index 21167f8d773..a0a2733ded1 100644
--- a/src/tools/processlauncher/launchersockethandler.cpp
+++ b/src/tools/processlauncher/launchersockethandler.cpp
@@ -172,6 +172,7 @@ void LauncherSocketHandler::handleStartPacket()
const auto packet = LauncherPacket::extractPacket<StartProcessPacket>(
m_packetParser.token(),
m_packetParser.packetData());
+
process->setEnvironment(packet.env);
process->setWorkingDirectory(packet.workingDir);
// Forwarding is handled by the LauncherInterface
@@ -179,10 +180,10 @@ void LauncherSocketHandler::handleStartPacket()
? QProcess::MergedChannels : QProcess::SeparateChannels);
process->setStandardInputFile(packet.standardInputFile);
ProcessStartHandler *handler = process->processStartHandler();
+ handler->setWindowsSpecificStartupFlags(packet.belowNormalPriority,
+ packet.createConsoleOnWindows);
handler->setProcessMode(packet.processMode);
handler->setWriteData(packet.writeData);
- if (packet.belowNormalPriority)
- handler->setBelowNormalPriority();
handler->setNativeArguments(packet.nativeArguments);
if (packet.lowPriority)
process->setLowPriority();
diff --git a/src/tools/qml2puppet/CMakeLists.txt b/src/tools/qml2puppet/CMakeLists.txt
index 75c9dd9ce5b..4543091ab71 100644
--- a/src/tools/qml2puppet/CMakeLists.txt
+++ b/src/tools/qml2puppet/CMakeLists.txt
@@ -29,8 +29,6 @@ else()
set(QT_VERSION_MAJOR ${Qt6_VERSION_MAJOR})
endif()
-configure_file(../../app/app_version.h.cmakein app/app_version.h ESCAPE_QUOTES)
-
if (NOT TARGET QmlPuppetCommunication)
include(../../libs/qmlpuppetcommunication/QmlPuppetCommunication.cmake)
endif()
@@ -46,28 +44,32 @@ add_qtc_executable(qml2puppet
${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}
SOURCES
qml2puppet/main.cpp
- qml2puppet/qmlbase.h qml2puppet/appmetadata.h
+ qml2puppet/qmlbase.h
+ qml2puppet/appmetadata.cpp qml2puppet/appmetadata.h
qml2puppet/qmlpuppet.h qml2puppet/qmlpuppet.cpp qml2puppet/configcrashpad.h
qmlpuppet.qrc
PROPERTIES
OUTPUT_NAME qml2puppet-${IDE_VERSION}
)
-if(TARGET qml2puppet)
+if (TARGET qml2puppet)
execute_process(
COMMAND git describe --tags --always --dirty=+
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
RESULT_VARIABLE GIT_SHA_RESULT
- OUTPUT_VARIABLE GIT_SHA_OUTPUT
+ OUTPUT_VARIABLE GIT_SHA
ERROR_VARIABLE GIT_SHA_ERROR
+ OUTPUT_STRIP_TRAILING_WHITESPACE
)
#if we are not a git repository use the .tag file
- if(NOT GIT_SHA_OUTPUT)
- file(READ ${CMAKE_CURRENT_SOURCE_DIR}/../../../.tag GIT_SHA_OUTPUT)
+ if(NOT GIT_SHA)
+ file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/../../../.tag GIT_SHA LIMIT_COUNT 1)
endif()
- add_definitions( -D GIT_SHA=${GIT_SHA_OUTPUT} )
+ set(IDE_REVISION_STR ${GIT_SHA})
+
+ configure_file(../../app/app_version.h.cmakein app/app_version.h ESCAPE_QUOTES)
endif()
extend_qtc_executable(qml2puppet
diff --git a/src/tools/qml2puppet/qml2puppet/appmetadata.cpp b/src/tools/qml2puppet/qml2puppet/appmetadata.cpp
new file mode 100644
index 00000000000..1896e4db926
--- /dev/null
+++ b/src/tools/qml2puppet/qml2puppet/appmetadata.cpp
@@ -0,0 +1,51 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
+
+#include "appmetadata.h"
+
+#include <app/app_version.h>
+
+namespace QDSMeta::AppInfo {
+
+void printAppInfo()
+{
+ qInfo() << Qt::endl
+ << "<< QDS Meta Info >>" << Qt::endl
+ << "App Info" << Qt::endl
+ << " - Name :" << Core::Constants::IDE_ID << Qt::endl
+ << " - Version :" << Core::Constants::IDE_VERSION_DISPLAY << Qt::endl
+ << " - Author :" << Core::Constants::IDE_AUTHOR << Qt::endl
+ << " - Year :" << Core::Constants::IDE_YEAR << Qt::endl
+ << " - App :" << QCoreApplication::applicationName() << Qt::endl
+ << "Build Info " << Qt::endl
+ << " - Date :" << __DATE__ << Qt::endl
+ << " - Commit :" << QStringLiteral(QDS_STRINGIFY(IDE_REVISION_STR)) << Qt::endl
+ << " - Qt Version :" << QT_VERSION_STR << Qt::endl
+ << "Compiler Info " << Qt::endl
+#if defined(__GNUC__)
+ << " - GCC :" << __GNUC__ << Qt::endl
+ << " - GCC Minor :" << __GNUC_MINOR__ << Qt::endl
+ << " - GCC Patch :" << __GNUC_PATCHLEVEL__ << Qt::endl
+#endif
+#if defined(_MSC_VER)
+ << " - MSC Short :" << _MSC_VER << Qt::endl
+ << " - MSC Full :" << _MSC_FULL_VER << Qt::endl
+#endif
+#if defined(__clang__)
+ << " - clang maj :" << __clang_major__ << Qt::endl
+ << " - clang min :" << __clang_minor__ << Qt::endl
+ << " - clang patch :" << __clang_patchlevel__ << Qt::endl
+#endif
+ << "<< End Of QDS Meta Info >>" << Qt::endl;
+ exit(0);
+}
+
+void registerAppInfo(const QString &appName)
+{
+ QCoreApplication::setOrganizationName(Core::Constants::IDE_AUTHOR);
+ QCoreApplication::setOrganizationDomain("qt-project.org");
+ QCoreApplication::setApplicationName(appName);
+ QCoreApplication::setApplicationVersion(Core::Constants::IDE_VERSION_LONG);
+}
+
+} // namespace QDSMeta::AppInfo
diff --git a/src/tools/qml2puppet/qml2puppet/appmetadata.h b/src/tools/qml2puppet/qml2puppet/appmetadata.h
index d134135fd8f..18eb650461e 100644
--- a/src/tools/qml2puppet/qml2puppet/appmetadata.h
+++ b/src/tools/qml2puppet/qml2puppet/appmetadata.h
@@ -5,8 +5,6 @@
#include <QCommandLineParser>
#include <QLoggingCategory>
-#include <app/app_version.h>
-
// Common functions can be used in all QDS apps
namespace QDSMeta {
@@ -50,46 +48,8 @@ namespace AppInfo {
#define STRINGIFY_INTERNAL(x) #x
#define QDS_STRINGIFY(x) STRINGIFY_INTERNAL(x)
-inline void printAppInfo()
-{
- qInfo() << Qt::endl
- << "<< QDS Meta Info >>" << Qt::endl
- << "App Info" << Qt::endl
- << " - Name :" << Core::Constants::IDE_ID << Qt::endl
- << " - Version :" << Core::Constants::IDE_VERSION_DISPLAY << Qt::endl
- << " - Author :" << Core::Constants::IDE_AUTHOR << Qt::endl
- << " - Year :" << Core::Constants::IDE_YEAR << Qt::endl
- << " - App :" << QCoreApplication::applicationName() << Qt::endl
- << "Build Info " << Qt::endl
- << " - Date :" << __DATE__ << Qt::endl
- << " - Commit :" << QStringLiteral(QDS_STRINGIFY(GIT_SHA)) << Qt::endl
- << " - Qt Version :" << QT_VERSION_STR << Qt::endl
- << "Compiler Info " << Qt::endl
-#if defined(__GNUC__)
- << " - GCC :" << __GNUC__ << Qt::endl
- << " - GCC Minor :" << __GNUC_MINOR__ << Qt::endl
- << " - GCC Patch :" << __GNUC_PATCHLEVEL__ << Qt::endl
-#endif
-#if defined(_MSC_VER)
- << " - MSC Short :" << _MSC_VER << Qt::endl
- << " - MSC Full :" << _MSC_FULL_VER << Qt::endl
-#endif
-#if defined(__clang__)
- << " - clang maj :" << __clang_major__ << Qt::endl
- << " - clang min :" << __clang_minor__ << Qt::endl
- << " - clang patch :" << __clang_patchlevel__ << Qt::endl
-#endif
- << "<< End Of QDS Meta Info >>" << Qt::endl;
- exit(0);
-}
-
-inline void registerAppInfo(const QString &appName)
-{
- QCoreApplication::setOrganizationName(Core::Constants::IDE_AUTHOR);
- QCoreApplication::setOrganizationDomain("qt-project.org");
- QCoreApplication::setApplicationName(appName);
- QCoreApplication::setApplicationVersion(Core::Constants::IDE_VERSION_LONG);
-}
+void printAppInfo();
+void registerAppInfo(const QString &appName);
} // namespace AppInfo
} // namespace QDSMeta
diff --git a/src/tools/qtcdebugger/main.cpp b/src/tools/qtcdebugger/main.cpp
index 5aee7b6fe91..cbb4b935d72 100644
--- a/src/tools/qtcdebugger/main.cpp
+++ b/src/tools/qtcdebugger/main.cpp
@@ -520,7 +520,7 @@ int main(int argc, char *argv[])
}
if (debug)
qDebug() << "Mode=" << optMode << " PID=" << argProcessId << " Evt=" << argWinCrashEvent;
- bool ex = 0;
+ int ex = 0;
switch (optMode) {
case HelpMode:
usage(QCoreApplication::applicationFilePath(), errorMessage);
diff --git a/src/tools/sdktool/CMakeLists.txt b/src/tools/sdktool/CMakeLists.txt
index 4adf618b845..937aa04087a 100644
--- a/src/tools/sdktool/CMakeLists.txt
+++ b/src/tools/sdktool/CMakeLists.txt
@@ -66,33 +66,7 @@ add_qtc_library(sdktoolLib
rmqtoperation.cpp rmqtoperation.h
rmtoolchainoperation.cpp rmtoolchainoperation.h
settings.cpp settings.h
-)
-
-extend_qtc_library(sdktoolLib
- SOURCES_PREFIX "${UtilsSourcesDir}"
- PUBLIC_DEFINES UTILS_STATIC_LIBRARY
- SOURCES
- commandline.cpp commandline.h
- devicefileaccess.cpp devicefileaccess.h
- environment.cpp environment.h
- filepath.cpp filepath.h
- fileutils.cpp fileutils.h
- hostosinfo.cpp hostosinfo.h
- macroexpander.cpp macroexpander.h
- namevaluedictionary.cpp namevaluedictionary.h
- namevalueitem.cpp namevalueitem.h
- persistentsettings.cpp persistentsettings.h
- qtcassert.cpp qtcassert.h
- savefile.cpp savefile.h
- stringutils.cpp stringutils.h
-)
-
-extend_qtc_library(sdktoolLib CONDITION APPLE
- SOURCES_PREFIX "${UtilsSourcesDir}"
- SOURCES
- fileutils_mac.mm fileutils_mac.h
- PUBLIC_DEPENDS
- ${FWFoundation}
+ sdkpersistentsettings.cpp sdkpersistentsettings.h
)
if (MSVC)
@@ -120,7 +94,7 @@ add_qtc_executable(sdktool
main.cpp
)
-if (MSVC AND TARGET sdktool AND Qt5_VERSION VERSION_LESS 6.0.0)
+if (MSVC AND TARGET sdktool AND TARGET Qt5::Core)
# find out if Qt is static and set /MT if so
get_target_property(_input_type Qt5::Core TYPE)
if (${_input_type} STREQUAL "STATIC_LIBRARY")
diff --git a/src/tools/sdktool/addcmakeoperation.cpp b/src/tools/sdktool/addcmakeoperation.cpp
index 751db965e82..f97fcef2a68 100644
--- a/src/tools/sdktool/addcmakeoperation.cpp
+++ b/src/tools/sdktool/addcmakeoperation.cpp
@@ -4,13 +4,10 @@
#include "addcmakeoperation.h"
#include "addkeysoperation.h"
-#include "findkeyoperation.h"
#include "findvalueoperation.h"
#include "getoperation.h"
#include "rmkeysoperation.h"
-#include "settings.h"
-
#ifdef WITH_TESTS
#include <QTest>
#endif
@@ -205,7 +202,7 @@ QVariantMap AddCMakeData::addCMake(const QVariantMap &map) const
data << KeyValuePair({cm, ID_KEY}, QVariant(m_id));
data << KeyValuePair({cm, DISPLAYNAME_KEY}, QVariant(m_displayName));
data << KeyValuePair({cm, AUTODETECTED_KEY}, QVariant(true));
- data << KeyValuePair({cm, PATH_KEY}, Utils::FilePath::fromUserInput(m_path).toVariant());
+ data << KeyValuePair({cm, PATH_KEY}, QVariant(m_path));
KeyValuePairList extraList;
for (const KeyValuePair &pair : std::as_const(m_extra))
extraList << KeyValuePair(QStringList({cm}) << pair.key, pair.value);
diff --git a/src/tools/sdktool/adddebuggeroperation.cpp b/src/tools/sdktool/adddebuggeroperation.cpp
index c11b20ec81d..69de58be05e 100644
--- a/src/tools/sdktool/adddebuggeroperation.cpp
+++ b/src/tools/sdktool/adddebuggeroperation.cpp
@@ -222,8 +222,7 @@ QVariantMap AddDebuggerData::addDebugger(const QVariantMap &map) const
data << KeyValuePair(QStringList() << debugger << QLatin1String(ABIS), QVariant(m_abis));
data << KeyValuePair(QStringList() << debugger << QLatin1String(ENGINE_TYPE), QVariant(m_engine));
- data << KeyValuePair(QStringList() << debugger << QLatin1String(BINARY),
- Utils::FilePath::fromUserInput(m_binary).toSettings());
+ data << KeyValuePair(QStringList() << debugger << QLatin1String(BINARY), QVariant(m_binary));
data << KeyValuePair(QStringList() << QLatin1String(COUNT), QVariant(count + 1));
diff --git a/src/tools/sdktool/addkitoperation.cpp b/src/tools/sdktool/addkitoperation.cpp
index 14569ba8398..4f0911d011a 100644
--- a/src/tools/sdktool/addkitoperation.cpp
+++ b/src/tools/sdktool/addkitoperation.cpp
@@ -15,6 +15,7 @@
#include "settings.h"
+#include <QDir>
#include <QLoggingCategory>
#include <QRegularExpression>
@@ -685,8 +686,7 @@ QVariantMap AddKitData::addKit(const QVariantMap &map,
if (!m_buildDevice.isNull())
data << KeyValuePair({kit, DATA, BUILDDEVICE_ID}, QVariant(m_buildDevice));
if (!m_sysRoot.isNull())
- data << KeyValuePair({kit, DATA, SYSROOT},
- Utils::FilePath::fromUserInput(m_sysRoot).toSettings());
+ data << KeyValuePair({kit, DATA, SYSROOT}, QVariant(QDir::cleanPath(m_sysRoot)));
for (auto i = m_tcs.constBegin(); i != m_tcs.constEnd(); ++i)
data << KeyValuePair({kit, DATA, TOOLCHAIN, i.key()}, QVariant(i.value()));
if (!qtId.isNull())
diff --git a/src/tools/sdktool/addqtoperation.cpp b/src/tools/sdktool/addqtoperation.cpp
index 875e59d9143..ee48c1e5ec5 100644
--- a/src/tools/sdktool/addqtoperation.cpp
+++ b/src/tools/sdktool/addqtoperation.cpp
@@ -11,19 +11,16 @@
#include "settings.h"
-#include <utils/filepath.h>
-
#ifdef WITH_TESTS
#include <QTest>
#endif
+#include <QDir>
#include <QLoggingCategory>
#include <QRegularExpression>
Q_LOGGING_CATEGORY(log, "qtc.sdktool.operations.addqt", QtWarningMsg)
-using namespace Utils;
-
// Qt version file stuff:
const char PREFIX[] = "QtVersion.";
const char VERSION[] = "Version";
@@ -282,7 +279,7 @@ QVariantMap AddQtData::addQt(const QVariantMap &map) const
const QString qt = QString::fromLatin1(PREFIX) + QString::number(versionCount);
// Sanitize qmake path:
- FilePath saneQmake = FilePath::fromUserInput(m_qmake).cleanPath();
+ QString saneQmake = QDir::cleanPath(m_qmake);
// insert data:
KeyValuePairList data;
@@ -291,7 +288,7 @@ QVariantMap AddQtData::addQt(const QVariantMap &map) const
data << KeyValuePair(QStringList() << qt << QLatin1String(AUTODETECTED), QVariant(true));
data << KeyValuePair(QStringList() << qt << QLatin1String(AUTODETECTION_SOURCE), QVariant(sdkId));
- data << KeyValuePair(QStringList() << qt << QLatin1String(QMAKE), saneQmake.toSettings());
+ data << KeyValuePair(QStringList() << qt << QLatin1String(QMAKE), QVariant(saneQmake));
data << KeyValuePair(QStringList() << qt << QLatin1String(TYPE), QVariant(m_type));
data << KeyValuePair(QStringList() << qt << ABIS, QVariant(m_abis));
diff --git a/src/tools/sdktool/addtoolchainoperation.cpp b/src/tools/sdktool/addtoolchainoperation.cpp
index 1290711e566..74f4965cf87 100644
--- a/src/tools/sdktool/addtoolchainoperation.cpp
+++ b/src/tools/sdktool/addtoolchainoperation.cpp
@@ -4,13 +4,10 @@
#include "addtoolchainoperation.h"
#include "addkeysoperation.h"
-#include "findkeyoperation.h"
#include "findvalueoperation.h"
#include "getoperation.h"
#include "rmkeysoperation.h"
-#include "settings.h"
-
#include <iostream>
#ifdef WITH_TESTS
@@ -283,7 +280,7 @@ QVariantMap AddToolChainData::addToolChain(const QVariantMap &map) const
data << KeyValuePair({tc, LANGUAGE_KEY_V2}, QVariant(newLang));
data << KeyValuePair({tc, DISPLAYNAME}, QVariant(m_displayName));
data << KeyValuePair({tc, AUTODETECTED}, QVariant(true));
- data << KeyValuePair({tc, PATH}, Utils::FilePath::fromUserInput(m_path).toSettings());
+ data << KeyValuePair({tc, PATH}, QVariant(m_path));
data << KeyValuePair({tc, TARGET_ABI}, QVariant(m_targetAbi));
QVariantList abis;
const QStringList abiStrings = m_supportedAbis.split(',');
diff --git a/src/tools/sdktool/main.cpp b/src/tools/sdktool/main.cpp
index 4f28b64b9e5..1136aa9d446 100644
--- a/src/tools/sdktool/main.cpp
+++ b/src/tools/sdktool/main.cpp
@@ -28,8 +28,10 @@
#include <app/app_version.h>
#include <iostream>
+#include <memory>
#include <QCoreApplication>
+#include <QDir>
#include <QLibraryInfo>
#include <QStringList>
@@ -60,10 +62,7 @@ void printHelp(const std::vector<std::unique_ptr<Operation>> &operations)
std::cout << " --sdkpath=PATH|-s PATH Set the path to the SDK files" << std::endl << std::endl;
std::cout << "Default sdkpath is \""
- << qPrintable(QDir::cleanPath(
- Utils::FilePath::fromString(QCoreApplication::applicationDirPath())
- .pathAppended(DATA_PATH)
- .toUserOutput()))
+ << qPrintable(QDir::cleanPath(QCoreApplication::applicationDirPath() + '/' + DATA_PATH))
<< "\"" << std::endl
<< std::endl;
@@ -105,7 +104,7 @@ int parseArguments(const QStringList &args, Settings *s,
// sdkpath
if (current.startsWith(QLatin1String("--sdkpath="))) {
- s->sdkPath = Utils::FilePath::fromString(current.mid(10));
+ s->sdkPath = current.mid(10);
continue;
}
if (current == QLatin1String("-s")) {
@@ -114,7 +113,7 @@ int parseArguments(const QStringList &args, Settings *s,
printHelp(operations);
return 1;
}
- s->sdkPath = Utils::FilePath::fromString(next);
+ s->sdkPath = next;
++i; // skip next;
continue;
}
diff --git a/src/tools/sdktool/operation.cpp b/src/tools/sdktool/operation.cpp
index ed673b9b780..7a81c3c7adf 100644
--- a/src/tools/sdktool/operation.cpp
+++ b/src/tools/sdktool/operation.cpp
@@ -4,8 +4,7 @@
#include "operation.h"
#include "settings.h"
-
-#include <utils/persistentsettings.h>
+#include "sdkpersistentsettings.h"
#include <QDir>
#include <QFile>
@@ -65,9 +64,9 @@ QVariantMap Operation::load(const QString &file)
QVariantMap map;
// Read values from original file:
- Utils::FilePath path = Settings::instance()->getPath(file);
- if (path.exists()) {
- Utils::PersistentSettingsReader reader;
+ QString path = Settings::instance()->getPath(file);
+ if (QFileInfo::exists(path)) {
+ SdkPersistentSettingsReader reader;
if (!reader.load(path))
return QVariantMap();
map = reader.restoreValues();
@@ -78,32 +77,32 @@ QVariantMap Operation::load(const QString &file)
bool Operation::save(const QVariantMap &map, const QString &file) const
{
- Utils::FilePath path = Settings::instance()->getPath(file);
+ QString path = Settings::instance()->getPath(file);
if (path.isEmpty()) {
std::cerr << "Error: No path found for " << qPrintable(file) << "." << std::endl;
return false;
}
- Utils::FilePath dirName = path.parentDir();
- QDir dir(dirName.toString());
+ QString dirName = QDir::cleanPath(path + "/..");
+ QDir dir(dirName);
if (!dir.exists() && !dir.mkpath(QLatin1String("."))) {
- std::cerr << "Error: Could not create directory " << qPrintable(dirName.toString())
+ std::cerr << "Error: Could not create directory " << qPrintable(dirName)
<< "." << std::endl;
return false;
}
- Utils::PersistentSettingsWriter writer(path, QLatin1String("QtCreator")
+ SdkPersistentSettingsWriter writer(path, QLatin1String("QtCreator")
+ file[0].toUpper() + file.mid(1));
QString errorMessage;
if (!writer.save(map, &errorMessage)) {
- std::cerr << "Error: Could not save settings " << qPrintable(path.toString())
+ std::cerr << "Error: Could not save settings " << qPrintable(path)
<< "." << std::endl;
return false;
}
- if (!path.setPermissions(QFile::ReadOwner | QFile::WriteOwner
+ if (!QFile(path).setPermissions(QFile::ReadOwner | QFile::WriteOwner
| QFile::ReadGroup | QFile::ReadOther)) {
- std::cerr << "Error: Could not set permissions for " << qPrintable(path.toString())
+ std::cerr << "Error: Could not set permissions for " << qPrintable(path)
<< "." << std::endl;
return false;
}
diff --git a/src/tools/sdktool/operation.h b/src/tools/sdktool/operation.h
index 1b43eb7b441..886ed386675 100644
--- a/src/tools/sdktool/operation.h
+++ b/src/tools/sdktool/operation.h
@@ -3,8 +3,6 @@
#pragma once
-#include <utils/fileutils.h>
-
#include <QStringList>
#include <QVariant>
diff --git a/src/tools/sdktool/sdkpersistentsettings.cpp b/src/tools/sdktool/sdkpersistentsettings.cpp
new file mode 100644
index 00000000000..4ca9b5e3701
--- /dev/null
+++ b/src/tools/sdktool/sdkpersistentsettings.cpp
@@ -0,0 +1,871 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include "sdkpersistentsettings.h"
+
+#include <QCoreApplication>
+#include <QDataStream>
+#include <QDateTime>
+#include <QDebug>
+#include <QDir>
+#include <QRect>
+#include <QRegularExpression>
+#include <QStack>
+#include <QTextStream>
+#include <QXmlStreamAttributes>
+#include <QXmlStreamReader>
+#include <QXmlStreamWriter>
+#include <QTemporaryFile>
+
+#ifdef Q_OS_WIN
+# include <windows.h>
+# include <io.h>
+#else
+# include <unistd.h>
+# include <sys/stat.h>
+#endif
+
+
+#define QTC_ASSERT_STRINGIFY_HELPER(x) #x
+#define QTC_ASSERT_STRINGIFY(x) QTC_ASSERT_STRINGIFY_HELPER(x)
+#define QTC_ASSERT_STRING(cond) writeAssertLocation(\
+ "\"" cond"\" in " __FILE__ ":" QTC_ASSERT_STRINGIFY(__LINE__))
+
+// The 'do {...} while (0)' idiom is not used for the main block here to be
+// able to use 'break' and 'continue' as 'actions'.
+
+#define QTC_ASSERT(cond, action) if (Q_LIKELY(cond)) {} else { QTC_ASSERT_STRING(#cond); action; } do {} while (0)
+#define QTC_CHECK(cond) if (Q_LIKELY(cond)) {} else { QTC_ASSERT_STRING(#cond); } do {} while (0)
+#define QTC_GUARD(cond) ((Q_LIKELY(cond)) ? true : (QTC_ASSERT_STRING(#cond), false))
+
+void writeAssertLocation(const char *msg)
+{
+ const QByteArray time = QTime::currentTime().toString(Qt::ISODateWithMs).toLatin1();
+ static bool goBoom = qEnvironmentVariableIsSet("QTC_FATAL_ASSERTS");
+ if (goBoom)
+ qFatal("SOFT ASSERT [%s] made fatal: %s", time.data(), msg);
+ else
+ qDebug("SOFT ASSERT [%s]: %s", time.data(), msg);
+}
+
+static QFile::Permissions m_umask;
+
+class SdkSaveFile : public QFile
+{
+public:
+ explicit SdkSaveFile(const QString &filePath) : m_finalFilePath(filePath) {}
+ ~SdkSaveFile() override;
+
+ bool open(OpenMode flags = QIODevice::WriteOnly) override;
+
+ void rollback();
+ bool commit();
+
+ static void initializeUmask();
+
+private:
+ const QString m_finalFilePath;
+ std::unique_ptr<QTemporaryFile> m_tempFile;
+ bool m_finalized = true;
+};
+
+SdkSaveFile::~SdkSaveFile()
+{
+ if (!m_finalized)
+ rollback();
+}
+
+bool SdkSaveFile::open(OpenMode flags)
+{
+ if (m_finalFilePath.isEmpty()) {
+ qWarning("Save file path empty");
+ return false;
+ }
+
+ QFile ofi(m_finalFilePath);
+ // Check whether the existing file is writable
+ if (ofi.exists() && !ofi.open(QIODevice::ReadWrite)) {
+ setErrorString(ofi.errorString());
+ return false;
+ }
+
+ m_tempFile = std::make_unique<QTemporaryFile>(m_finalFilePath);
+ m_tempFile->setAutoRemove(false);
+ if (!m_tempFile->open())
+ return false;
+ setFileName(m_tempFile->fileName());
+
+ if (!QFile::open(flags))
+ return false;
+
+ m_finalized = false; // needs clean up in the end
+ if (ofi.exists()) {
+ setPermissions(ofi.permissions()); // Ignore errors
+ } else {
+ Permissions permAll = QFile::ReadOwner
+ | QFile::ReadGroup
+ | QFile::ReadOther
+ | QFile::WriteOwner
+ | QFile::WriteGroup
+ | QFile::WriteOther;
+
+ // set permissions with respect to the current umask
+ setPermissions(permAll & ~m_umask);
+ }
+
+ return true;
+}
+
+void SdkSaveFile::rollback()
+{
+ close();
+ if (m_tempFile)
+ m_tempFile->remove();
+ m_finalized = true;
+}
+
+static QString resolveSymlinks(QString current)
+{
+ int links = 16;
+ while (links--) {
+ const QFileInfo info(current);
+ if (!info.isSymLink())
+ return {};
+ current = info.symLinkTarget();
+ }
+ return current;
+}
+
+bool SdkSaveFile::commit()
+{
+ QTC_ASSERT(!m_finalized && m_tempFile, return false;);
+ m_finalized = true;
+
+ if (!flush()) {
+ close();
+ m_tempFile->remove();
+ return false;
+ }
+#ifdef Q_OS_WIN
+ FlushFileBuffers(reinterpret_cast<HANDLE>(_get_osfhandle(handle())));
+#elif _POSIX_SYNCHRONIZED_IO > 0
+ fdatasync(handle());
+#else
+ fsync(handle());
+#endif
+ close();
+ m_tempFile->close();
+ if (error() != NoError) {
+ m_tempFile->remove();
+ return false;
+ }
+
+ QString finalFileName = resolveSymlinks(m_finalFilePath);
+
+#ifdef Q_OS_WIN
+ // Release the file lock
+ m_tempFile.reset();
+ bool result = ReplaceFile(finalFileName.toStdWString().data(),
+ fileName().toStdWString().data(),
+ nullptr, REPLACEFILE_IGNORE_MERGE_ERRORS, nullptr, nullptr);
+ if (!result) {
+ DWORD replaceErrorCode = GetLastError();
+ QString errorStr;
+ if (!QFile::exists(finalFileName)) {
+ // Replace failed because finalFileName does not exist, try rename.
+ if (!(result = rename(finalFileName)))
+ errorStr = errorString();
+ } else {
+ if (replaceErrorCode == ERROR_UNABLE_TO_REMOVE_REPLACED) {
+ // If we do not get the rights to remove the original final file we still might try
+ // to replace the file contents
+ result = MoveFileEx(fileName().toStdWString().data(),
+ finalFileName.toStdWString().data(),
+ MOVEFILE_COPY_ALLOWED
+ | MOVEFILE_REPLACE_EXISTING
+ | MOVEFILE_WRITE_THROUGH);
+ if (!result)
+ replaceErrorCode = GetLastError();
+ }
+ if (!result) {
+ wchar_t messageBuffer[256];
+ FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
+ nullptr, replaceErrorCode,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ messageBuffer, sizeof(messageBuffer), nullptr);
+ errorStr = QString::fromWCharArray(messageBuffer);
+ }
+ }
+ if (!result) {
+ remove();
+ setErrorString(errorStr);
+ }
+ }
+
+ return result;
+#else
+ const QString backupName = finalFileName + '~';
+
+ // Back up current file.
+ // If it's opened by another application, the lock follows the move.
+ if (QFile::exists(finalFileName)) {
+ // Kill old backup. Might be useful if creator crashed before removing backup.
+ QFile::remove(backupName);
+ QFile finalFile(finalFileName);
+ if (!finalFile.rename(backupName)) {
+ m_tempFile->remove();
+ setErrorString(finalFile.errorString());
+ return false;
+ }
+ }
+
+ bool result = true;
+ if (!m_tempFile->rename(finalFileName)) {
+ // The case when someone else was able to create finalFileName after we've renamed it.
+ // Higher level call may try to save this file again but here we do nothing and
+ // return false while keeping the error string from last rename call.
+ const QString &renameError = m_tempFile->errorString();
+ m_tempFile->remove();
+ setErrorString(renameError);
+ QFile::rename(backupName, finalFileName); // rollback to backup if possible ...
+ return false; // ... or keep the backup copy at least
+ }
+
+ QFile::remove(backupName);
+
+ return result;
+#endif
+}
+
+void SdkSaveFile::initializeUmask()
+{
+#ifdef Q_OS_WIN
+ m_umask = QFile::WriteGroup | QFile::WriteOther;
+#else
+ // Get the current process' file creation mask (umask)
+ // umask() is not thread safe so this has to be done by single threaded
+ // application initialization
+ mode_t mask = umask(0); // get current umask
+ umask(mask); // set it back
+
+ m_umask = ((mask & S_IRUSR) ? QFile::ReadOwner : QFlags<QFile::Permission>())
+ | ((mask & S_IWUSR) ? QFile::WriteOwner : QFlags<QFile::Permission>())
+ | ((mask & S_IXUSR) ? QFile::ExeOwner : QFlags<QFile::Permission>())
+ | ((mask & S_IRGRP) ? QFile::ReadGroup : QFlags<QFile::Permission>())
+ | ((mask & S_IWGRP) ? QFile::WriteGroup : QFlags<QFile::Permission>())
+ | ((mask & S_IXGRP) ? QFile::ExeGroup : QFlags<QFile::Permission>())
+ | ((mask & S_IROTH) ? QFile::ReadOther : QFlags<QFile::Permission>())
+ | ((mask & S_IWOTH) ? QFile::WriteOther : QFlags<QFile::Permission>())
+ | ((mask & S_IXOTH) ? QFile::ExeOther : QFlags<QFile::Permission>());
+#endif
+}
+
+class SdkFileSaverBase
+{
+public:
+ SdkFileSaverBase() = default;
+ virtual ~SdkFileSaverBase() = default;
+
+ QString filePath() const { return m_filePath; }
+ bool hasError() const { return m_hasError; }
+ QString errorString() const { return m_errorString; }
+ virtual bool finalize();
+ bool finalize(QString *errStr);
+
+ bool write(const char *data, int len);
+ bool write(const QByteArray &bytes);
+ bool setResult(QTextStream *stream);
+ bool setResult(QDataStream *stream);
+ bool setResult(QXmlStreamWriter *stream);
+ bool setResult(bool ok);
+
+ QFile *file() { return m_file.get(); }
+
+protected:
+ std::unique_ptr<QFile> m_file;
+ QString m_filePath;
+ QString m_errorString;
+ bool m_hasError = false;
+};
+
+bool SdkFileSaverBase::finalize()
+{
+ m_file->close();
+ setResult(m_file->error() == QFile::NoError);
+ m_file.reset();
+ return !m_hasError;
+}
+
+bool SdkFileSaverBase::finalize(QString *errStr)
+{
+ if (finalize())
+ return true;
+ if (errStr)
+ *errStr = errorString();
+ return false;
+}
+
+bool SdkFileSaverBase::write(const char *data, int len)
+{
+ if (m_hasError)
+ return false;
+ return setResult(m_file->write(data, len) == len);
+}
+
+bool SdkFileSaverBase::write(const QByteArray &bytes)
+{
+ if (m_hasError)
+ return false;
+ return setResult(m_file->write(bytes) == bytes.count());
+}
+
+bool SdkFileSaverBase::setResult(bool ok)
+{
+ if (!ok && !m_hasError) {
+ if (!m_file->errorString().isEmpty()) {
+ m_errorString = QString("Cannot write file %1: %2")
+ .arg(m_filePath, m_file->errorString());
+ } else {
+ m_errorString = QString("Cannot write file %1. Disk full?")
+ .arg(m_filePath);
+ }
+ m_hasError = true;
+ }
+ return ok;
+}
+
+bool SdkFileSaverBase::setResult(QTextStream *stream)
+{
+ stream->flush();
+ return setResult(stream->status() == QTextStream::Ok);
+}
+
+bool SdkFileSaverBase::setResult(QDataStream *stream)
+{
+ return setResult(stream->status() == QDataStream::Ok);
+}
+
+bool SdkFileSaverBase::setResult(QXmlStreamWriter *stream)
+{
+ return setResult(!stream->hasError());
+}
+
+// SdkFileSaver
+
+class SdkFileSaver : public SdkFileSaverBase
+{
+public:
+ // QIODevice::WriteOnly is implicit
+ explicit SdkFileSaver(const QString &filePath, QIODevice::OpenMode mode = QIODevice::NotOpen);
+
+ bool finalize() override;
+
+private:
+ bool m_isSafe = false;
+};
+
+SdkFileSaver::SdkFileSaver(const QString &filePath, QIODevice::OpenMode mode)
+{
+ m_filePath = filePath;
+ // Workaround an assert in Qt -- and provide a useful error message, too:
+#ifdef Q_OS_WIN
+ // Taken from: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
+ static const QStringList reservedNames
+ = {"CON", "PRN", "AUX", "NUL",
+ "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
+ "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"};
+ const QString fn = QFileInfo(filePath).baseName().toUpper();
+ if (reservedNames.contains(fn)) {
+ m_errorString = QString("%1: Is a reserved filename on Windows. Cannot save.").arg(filePath);
+ m_hasError = true;
+ return;
+ }
+#endif
+
+ if (mode & (QIODevice::ReadOnly | QIODevice::Append)) {
+ m_file.reset(new QFile{filePath});
+ m_isSafe = false;
+ } else {
+ m_file.reset(new SdkSaveFile(filePath));
+ m_isSafe = true;
+ }
+ if (!m_file->open(QIODevice::WriteOnly | mode)) {
+ QString err = QFileInfo::exists(filePath) ?
+ QString("Cannot overwrite file %1: %2") : QString("Cannot create file %1: %2");
+ m_errorString = err.arg(filePath, m_file->errorString());
+ m_hasError = true;
+ }
+}
+
+bool SdkFileSaver::finalize()
+{
+ if (!m_isSafe)
+ return SdkFileSaverBase::finalize();
+
+ auto sf = static_cast<SdkSaveFile *>(m_file.get());
+ if (m_hasError) {
+ if (sf->isOpen())
+ sf->rollback();
+ } else {
+ setResult(sf->commit());
+ }
+ m_file.reset();
+ return !m_hasError;
+}
+
+
+// Read and write rectangle in X11 resource syntax "12x12+4+3"
+static QString rectangleToString(const QRect &r)
+{
+ QString result;
+ QTextStream str(&result);
+ str << r.width() << 'x' << r.height();
+ if (r.x() >= 0)
+ str << '+';
+ str << r.x();
+ if (r.y() >= 0)
+ str << '+';
+ str << r.y();
+ return result;
+}
+
+static QRect stringToRectangle(const QString &v)
+{
+ static QRegularExpression pattern("^(\\d+)x(\\d+)([-+]\\d+)([-+]\\d+)$");
+ Q_ASSERT(pattern.isValid());
+ const QRegularExpressionMatch match = pattern.match(v);
+ return match.hasMatch() ?
+ QRect(QPoint(match.captured(3).toInt(), match.captured(4).toInt()),
+ QSize(match.captured(1).toInt(), match.captured(2).toInt())) :
+ QRect();
+}
+
+/*!
+ \class SdkPersistentSettingsReader
+
+ \note This is aQString based fork of Utils::PersistentSettigsReader
+
+ \brief The SdkPersistentSettingsReader class reads a QVariantMap of arbitrary,
+ nested data structures from an XML file.
+
+ Handles all string-serializable simple types and QVariantList and QVariantMap. Example:
+ \code
+<qtcreator>
+ <data>
+ <variable>ProjectExplorer.Project.ActiveTarget</variable>
+ <value type="int">0</value>
+ </data>
+ <data>
+ <variable>ProjectExplorer.Project.EditorSettings</variable>
+ <valuemap type="QVariantMap">
+ <value type="bool" key="EditorConfiguration.AutoIndent">true</value>
+ </valuemap>
+ </data>
+ \endcode
+
+ When parsing the structure, a parse stack of ParseValueStackEntry is used for each
+ <data> element. ParseValueStackEntry is a variant/union of:
+ \list
+ \li simple value
+ \li map
+ \li list
+ \endlist
+
+ You can register string-serialize functions for custom types by registering them in the Qt Meta
+ type system. Example:
+ \code
+ QMetaType::registerConverter(&MyCustomType::toString);
+ QMetaType::registerConverter<QString, MyCustomType>(&myCustomTypeFromString);
+ \endcode
+
+ When entering a value element ( \c <value> / \c <valuelist> , \c <valuemap> ), entry is pushed
+ accordingly. When leaving the element, the QVariant-value of the entry is taken off the stack
+ and added to the stack entry below (added to list or inserted into map). The first element
+ of the stack is the value of the <data> element.
+
+ \sa SdkPersistentSettingsWriter
+*/
+
+struct Context // Basic context containing element name string constants.
+{
+ Context() {}
+ const QString qtCreatorElement = QString("qtcreator");
+ const QString dataElement = QString("data");
+ const QString variableElement = QString("variable");
+ const QString typeAttribute = QString("type");
+ const QString valueElement = QString("value");
+ const QString valueListElement = QString("valuelist");
+ const QString valueMapElement = QString("valuemap");
+ const QString keyAttribute = QString("key");
+};
+
+struct ParseValueStackEntry
+{
+ explicit ParseValueStackEntry(QVariant::Type t = QVariant::Invalid, const QString &k = QString()) : type(t), key(k) {}
+ explicit ParseValueStackEntry(const QVariant &aSimpleValue, const QString &k);
+
+ QVariant value() const;
+ void addChild(const QString &key, const QVariant &v);
+
+ QVariant::Type type;
+ QString key;
+ QVariant simpleValue;
+ QVariantList listValue;
+ QVariantMap mapValue;
+};
+
+ParseValueStackEntry::ParseValueStackEntry(const QVariant &aSimpleValue, const QString &k) :
+ type(aSimpleValue.type()), key(k), simpleValue(aSimpleValue)
+{
+ QTC_ASSERT(simpleValue.isValid(), return);
+}
+
+QVariant ParseValueStackEntry::value() const
+{
+ switch (type) {
+ case QVariant::Invalid:
+ return QVariant();
+ case QVariant::Map:
+ return QVariant(mapValue);
+ case QVariant::List:
+ return QVariant(listValue);
+ default:
+ break;
+ }
+ return simpleValue;
+}
+
+void ParseValueStackEntry::addChild(const QString &key, const QVariant &v)
+{
+ switch (type) {
+ case QVariant::Map:
+ mapValue.insert(key, v);
+ break;
+ case QVariant::List:
+ listValue.push_back(v);
+ break;
+ default:
+ qWarning() << "ParseValueStackEntry::Internal error adding " << key << v << " to "
+ << QVariant::typeToName(type) << value();
+ break;
+ }
+}
+
+class ParseContext : public Context
+{
+public:
+ QVariantMap parse(const QString &file);
+
+private:
+ enum Element { QtCreatorElement, DataElement, VariableElement,
+ SimpleValueElement, ListValueElement, MapValueElement, UnknownElement };
+
+ Element element(const QStringView &r) const;
+ static inline bool isValueElement(Element e)
+ { return e == SimpleValueElement || e == ListValueElement || e == MapValueElement; }
+ QVariant readSimpleValue(QXmlStreamReader &r, const QXmlStreamAttributes &attributes) const;
+
+ bool handleStartElement(QXmlStreamReader &r);
+ bool handleEndElement(const QStringView &name);
+
+ static QString formatWarning(const QXmlStreamReader &r, const QString &message);
+
+ QStack<ParseValueStackEntry> m_valueStack;
+ QVariantMap m_result;
+ QString m_currentVariableName;
+};
+
+static QByteArray fileContents(const QString &path)
+{
+ QFile f(path);
+ if (!f.exists())
+ return {};
+
+ if (!f.open(QFile::ReadOnly))
+ return {};
+
+ return f.readAll();
+}
+
+QVariantMap ParseContext::parse(const QString &file)
+{
+ QXmlStreamReader r(fileContents(file));
+
+ m_result.clear();
+ m_currentVariableName.clear();
+
+ while (!r.atEnd()) {
+ switch (r.readNext()) {
+ case QXmlStreamReader::StartElement:
+ if (handleStartElement(r))
+ return m_result;
+ break;
+ case QXmlStreamReader::EndElement:
+ if (handleEndElement(r.name()))
+ return m_result;
+ break;
+ case QXmlStreamReader::Invalid:
+ qWarning("Error reading %s:%d: %s", qPrintable(file),
+ int(r.lineNumber()), qPrintable(r.errorString()));
+ return QVariantMap();
+ default:
+ break;
+ } // switch token
+ } // while (!r.atEnd())
+ return m_result;
+}
+
+bool ParseContext::handleStartElement(QXmlStreamReader &r)
+{
+ const QStringView name = r.name();
+ const Element e = element(name);
+ if (e == VariableElement) {
+ m_currentVariableName = r.readElementText();
+ return false;
+ }
+ if (!ParseContext::isValueElement(e))
+ return false;
+
+ const QXmlStreamAttributes attributes = r.attributes();
+ const QString key = attributes.hasAttribute(keyAttribute) ?
+ attributes.value(keyAttribute).toString() : QString();
+ switch (e) {
+ case SimpleValueElement: {
+ // This reads away the end element, so, handle end element right here.
+ const QVariant v = readSimpleValue(r, attributes);
+ if (!v.isValid()) {
+ qWarning() << ParseContext::formatWarning(r, QString::fromLatin1("Failed to read element \"%1\".").arg(name.toString()));
+ return false;
+ }
+ m_valueStack.push_back(ParseValueStackEntry(v, key));
+ return handleEndElement(name);
+ }
+ case ListValueElement:
+ m_valueStack.push_back(ParseValueStackEntry(QVariant::List, key));
+ break;
+ case MapValueElement:
+ m_valueStack.push_back(ParseValueStackEntry(QVariant::Map, key));
+ break;
+ default:
+ break;
+ }
+ return false;
+}
+
+bool ParseContext::handleEndElement(const QStringView &name)
+{
+ const Element e = element(name);
+ if (ParseContext::isValueElement(e)) {
+ QTC_ASSERT(!m_valueStack.isEmpty(), return true);
+ const ParseValueStackEntry top = m_valueStack.pop();
+ if (m_valueStack.isEmpty()) { // Last element? -> Done with that variable.
+ QTC_ASSERT(!m_currentVariableName.isEmpty(), return true);
+ m_result.insert(m_currentVariableName, top.value());
+ m_currentVariableName.clear();
+ return false;
+ }
+ m_valueStack.top().addChild(top.key, top.value());
+ }
+ return e == QtCreatorElement;
+}
+
+QString ParseContext::formatWarning(const QXmlStreamReader &r, const QString &message)
+{
+ QString result = QLatin1String("Warning reading ");
+ if (const QIODevice *device = r.device())
+ if (const auto file = qobject_cast<const QFile *>(device))
+ result += QDir::toNativeSeparators(file->fileName()) + QLatin1Char(':');
+ result += QString::number(r.lineNumber());
+ result += QLatin1String(": ");
+ result += message;
+ return result;
+}
+
+ParseContext::Element ParseContext::element(const QStringView &r) const
+{
+ if (r == valueElement)
+ return SimpleValueElement;
+ if (r == valueListElement)
+ return ListValueElement;
+ if (r == valueMapElement)
+ return MapValueElement;
+ if (r == qtCreatorElement)
+ return QtCreatorElement;
+ if (r == dataElement)
+ return DataElement;
+ if (r == variableElement)
+ return VariableElement;
+ return UnknownElement;
+}
+
+QVariant ParseContext::readSimpleValue(QXmlStreamReader &r, const QXmlStreamAttributes &attributes) const
+{
+ // Simple value
+ const QStringView type = attributes.value(typeAttribute);
+ const QString text = r.readElementText();
+ if (type == QLatin1String("QChar")) { // Workaround: QTBUG-12345
+ QTC_ASSERT(text.size() == 1, return QVariant());
+ return QVariant(QChar(text.at(0)));
+ }
+ if (type == QLatin1String("QRect")) {
+ const QRect rectangle = stringToRectangle(text);
+ return rectangle.isValid() ? QVariant(rectangle) : QVariant();
+ }
+ QVariant value;
+ value.setValue(text);
+ value.convert(QMetaType::type(type.toLatin1().constData()));
+ return value;
+}
+
+// =================================== SdkPersistentSettingsReader
+
+SdkPersistentSettingsReader::SdkPersistentSettingsReader() = default;
+
+QVariant SdkPersistentSettingsReader::restoreValue(const QString &variable, const QVariant &defaultValue) const
+{
+ if (m_valueMap.contains(variable))
+ return m_valueMap.value(variable);
+ return defaultValue;
+}
+
+QVariantMap SdkPersistentSettingsReader::restoreValues() const
+{
+ return m_valueMap;
+}
+
+bool SdkPersistentSettingsReader::load(const QString &fileName)
+{
+ m_valueMap.clear();
+
+ if (QFileInfo(fileName).size() == 0) // skip empty files
+ return false;
+
+ ParseContext ctx;
+ m_valueMap = ctx.parse(fileName);
+ return true;
+}
+
+/*!
+ \class SdkPersistentSettingsWriter
+
+ \note This is a fork of Utils::PersistentSettingsWriter
+
+ \brief The SdkPersistentSettingsWriter class serializes a QVariantMap of
+ arbitrary, nested data structures to an XML file.
+ \sa SdkPersistentSettingsReader
+*/
+
+static void writeVariantValue(QXmlStreamWriter &w, const Context &ctx,
+ const QVariant &variant, const QString &key = QString())
+{
+ switch (static_cast<int>(variant.type())) {
+ case static_cast<int>(QVariant::StringList):
+ case static_cast<int>(QVariant::List): {
+ w.writeStartElement(ctx.valueListElement);
+ w.writeAttribute(ctx.typeAttribute, QLatin1String(QVariant::typeToName(QVariant::List)));
+ if (!key.isEmpty())
+ w.writeAttribute(ctx.keyAttribute, key);
+ const QList<QVariant> list = variant.toList();
+ for (const QVariant &var : list)
+ writeVariantValue(w, ctx, var);
+ w.writeEndElement();
+ break;
+ }
+ case static_cast<int>(QVariant::Map): {
+ w.writeStartElement(ctx.valueMapElement);
+ w.writeAttribute(ctx.typeAttribute, QLatin1String(QVariant::typeToName(QVariant::Map)));
+ if (!key.isEmpty())
+ w.writeAttribute(ctx.keyAttribute, key);
+ const QVariantMap varMap = variant.toMap();
+ const QVariantMap::const_iterator cend = varMap.constEnd();
+ for (QVariantMap::const_iterator i = varMap.constBegin(); i != cend; ++i)
+ writeVariantValue(w, ctx, i.value(), i.key());
+ w.writeEndElement();
+ }
+ break;
+ case static_cast<int>(QMetaType::QObjectStar): // ignore QObjects!
+ case static_cast<int>(QMetaType::VoidStar): // ignore void pointers!
+ break;
+ default:
+ w.writeStartElement(ctx.valueElement);
+ w.writeAttribute(ctx.typeAttribute, QLatin1String(variant.typeName()));
+ if (!key.isEmpty())
+ w.writeAttribute(ctx.keyAttribute, key);
+ switch (variant.type()) {
+ case QVariant::Rect:
+ w.writeCharacters(rectangleToString(variant.toRect()));
+ break;
+ default:
+ w.writeCharacters(variant.toString());
+ break;
+ }
+ w.writeEndElement();
+ break;
+ }
+}
+
+SdkPersistentSettingsWriter::SdkPersistentSettingsWriter(const QString &fileName, const QString &docType) :
+ m_fileName(fileName), m_docType(docType)
+{ }
+
+bool SdkPersistentSettingsWriter::save(const QVariantMap &data, QString *errorString) const
+{
+ if (data == m_savedData)
+ return true;
+ return write(data, errorString);
+}
+
+QString SdkPersistentSettingsWriter::fileName() const
+{ return m_fileName; }
+
+//** * @brief Set contents of file (e.g. from data read from it). */
+void SdkPersistentSettingsWriter::setContents(const QVariantMap &data)
+{
+ m_savedData = data;
+}
+
+bool SdkPersistentSettingsWriter::write(const QVariantMap &data, QString *errorString) const
+{
+ const QString parentDir = QDir::cleanPath(m_fileName + "/..");
+
+ const QFileInfo fi(parentDir);
+ if (!(fi.exists() && fi.isDir() && fi.isWritable())) {
+ bool res = QDir().mkpath(parentDir);
+ if (!res)
+ return false;
+ }
+
+ SdkFileSaver saver(m_fileName, QIODevice::Text);
+ if (!saver.hasError()) {
+ const Context ctx;
+ QXmlStreamWriter w(saver.file());
+ w.setAutoFormatting(true);
+ w.setAutoFormattingIndent(1); // Historical, used to be QDom.
+ w.writeStartDocument();
+ w.writeDTD(QLatin1String("<!DOCTYPE ") + m_docType + QLatin1Char('>'));
+ w.writeComment(QString::fromLatin1(" Written by %1 %2, %3. ").
+ arg(QCoreApplication::applicationName(),
+ QCoreApplication::applicationVersion(),
+ QDateTime::currentDateTime().toString(Qt::ISODate)));
+ w.writeStartElement(ctx.qtCreatorElement);
+ const QVariantMap::const_iterator cend = data.constEnd();
+ for (QVariantMap::const_iterator it = data.constBegin(); it != cend; ++it) {
+ w.writeStartElement(ctx.dataElement);
+ w.writeTextElement(ctx.variableElement, it.key());
+ writeVariantValue(w, ctx, it.value());
+ w.writeEndElement();
+ }
+ w.writeEndDocument();
+
+ saver.setResult(&w);
+ }
+ bool ok = saver.finalize();
+ if (ok) {
+ m_savedData = data;
+ } else if (errorString) {
+ m_savedData.clear();
+ *errorString = saver.errorString();
+ }
+
+ return ok;
+}
diff --git a/src/tools/sdktool/sdkpersistentsettings.h b/src/tools/sdktool/sdkpersistentsettings.h
new file mode 100644
index 00000000000..691cf0e08a7
--- /dev/null
+++ b/src/tools/sdktool/sdkpersistentsettings.h
@@ -0,0 +1,37 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#pragma once
+
+#include <QVariant>
+
+class SdkPersistentSettingsReader
+{
+public:
+ SdkPersistentSettingsReader();
+ QVariant restoreValue(const QString &variable, const QVariant &defaultValue = QVariant()) const;
+ QVariantMap restoreValues() const;
+ bool load(const QString &fileName);
+
+private:
+ QMap<QString, QVariant> m_valueMap;
+};
+
+class SdkPersistentSettingsWriter
+{
+public:
+ SdkPersistentSettingsWriter(const QString &fileName, const QString &docType);
+
+ bool save(const QVariantMap &data, QString *errorString) const;
+
+ QString fileName() const;
+
+ void setContents(const QVariantMap &data);
+
+private:
+ bool write(const QVariantMap &data, QString *errorString) const;
+
+ const QString m_fileName;
+ const QString m_docType;
+ mutable QMap<QString, QVariant> m_savedData;
+};
diff --git a/src/tools/sdktool/sdktoollib.qbs b/src/tools/sdktool/sdktoollib.qbs
index 60823390832..ca40590d8d1 100644
--- a/src/tools/sdktool/sdktoollib.qbs
+++ b/src/tools/sdktool/sdktoollib.qbs
@@ -85,34 +85,7 @@ QtcLibrary {
"rmtoolchainoperation.h",
"settings.cpp",
"settings.h",
+ "sdkpersistentsettings.cpp",
+ "sdkpersistentsettings.h",
]
-
- Group {
- name: "Utils"
- prefix: libsDir + "/utils/"
- files: [
- "commandline.cpp", "commandline.h",
- "devicefileaccess.cpp", "devicefileaccess.h",
- "environment.cpp", "environment.h",
- "filepath.cpp", "filepath.h",
- "fileutils.cpp", "fileutils.h",
- "hostosinfo.cpp", "hostosinfo.h",
- "macroexpander.cpp", "macroexpander.h",
- "namevaluedictionary.cpp", "namevaluedictionary.h",
- "namevalueitem.cpp", "namevalueitem.h",
- "persistentsettings.cpp", "persistentsettings.h",
- "qtcassert.cpp", "qtcassert.h",
- "savefile.cpp", "savefile.h",
- "stringutils.cpp"
- ]
- }
- Group {
- name: "Utils/macOS"
- condition: qbs.targetOS.contains("macos")
- prefix: libsDir + "/utils/"
- files: [
- "fileutils_mac.h",
- "fileutils_mac.mm",
- ]
- }
}
diff --git a/src/tools/sdktool/settings.cpp b/src/tools/sdktool/settings.cpp
index d87df504f6e..53a1af0565f 100644
--- a/src/tools/sdktool/settings.cpp
+++ b/src/tools/sdktool/settings.cpp
@@ -2,11 +2,11 @@
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "settings.h"
-#include "operation.h"
#include <app/app_version.h>
#include <QCoreApplication>
+#include <QDir>
static Settings *m_instance = nullptr;
@@ -21,28 +21,28 @@ Settings::Settings()
m_instance = this;
// autodetect sdk dir:
- sdkPath = Utils::FilePath::fromUserInput(QCoreApplication::applicationDirPath())
- .pathAppended(DATA_PATH).cleanPath()
- .pathAppended(Core::Constants::IDE_SETTINGSVARIANT_STR)
- .pathAppended(Core::Constants::IDE_ID);
+ sdkPath = QDir::cleanPath(QCoreApplication::applicationDirPath()
+ + '/' + DATA_PATH
+ + '/' + Core::Constants::IDE_SETTINGSVARIANT_STR
+ + '/' + Core::Constants::IDE_ID);
}
-Utils::FilePath Settings::getPath(const QString &file)
+QString Settings::getPath(const QString &file)
{
- Utils::FilePath result = sdkPath;
+ QString result = sdkPath;
const QString lowerFile = file.toLower();
const QStringList identical = {
"android", "cmaketools", "debuggers", "devices", "profiles", "qtversions", "toolchains", "abi"
};
if (lowerFile == "cmake")
- result = result.pathAppended("cmaketools");
+ result += "/cmaketools";
else if (lowerFile == "kits")
- result = result.pathAppended("profiles");
+ result += "/profiles";
else if (lowerFile == "qtversions")
- result = result.pathAppended("qtversion");
+ result += "/qtversion";
else if (identical.contains(lowerFile))
- result = result.pathAppended(lowerFile);
+ result += '/' + lowerFile;
else
- result = result.pathAppended(file); // handle arbitrary file names not known yet
- return result.stringAppended(".xml");
+ result += '/' + file; // handle arbitrary file names not known yet
+ return result += ".xml";
}
diff --git a/src/tools/sdktool/settings.h b/src/tools/sdktool/settings.h
index b94c2c9ad0c..3b182e1c96f 100644
--- a/src/tools/sdktool/settings.h
+++ b/src/tools/sdktool/settings.h
@@ -3,7 +3,7 @@
#pragma once
-#include <utils/fileutils.h>
+#include <QString>
class Operation;
@@ -13,8 +13,8 @@ public:
Settings();
static Settings *instance();
- Utils::FilePath getPath(const QString &file);
+ QString getPath(const QString &file);
- Utils::FilePath sdkPath;
+ QString sdkPath;
Operation *operation = nullptr;
};
diff --git a/src/tools/tools.qbs b/src/tools/tools.qbs
index cb4230dfde4..48a35448d7e 100644
--- a/src/tools/tools.qbs
+++ b/src/tools/tools.qbs
@@ -7,6 +7,7 @@ Project {
"buildoutputparser/buildoutputparser.qbs",
"cplusplustools.qbs",
"disclaim/disclaim.qbs",
+ "process_stub/process_stub.qbs",
"processlauncher/processlauncher.qbs",
"qml2puppet/qml2puppet.qbs",
"qtcdebugger/qtcdebugger.qbs",
diff --git a/tests/auto/CMakeLists.txt b/tests/auto/CMakeLists.txt
index c71cbafdb2d..98ee3458965 100644
--- a/tests/auto/CMakeLists.txt
+++ b/tests/auto/CMakeLists.txt
@@ -6,6 +6,7 @@ add_subdirectory(cplusplus)
add_subdirectory(debugger)
add_subdirectory(diff)
add_subdirectory(environment)
+add_subdirectory(examples)
add_subdirectory(extensionsystem)
add_subdirectory(externaltool)
add_subdirectory(filesearch)
diff --git a/tests/auto/auto.qbs b/tests/auto/auto.qbs
index af173fa9f09..2ff98d14097 100644
--- a/tests/auto/auto.qbs
+++ b/tests/auto/auto.qbs
@@ -12,6 +12,7 @@ Project {
"debugger/debugger.qbs",
"diff/diff.qbs",
"environment/environment.qbs",
+ "examples/examples.qbs",
"extensionsystem/extensionsystem.qbs",
"externaltool/externaltool.qbs",
"filesearch/filesearch.qbs",
diff --git a/tests/auto/cplusplus/cxx11/tst_cxx11.cpp b/tests/auto/cplusplus/cxx11/tst_cxx11.cpp
index 0dc6484ff44..958aa631c68 100644
--- a/tests/auto/cplusplus/cxx11/tst_cxx11.cpp
+++ b/tests/auto/cplusplus/cxx11/tst_cxx11.cpp
@@ -145,6 +145,11 @@ private Q_SLOTS:
void lambdaType_data();
void lambdaType();
+
+ void concepts();
+ void requiresClause();
+ void coroutines();
+ void genericLambdas();
};
@@ -293,5 +298,257 @@ void tst_cxx11::lambdaType()
QCOMPARE(oo.prettyType(function->type()), expectedType);
}
+void tst_cxx11::concepts()
+{
+ LanguageFeatures features;
+ features.cxxEnabled = true;
+ features.cxx11Enabled = features.cxx14Enabled = features.cxx20Enabled = true;
+
+ const QString source = R"(
+template<typename T> concept IsPointer = requires(T p) { *p; };
+template<IsPointer T> void* func(T p) { return p; }
+void *func2(IsPointer auto p)
+{
+ return p;
+}
+)";
+ QByteArray errors;
+ Document::Ptr doc = Document::create(FilePath::fromPathPart(u"testFile"));
+ processDocument(doc, source.toUtf8(), features, &errors);
+ const bool hasErrors = !errors.isEmpty();
+ if (hasErrors)
+ qDebug().noquote() << errors;
+ QVERIFY(!hasErrors);
+}
+
+void tst_cxx11::requiresClause()
+{
+ LanguageFeatures features;
+ features.cxxEnabled = true;
+ features.cxx11Enabled = features.cxx14Enabled = features.cxx20Enabled = true;
+
+ const QString source = R"(
+template<class T> constexpr bool is_meowable = true;
+template<class T> constexpr bool is_purrable() { return true; }
+template<class T> void f(T) requires is_meowable<T>;
+template<class T> requires is_meowable<T> void g(T) ;
+template<class T> void h(T) requires (is_purrable<T>());
+)";
+ QByteArray errors;
+ Document::Ptr doc = Document::create(FilePath::fromPathPart(u"testFile"));
+ processDocument(doc, source.toUtf8(), features, &errors);
+ const bool hasErrors = !errors.isEmpty();
+ if (hasErrors)
+ qDebug().noquote() << errors;
+ QVERIFY(!hasErrors);
+}
+
+void tst_cxx11::coroutines()
+{
+ LanguageFeatures features;
+ features.cxxEnabled = true;
+ features.cxx11Enabled = features.cxx14Enabled = features.cxx20Enabled = true;
+
+ const QString source = R"(
+struct promise;
+struct coroutine : std::coroutine_handle<promise>
+{
+ using promise_type = struct promise;
+};
+struct promise
+{
+ coroutine get_return_object() { return {coroutine::from_promise(*this)}; }
+ std::suspend_always initial_suspend() noexcept { return {}; }
+ std::suspend_always final_suspend() noexcept { return {}; }
+ void return_void() {}
+ void unhandled_exception() {}
+};
+struct S
+{
+ int i;
+ coroutine f()
+ {
+ std::cout << i;
+ co_return;
+ }
+};
+void good()
+{
+ coroutine h = [](int i) -> coroutine
+ {
+ std::cout << i;
+ co_return;
+ }(0);
+ h.resume();
+ h.destroy();
+}
+auto switch_to_new_thread(std::jthread& out)
+{
+ struct awaitable
+ {
+ std::jthread* p_out;
+ bool await_ready() { return false; }
+ void await_suspend(std::coroutine_handle<> h)
+ {
+ std::jthread& out = *p_out;
+ if (out.joinable())
+ throw std::runtime_error("Output jthread parameter not empty");
+ out = std::jthread([h] { h.resume(); });
+ std::cout << "New thread ID: " << out.get_id() << '\n'; // this is OK
+ }
+ void await_resume() {}
+ };
+ return awaitable{&out};
+}
+struct task
+{
+ struct promise_type
+ {
+ task get_return_object() { return {}; }
+ std::suspend_never initial_suspend() { return {}; }
+ std::suspend_never final_suspend() noexcept { return {}; }
+ void return_void() {}
+ void unhandled_exception() {}
+ };
+};
+task resuming_on_new_thread(std::jthread& out)
+{
+ std::cout << "Coroutine started on thread: " << std::this_thread::get_id() << '\n';
+ co_await switch_to_new_thread(out);
+ std::cout << "Coroutine resumed on thread: " << std::this_thread::get_id() << '\n';
+}
+void run()
+{
+ std::jthread out;
+ resuming_on_new_thread(out);
+}
+template <typename T>
+struct Generator
+{
+ struct promise_type;
+ using handle_type = std::coroutine_handle<promise_type>;
+ struct promise_type // required
+ {
+ T value_;
+ std::exception_ptr exception_;
+
+ Generator get_return_object()
+ {
+ return Generator(handle_type::from_promise(*this));
+ }
+ std::suspend_always initial_suspend() { return {}; }
+ std::suspend_always final_suspend() noexcept { return {}; }
+ void unhandled_exception() { exception_ = std::current_exception(); }
+ template <std::convertible_to<T> From>
+ std::suspend_always yield_value(From&& from)
+ {
+ value_ = std::forward<From>(from);
+ return {};
+ }
+ void return_void() { }
+ };
+ handle_type h_;
+ Generator(handle_type h) : h_(h) {}
+ ~Generator() { h_.destroy(); }
+ explicit operator bool()
+ {
+ fill();
+ return !h_.done();
+ }
+ T operator()()
+ {
+ fill();
+ full_ = false;
+ return std::move(h_.promise().value_);
+ }
+private:
+ bool full_ = false;
+ void fill()
+ {
+ if (!full_)
+ {
+ h_();
+ if (h_.promise().exception_)
+ std::rethrow_exception(h_.promise().exception_);
+ full_ = true;
+ }
+ }
+};
+Generator<std::uint64_t>
+fibonacci_sequence(unsigned n)
+{
+ if (n == 0)
+ co_return;
+ if (n > 94)
+ throw std::runtime_error("Too big Fibonacci sequence. Elements would overflow.");
+ co_yield 0;
+ if (n == 1)
+ co_return;
+ co_yield 1;
+ if (n == 2)
+ co_return;
+ std::uint64_t a = 0;
+ std::uint64_t b = 1;
+ for (unsigned i = 2; i < n; i++)
+ {
+ std::uint64_t s = a + b;
+ co_yield s;
+ a = b;
+ b = s;
+ }
+}
+int main()
+{
+ try
+ {
+ auto gen = fibonacci_sequence(10); // max 94 before uint64_t overflows
+ for (int j = 0; gen; j++)
+ std::cout << "fib(" << j << ")=" << gen() << '\n';
+ } catch (const std::exception& ex) {
+ std::cerr << "Exception: " << ex.what() << '\n';
+ }
+ catch (...)
+ {
+ std::cerr << "Unknown exception.\n";
+ }
+}
+)";
+ QByteArray errors;
+ Document::Ptr doc = Document::create(FilePath::fromPathPart(u"testFile"));
+ processDocument(doc, source.toUtf8(), features, &errors);
+ const bool hasErrors = !errors.isEmpty();
+ if (hasErrors)
+ qDebug().noquote() << errors;
+ QVERIFY(!hasErrors);
+}
+
+void tst_cxx11::genericLambdas()
+{
+ LanguageFeatures features;
+ features.cxxEnabled = true;
+ features.cxx11Enabled = features.cxx14Enabled = features.cxx20Enabled = true;
+
+ const QString source = R"(
+template <typename T> concept C1 = true;
+template <std::size_t N> concept C2 = true;
+template <typename A, typename B> concept C3 = true;
+int main()
+{
+ auto f = []<class T>(T a, auto&& b) { return a < b; };
+ auto g = []<typename... Ts>(Ts&&... ts) { return foo(std::forward<Ts>(ts)...); };
+ auto h = []<typename T1, C1 T2> requires C2<sizeof(T1) + sizeof(T2)>
+ (T1 a1, T1 b1, T2 a2, auto a3, auto a4) requires C3<decltype(a4), T2> {
+ };
+}
+)";
+ QByteArray errors;
+ Document::Ptr doc = Document::create(FilePath::fromPathPart(u"testFile"));
+ processDocument(doc, source.toUtf8(), features, &errors);
+ const bool hasErrors = !errors.isEmpty();
+ if (hasErrors)
+ qDebug().noquote() << errors;
+ QVERIFY(!hasErrors);
+}
+
QTEST_APPLESS_MAIN(tst_cxx11)
#include "tst_cxx11.moc"
diff --git a/tests/auto/cplusplus/lexer/tst_lexer.cpp b/tests/auto/cplusplus/lexer/tst_lexer.cpp
index 641b62f248c..09a474504ee 100644
--- a/tests/auto/cplusplus/lexer/tst_lexer.cpp
+++ b/tests/auto/cplusplus/lexer/tst_lexer.cpp
@@ -43,6 +43,7 @@ public:
private slots:
void basic();
void basic_data();
+ void cxx20();
void incremental();
void incremental_data();
void literals();
@@ -250,6 +251,38 @@ void tst_SimpleLexer::basic_data()
QTest::newRow(source) << source << expectedTokenKindList;
}
+void tst_SimpleLexer::cxx20()
+{
+ LanguageFeatures features;
+ features.cxxEnabled = features.cxx11Enabled = features.cxx14Enabled
+ = features.cxx20Enabled = true;
+ const QString source = R"(
+template<typename T> concept IsPointer = requires(T p) { *p; };
+SomeType coroutine()
+{
+ constinit const char8_t = 'c';
+ if consteval {} else {}
+ co_await std::suspend_always{};
+ co_yield 1;
+ co_return;
+}
+)";
+ const TokenKindList expectedTokens = {
+ T_TEMPLATE, T_LESS, T_TYPENAME, T_IDENTIFIER, T_GREATER, T_CONCEPT, T_IDENTIFIER, T_EQUAL,
+ T_REQUIRES, T_LPAREN, T_IDENTIFIER, T_IDENTIFIER, T_RPAREN, T_LBRACE, T_STAR, T_IDENTIFIER,
+ T_SEMICOLON, T_RBRACE, T_SEMICOLON,
+ T_IDENTIFIER, T_IDENTIFIER, T_LPAREN, T_RPAREN,
+ T_LBRACE,
+ T_CONSTINIT, T_CONST, T_CHAR8_T, T_EQUAL, T_CHAR_LITERAL, T_SEMICOLON,
+ T_IF, T_CONSTEVAL, T_LBRACE, T_RBRACE, T_ELSE, T_LBRACE, T_RBRACE,
+ T_CO_AWAIT, T_IDENTIFIER, T_COLON_COLON, T_IDENTIFIER, T_LBRACE, T_RBRACE, T_SEMICOLON,
+ T_CO_YIELD, T_NUMERIC_LITERAL, T_SEMICOLON,
+ T_CO_RETURN, T_SEMICOLON,
+ T_RBRACE
+ };
+ run(source.toUtf8(), toTokens(expectedTokens), false, CompareKind, false, features);
+}
+
void tst_SimpleLexer::literals()
{
QFETCH(QByteArray, source);
diff --git a/tests/auto/debugger/CMakeLists.txt b/tests/auto/debugger/CMakeLists.txt
index 5ed44a94d2f..4abe98c05e8 100644
--- a/tests/auto/debugger/CMakeLists.txt
+++ b/tests/auto/debugger/CMakeLists.txt
@@ -27,7 +27,7 @@ if (NOT QT_CREATOR_API_DEFINED)
set(WITH_TESTS ON)
- find_package(Qt5
+ find_package(Qt6
COMPONENTS
Gui Core Core5Compat Widgets Network Qml Concurrent Test Xml MODULE)
find_package(Threads)
diff --git a/tests/auto/debugger/tst_dumpers.cpp b/tests/auto/debugger/tst_dumpers.cpp
index 3f2f66116f6..a1df2ae70c5 100644
--- a/tests/auto/debugger/tst_dumpers.cpp
+++ b/tests/auto/debugger/tst_dumpers.cpp
@@ -1736,7 +1736,8 @@ void tst_Dumpers::dumper()
expandedq.append(',');
}
expanded += iname;
- expandedq += '\'' + iname + '\'';
+ expandedq += '\'' + iname + "':";
+ expandedq += data.bigArray ? "10000" : "100";
}
QString exe = m_debuggerBinary;
@@ -1769,7 +1770,7 @@ void tst_Dumpers::dumper()
"'token':2,'fancy':1,'forcens':1,"
"'autoderef':1,'dyntype':1,'passexceptions':1,"
"'testing':1,'qobjectnames':1,"
- "'expanded':[" + expandedq + "]})\n";
+ "'expanded':{" + expandedq + "}})\n";
cmds += "quit\n";
@@ -1792,7 +1793,7 @@ void tst_Dumpers::dumper()
"'token':2,'fancy':1,'forcens':1,"
"'autoderef':1,'dyntype':1,'passexceptions':0,"
"'testing':1,'qobjectnames':1,"
- "'expanded':[" + expandedq + "]})\n"
+ "'expanded':{" + expandedq + "}})\n"
"q\n";
} else if (m_debuggerEngine == LldbEngine) {
QFile fullLldb(t->buildPath + "/lldbcommand.txt");
@@ -1808,7 +1809,7 @@ void tst_Dumpers::dumper()
"'fancy':1,'forcens':1,"
"'autoderef':1,'dyntype':1,'passexceptions':1,"
"'testing':1,'qobjectnames':1,"
- "'expanded':[" + expandedq + "]})\n"
+ "'expanded':{" + expandedq + "}})\n"
"quit\n";
fullLldb.write(cmds.toUtf8());
@@ -5314,6 +5315,7 @@ void tst_Dumpers::dumper_data()
"&v0, &v1, &v2, &v3, &v4, &v5, &b0, &b1, &b2, &b3")
+ Cxx11Profile()
+ + BigArrayProfile()
+ Check("v0", "<0 items>", "std::valarray<double>")
+ Check("v1", "<3 items>", "std::valarray<double>")
diff --git a/tests/auto/environment/tst_environment.cpp b/tests/auto/environment/tst_environment.cpp
index 994027308db..8aa0cbe063a 100644
--- a/tests/auto/environment/tst_environment.cpp
+++ b/tests/auto/environment/tst_environment.cpp
@@ -40,6 +40,9 @@ private slots:
void incrementalChanges();
+ void pathChanges_data();
+ void pathChanges();
+
void find_data();
void find();
@@ -270,8 +273,9 @@ void tst_Environment::incrementalChanges()
newEnv.modify(changes);
QVERIFY(!newEnv.hasKey("VAR1"));
QCOMPARE(newEnv.value("VAR2"), QString());
- QCOMPARE(newEnv.constFind("VAR2")->first, "VALUE2");
- QVERIFY(!newEnv.isEnabled(newEnv.constFind("VAR2")));
+ Environment::FindResult res = newEnv.find("VAR2");
+ QCOMPARE(res->value, "VALUE2");
+ QVERIFY(!res->enabled);
const QChar sep = HostOsInfo::pathListSeparator();
QCOMPARE(newEnv.value("PATH"),
QString("/tmp").append(sep).append("/usr/bin").append(sep).append("/usr/local/bin"));
@@ -295,6 +299,82 @@ void tst_Environment::incrementalChanges()
reverseDiff);
}
+void tst_Environment::pathChanges_data()
+{
+ const Environment origEnvLinux({"PATH=/bin:/usr/bin", "VAR=VALUE"}, OsTypeLinux);
+ const Environment origEnvWin({"PATH=C:\\Windows\\System32;D:\\gnu\\bin", "VAR=VALUE"}, OsTypeWindows);
+
+ QTest::addColumn<Environment>("environment");
+ QTest::addColumn<bool>("prepend"); // if false => append
+ QTest::addColumn<QString>("variable");
+ QTest::addColumn<QString>("value");
+ QTest::addColumn<Environment>("expected");
+
+ QTest::newRow("appendOrSetPath existingLeading Unix")
+ << origEnvLinux << false << "PATH" << "/bin"
+ << Environment({"PATH=/bin:/usr/bin:/bin", "VAR=VALUE"}, OsTypeLinux);
+ QTest::newRow("appendOrSetPath existingLeading Win")
+ << origEnvWin << false << "PATH" << "C:\\Windows\\System32"
+ << Environment({"PATH=C:\\Windows\\System32;D:\\gnu\\bin;C:\\Windows\\System32",
+ "VAR=VALUE"}, OsTypeWindows);
+ QTest::newRow("appendOrSetPath existingTrailing Unix")
+ << origEnvLinux << false << "PATH" << "/usr/bin"
+ << Environment({"PATH=/bin:/usr/bin", "VAR=VALUE"}, OsTypeLinux);
+ QTest::newRow("appendOrSetPath existingTrailing Win")
+ << origEnvWin << false << "PATH" << "D:\\gnu\\bin"
+ << Environment({"PATH=C:\\Windows\\System32;D:\\gnu\\bin",
+ "VAR=VALUE"}, OsTypeWindows);
+ QTest::newRow("prependOrSetPath existingLeading Unix")
+ << origEnvLinux << true << "PATH" << "/bin"
+ << Environment({"PATH=/bin:/usr/bin", "VAR=VALUE"}, OsTypeLinux);
+ QTest::newRow("prependOrSetPath existingLeading Win")
+ << origEnvWin << true << "PATH" << "C:\\Windows\\System32"
+ << Environment({"PATH=C:\\Windows\\System32;D:\\gnu\\bin",
+ "VAR=VALUE"}, OsTypeWindows);
+ QTest::newRow("prependOrSetPath existingTrailing Unix")
+ << origEnvLinux << true << "PATH" << "/usr/bin"
+ << Environment({"PATH=/usr/bin:/bin:/usr/bin", "VAR=VALUE"}, OsTypeLinux);
+ QTest::newRow("prependOrSetPath existingTrailing Win")
+ << origEnvWin << true << "PATH" << "D:\\gnu\\bin"
+ << Environment({"PATH=D:\\gnu\\bin;C:\\Windows\\System32;D:\\gnu\\bin",
+ "VAR=VALUE"}, OsTypeWindows);
+
+ QTest::newRow("appendOrSetPath non-existing Unix")
+ << origEnvLinux << false << "PATH" << "/opt"
+ << Environment({"PATH=/bin:/usr/bin:/opt", "VAR=VALUE"}, OsTypeLinux);
+ QTest::newRow("appendOrSetPath non-existing Win")
+ << origEnvWin << false << "PATH" << "C:\\Windows"
+ << Environment({"PATH=C:\\Windows\\System32;D:\\gnu\\bin;C:\\Windows",
+ "VAR=VALUE"}, OsTypeWindows);
+ QTest::newRow("prependOrSetPath non-existing half-matching Unix")
+ << origEnvLinux << true << "PATH" << "/bi"
+ << Environment({"PATH=/bi:/bin:/usr/bin", "VAR=VALUE"}, OsTypeLinux);
+ QTest::newRow("prependOrSetPath non-existing half-matching Win")
+ << origEnvWin << true << "PATH" << "C:\\Windows"
+ << Environment({"PATH=C:\\Windows;C:\\Windows\\System32;D:\\gnu\\bin",
+ "VAR=VALUE"}, OsTypeWindows);
+}
+
+void tst_Environment::pathChanges()
+{
+ QFETCH(Environment, environment);
+ QFETCH(bool, prepend);
+ QFETCH(QString, variable);
+ QFETCH(QString, value);
+ QFETCH(Environment, expected);
+
+ const QString sep = environment.osType() == OsTypeWindows ? ";" : ":";
+
+ if (prepend)
+ environment.prependOrSet(variable, value, sep);
+ else
+ environment.appendOrSet(variable, value, sep);
+
+ qDebug() << "Actual :" << environment.toStringList();
+ qDebug() << "Expected:" << expected.toStringList();
+ QCOMPARE(environment, expected);
+}
+
void tst_Environment::find_data()
{
QTest::addColumn<Utils::OsType>("osType");
@@ -317,13 +397,12 @@ void tst_Environment::find()
Environment env(QStringList({"Foo=bar", "Hi=HO"}), osType);
- auto end = env.constEnd();
- auto it = env.constFind(variable);
+ Environment::FindResult res = env.find(variable);
- QCOMPARE((end != it), contains);
+ QCOMPARE(bool(res), contains);
if (contains)
- QCOMPARE(env.value(it), QString("bar"));
+ QCOMPARE(res->value, QString("bar"));
}
diff --git a/tests/auto/examples/CMakeLists.txt b/tests/auto/examples/CMakeLists.txt
new file mode 100644
index 00000000000..cf8d1e4191e
--- /dev/null
+++ b/tests/auto/examples/CMakeLists.txt
@@ -0,0 +1,5 @@
+add_qtc_test(tst_examples
+ DEPENDS Utils Core QtSupport
+ SOURCES tst_examples.cpp
+)
+
diff --git a/tests/auto/examples/examples.qbs b/tests/auto/examples/examples.qbs
new file mode 100644
index 00000000000..39cacc395fa
--- /dev/null
+++ b/tests/auto/examples/examples.qbs
@@ -0,0 +1,9 @@
+import qbs
+
+QtcAutotest {
+ name: "Examples autotest"
+ Depends { name: "Core" }
+ Depends { name: "QtSupport" }
+ Depends { name: "Utils" }
+ files: "tst_examples.cpp"
+}
diff --git a/tests/auto/examples/tst_examples.cpp b/tests/auto/examples/tst_examples.cpp
new file mode 100644
index 00000000000..eef0429d29f
--- /dev/null
+++ b/tests/auto/examples/tst_examples.cpp
@@ -0,0 +1,162 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include <utils/filepath.h>
+#include <qtsupport/examplesparser.h>
+
+#include <QtTest>
+
+using namespace Utils;
+using namespace QtSupport::Internal;
+
+class tst_Examples : public QObject
+{
+ Q_OBJECT
+
+public:
+ tst_Examples();
+ ~tst_Examples();
+
+private slots:
+ void parsing_data();
+ void parsing();
+};
+
+tst_Examples::tst_Examples() = default;
+tst_Examples::~tst_Examples() = default;
+
+using MetaData = QHash<QString, QStringList>;
+
+static ExampleItem fetchItem()
+{
+ QFETCH(QString, name);
+ QFETCH(QString, description);
+ QFETCH(QString, imageUrl);
+ QFETCH(QStringList, tags);
+ QFETCH(FilePath, projectPath);
+ QFETCH(QString, docUrl);
+ QFETCH(FilePaths, filesToOpen);
+ QFETCH(FilePath, mainFile);
+ QFETCH(FilePaths, dependencies);
+ QFETCH(InstructionalType, type);
+ QFETCH(bool, hasSourceCode);
+ QFETCH(bool, isVideo);
+ QFETCH(bool, isHighlighted);
+ QFETCH(QString, videoUrl);
+ QFETCH(QString, videoLength);
+ QFETCH(QStringList, platforms);
+ QFETCH(MetaData, metaData);
+ ExampleItem item;
+ item.name = name;
+ item.description = description;
+ item.imageUrl = imageUrl;
+ item.tags = tags;
+ item.projectPath = projectPath;
+ item.docUrl = docUrl;
+ item.filesToOpen = filesToOpen;
+ item.mainFile = mainFile;
+ item.dependencies = dependencies;
+ item.type = type;
+ item.hasSourceCode = hasSourceCode;
+ item.isVideo = isVideo;
+ item.isHighlighted = isHighlighted;
+ item.videoUrl = videoUrl;
+ item.videoLength = videoLength;
+ item.platforms = platforms;
+ item.metaData = metaData;
+ return item;
+}
+
+void tst_Examples::parsing_data()
+{
+ QTest::addColumn<QByteArray>("data");
+ QTest::addColumn<bool>("isExamples");
+ QTest::addColumn<QString>("name");
+ QTest::addColumn<QString>("description");
+ QTest::addColumn<QString>("imageUrl");
+ QTest::addColumn<QStringList>("tags");
+ QTest::addColumn<FilePath>("projectPath");
+ QTest::addColumn<QString>("docUrl");
+ QTest::addColumn<FilePaths>("filesToOpen");
+ QTest::addColumn<FilePath>("mainFile");
+ QTest::addColumn<FilePaths>("dependencies");
+ QTest::addColumn<InstructionalType>("type");
+ QTest::addColumn<bool>("hasSourceCode");
+ QTest::addColumn<bool>("isVideo");
+ QTest::addColumn<bool>("isHighlighted");
+ QTest::addColumn<QString>("videoUrl");
+ QTest::addColumn<QString>("videoLength");
+ QTest::addColumn<QStringList>("platforms");
+ QTest::addColumn<MetaData>("metaData");
+
+ QTest::addRow("example")
+ << QByteArray(R"raw(
+ <examples>
+ <example docUrl="qthelp://org.qt-project.qtwidgets.660/qtwidgets/qtwidgets-widgets-analogclock-example.html"
+ imageUrl="qthelp://org.qt-project.qtwidgets.660/qtwidgets/images/analogclock-example.png"
+ name="Analog Clock" projectPath="widgets/widgets/analogclock/CMakeLists.txt">
+ <description><![CDATA[The Analog Clock example shows how to draw the contents of a custom widget.]]></description>
+ <tags>ios,widgets</tags>
+ <fileToOpen>widgets/widgets/analogclock/main.cpp</fileToOpen>
+ <fileToOpen>widgets/widgets/analogclock/analogclock.h</fileToOpen>
+ <fileToOpen mainFile="true">widgets/widgets/analogclock/analogclock.cpp</fileToOpen>
+ <meta>
+ <entry name="category">Graphics</entry>
+ <entry name="tags">widgets</entry>
+ </meta>
+ </example>
+ </examples>
+ )raw") << /*isExamples=*/true
+ << "Analog Clock"
+ << "The Analog Clock example shows how to draw the contents of a custom widget."
+ << "qthelp://org.qt-project.qtwidgets.660/qtwidgets/images/analogclock-example.png"
+ << QStringList{"ios", "widgets"}
+ << FilePath::fromUserInput("examples/widgets/widgets/analogclock/CMakeLists.txt")
+ << "qthelp://org.qt-project.qtwidgets.660/qtwidgets/"
+ "qtwidgets-widgets-analogclock-example.html"
+ << FilePaths{FilePath::fromUserInput("examples/widgets/widgets/analogclock/main.cpp"),
+ FilePath::fromUserInput("examples/widgets/widgets/analogclock/analogclock.h"),
+ FilePath::fromUserInput(
+ "examples/widgets/widgets/analogclock/analogclock.cpp")}
+ << FilePath::fromUserInput("examples/widgets/widgets/analogclock/analogclock.cpp")
+ << FilePaths() << Example << true << false << false << ""
+ << "" << QStringList() << MetaData({{"category", {"Graphics"}}, {"tags", {"widgets"}}});
+}
+
+void tst_Examples::parsing()
+{
+ QFETCH(QByteArray, data);
+ QFETCH(bool, isExamples);
+ const ExampleItem expected = fetchItem();
+ const expected_str<QList<ExampleItem *>> result
+ = parseExamples(data,
+ FilePath("manifest/examples-manifest.xml"),
+ FilePath("examples"),
+ FilePath("demos"),
+ isExamples);
+ QVERIFY(result);
+ QCOMPARE(result->size(), 1);
+ const ExampleItem item = *result->at(0);
+ QCOMPARE(item.name, expected.name);
+ QCOMPARE(item.description, expected.description);
+ QCOMPARE(item.imageUrl, expected.imageUrl);
+ QCOMPARE(item.tags, expected.tags);
+ QCOMPARE(item.projectPath, expected.projectPath);
+ QCOMPARE(item.docUrl, expected.docUrl);
+ QCOMPARE(item.filesToOpen, expected.filesToOpen);
+ QCOMPARE(item.mainFile, expected.mainFile);
+ QCOMPARE(item.dependencies, expected.dependencies);
+ QCOMPARE(item.type, expected.type);
+ QCOMPARE(item.hasSourceCode, expected.hasSourceCode);
+ QCOMPARE(item.isVideo, expected.isVideo);
+ QCOMPARE(item.isHighlighted, expected.isHighlighted);
+ QCOMPARE(item.videoUrl, expected.videoUrl);
+ QCOMPARE(item.videoLength, expected.videoLength);
+ QCOMPARE(item.platforms, expected.platforms);
+ QCOMPARE(item.metaData, expected.metaData);
+ qDeleteAll(*result);
+}
+
+QTEST_APPLESS_MAIN(tst_Examples)
+
+#include "tst_examples.moc"
diff --git a/tests/auto/extensionsystem/pluginmanager/tst_pluginmanager.cpp b/tests/auto/extensionsystem/pluginmanager/tst_pluginmanager.cpp
index 96657471459..e57e71c533e 100644
--- a/tests/auto/extensionsystem/pluginmanager/tst_pluginmanager.cpp
+++ b/tests/auto/extensionsystem/pluginmanager/tst_pluginmanager.cpp
@@ -57,9 +57,9 @@ void tst_PluginManager::init()
m_pm = new PluginManager;
PluginManager::setSettings(new Utils::QtcSettings);
PluginManager::setPluginIID(QLatin1String("plugin"));
- m_objectAdded = new QSignalSpy(m_pm, SIGNAL(objectAdded(QObject*)));
- m_aboutToRemoveObject = new QSignalSpy(m_pm, SIGNAL(aboutToRemoveObject(QObject*)));
- m_pluginsChanged = new QSignalSpy(m_pm, SIGNAL(pluginsChanged()));
+ m_objectAdded = new QSignalSpy(m_pm, &PluginManager::objectAdded);
+ m_aboutToRemoveObject = new QSignalSpy(m_pm, &PluginManager::aboutToRemoveObject);
+ m_pluginsChanged = new QSignalSpy(m_pm, &PluginManager::pluginsChanged);
}
void tst_PluginManager::cleanup()
diff --git a/tests/auto/filesearch/tst_filesearch.cpp b/tests/auto/filesearch/tst_filesearch.cpp
index 51f7d5a8bfe..2532f7a313a 100644
--- a/tests/auto/filesearch/tst_filesearch.cpp
+++ b/tests/auto/filesearch/tst_filesearch.cpp
@@ -31,12 +31,10 @@ namespace {
const QString &term,
QTextDocument::FindFlags flags, tst_FileSearch::RegExpFlag regexp = tst_FileSearch::NoRegExp)
{
- Utils::FileIterator *it = new Utils::FileListIterator(FilePaths{FilePath::fromString(
- FILENAME)},
- QList<QTextCodec *>()
- << QTextCodec::codecForLocale());
+ Utils::FileIterator *it = new Utils::FileListIterator({FilePath::fromString(FILENAME)},
+ {QTextCodec::codecForLocale()});
QFutureWatcher<Utils::FileSearchResultList> watcher;
- QSignalSpy ready(&watcher, SIGNAL(resultsReadyAt(int,int)));
+ QSignalSpy ready(&watcher, &QFutureWatcherBase::resultsReadyAt);
if (regexp == tst_FileSearch::NoRegExp)
watcher.setFuture(Utils::findInFiles(term, it, flags));
else
diff --git a/tests/auto/qml/codemodel/check/tst_check.cpp b/tests/auto/qml/codemodel/check/tst_check.cpp
index 46542b8bf8f..56ccdfd2a37 100644
--- a/tests/auto/qml/codemodel/check/tst_check.cpp
+++ b/tests/auto/qml/codemodel/check/tst_check.cpp
@@ -72,12 +72,11 @@ void tst_Check::initTestCase()
new ExtensionSystem::PluginManager;
ModelManagerInterface *modelManager = ModelManagerInterface::instance();
- QFutureInterface<void> result;
PathsAndLanguages lPaths;
QStringList paths(QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath));
for (auto p: paths)
lPaths.maybeInsert(Utils::FilePath::fromString(p), Dialect::Qml);
- ModelManagerInterface::importScan(result, ModelManagerInterface::workingCopy(), lPaths,
+ ModelManagerInterface::importScan(ModelManagerInterface::workingCopy(), lPaths,
modelManager, false);
modelManager->test_joinAllThreads();
}
diff --git a/tests/auto/qml/codemodel/dependencies/tst_dependencies.cpp b/tests/auto/qml/codemodel/dependencies/tst_dependencies.cpp
index 1a09449c114..48f8d7ce6cf 100644
--- a/tests/auto/qml/codemodel/dependencies/tst_dependencies.cpp
+++ b/tests/auto/qml/codemodel/dependencies/tst_dependencies.cpp
@@ -122,13 +122,12 @@ void tst_Dependencies::test()
ModelManagerInterface *modelManager = ModelManagerInterface::instance();
- QFutureInterface<void> result;
PathsAndLanguages lPaths;
QStringList paths(m_basePaths);
paths << m_path;
for (auto p: paths)
lPaths.maybeInsert(Utils::FilePath::fromString(p), Dialect::Qml);
- ModelManagerInterface::importScan(result, ModelManagerInterface::workingCopy(), lPaths,
+ ModelManagerInterface::importScan(ModelManagerInterface::workingCopy(), lPaths,
ModelManagerInterface::instance(), false);
ModelManagerInterface::instance()->test_joinAllThreads();
TestData data = testData(filename);
diff --git a/tests/auto/qml/codemodel/ecmascript7/tst_ecmascript7.cpp b/tests/auto/qml/codemodel/ecmascript7/tst_ecmascript7.cpp
index 9ca768cbeee..4f86e3ba988 100644
--- a/tests/auto/qml/codemodel/ecmascript7/tst_ecmascript7.cpp
+++ b/tests/auto/qml/codemodel/ecmascript7/tst_ecmascript7.cpp
@@ -149,12 +149,11 @@ void tst_Ecmascript::test()
ModelManagerInterface *modelManager = ModelManagerInterface::instance();
- QFutureInterface<void> result;
PathsAndLanguages lPaths;
QStringList paths(m_basePaths);
for (auto p: paths)
lPaths.maybeInsert(Utils::FilePath::fromString(p), Dialect::Qml);
- ModelManagerInterface::importScan(result, ModelManagerInterface::workingCopy(), lPaths,
+ ModelManagerInterface::importScan(ModelManagerInterface::workingCopy(), lPaths,
ModelManagerInterface::instance(), false);
TestData data = testData(filename);
diff --git a/tests/auto/qml/codemodel/importscheck/tst_importscheck.cpp b/tests/auto/qml/codemodel/importscheck/tst_importscheck.cpp
index 5cad7930e8f..e47c77db482 100644
--- a/tests/auto/qml/codemodel/importscheck/tst_importscheck.cpp
+++ b/tests/auto/qml/codemodel/importscheck/tst_importscheck.cpp
@@ -51,10 +51,9 @@ private:
void scanDirectory(const QString &dir)
{
auto dirPath = Utils::FilePath::fromString(dir);
- QFutureInterface<void> result;
PathsAndLanguages paths;
paths.maybeInsert(dirPath, Dialect::Qml);
- ModelManagerInterface::importScan(result, ModelManagerInterface::workingCopy(), paths,
+ ModelManagerInterface::importScan(ModelManagerInterface::workingCopy(), paths,
ModelManagerInterface::instance(), false);
ModelManagerInterface::instance()->test_joinAllThreads();
ViewerContext vCtx;
@@ -170,11 +169,10 @@ void tst_ImportCheck::test()
const auto pathPaths = Utils::transform(paths, [](const QString &s) {
return Utils::FilePath::fromString(s);
});
- QFutureInterface<void> result;
PathsAndLanguages lPaths;
for (const Utils::FilePath &path : pathPaths)
lPaths.maybeInsert(path, Dialect::Qml);
- ModelManagerInterface::importScan(result, ModelManagerInterface::workingCopy(), lPaths,
+ ModelManagerInterface::importScan(ModelManagerInterface::workingCopy(), lPaths,
ModelManagerInterface::instance(), false);
ModelManagerInterface::instance()->test_joinAllThreads();
ViewerContext vCtx;
diff --git a/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp b/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp
index d6e62263807..f483e1d09dc 100644
--- a/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp
+++ b/tests/auto/qml/qmldesigner/coretests/tst_testcore.cpp
@@ -233,12 +233,10 @@ void tst_TestCore::initTestCase()
QStringList basePaths;
basePaths.append(QLibraryInfo::location(QLibraryInfo::Qml2ImportsPath));
-
- QFutureInterface<void> result;
QmlJS::PathsAndLanguages lPaths;
lPaths.maybeInsert(Utils::FilePath::fromString(basePaths.first()), QmlJS::Dialect::Qml);
- QmlJS::ModelManagerInterface::importScan(result, QmlJS::ModelManagerInterface::workingCopy(),
+ QmlJS::ModelManagerInterface::importScan(QmlJS::ModelManagerInterface::workingCopy(),
lPaths, QmlJS::ModelManagerInterface::instance(), false);
// Load plugins
diff --git a/tests/auto/tracing/timelineabstractrenderer/tst_timelineabstractrenderer.cpp b/tests/auto/tracing/timelineabstractrenderer/tst_timelineabstractrenderer.cpp
index 0da3dd1f6a7..66ec7f67098 100644
--- a/tests/auto/tracing/timelineabstractrenderer/tst_timelineabstractrenderer.cpp
+++ b/tests/auto/tracing/timelineabstractrenderer/tst_timelineabstractrenderer.cpp
@@ -36,7 +36,7 @@ void tst_TimelineAbstractRenderer::privateCtor()
void tst_TimelineAbstractRenderer::selectionLocked()
{
TimelineAbstractRenderer renderer;
- QSignalSpy spy(&renderer, SIGNAL(selectionLockedChanged(bool)));
+ QSignalSpy spy(&renderer, &TimelineAbstractRenderer::selectionLockedChanged);
QCOMPARE(spy.count(), 0);
QVERIFY(renderer.selectionLocked());
renderer.setSelectionLocked(false);
@@ -50,7 +50,7 @@ void tst_TimelineAbstractRenderer::selectionLocked()
void tst_TimelineAbstractRenderer::selectedItem()
{
TimelineAbstractRenderer renderer;
- QSignalSpy spy(&renderer, SIGNAL(selectedItemChanged(int)));
+ QSignalSpy spy(&renderer, &TimelineAbstractRenderer::selectedItemChanged);
QCOMPARE(spy.count(), 0);
QCOMPARE(renderer.selectedItem(), -1);
renderer.setSelectedItem(12);
@@ -62,7 +62,7 @@ void tst_TimelineAbstractRenderer::model()
{
TimelineAbstractRenderer renderer;
TimelineModelAggregator aggregator;
- QSignalSpy spy(&renderer, SIGNAL(modelChanged(TimelineModel*)));
+ QSignalSpy spy(&renderer, &TimelineAbstractRenderer::modelChanged);
QVERIFY(!renderer.modelDirty());
QCOMPARE(spy.count(), 0);
TimelineModel model(&aggregator);
@@ -80,7 +80,7 @@ void tst_TimelineAbstractRenderer::model()
void tst_TimelineAbstractRenderer::notes()
{
TimelineAbstractRenderer renderer;
- QSignalSpy spy(&renderer, SIGNAL(notesChanged(TimelineNotesModel*)));
+ QSignalSpy spy(&renderer, &TimelineAbstractRenderer::notesChanged);
QVERIFY(!renderer.notesDirty());
QCOMPARE(spy.count(), 0);
TimelineNotesModel notes;
@@ -98,7 +98,7 @@ void tst_TimelineAbstractRenderer::notes()
void tst_TimelineAbstractRenderer::zoomer()
{
TimelineAbstractRenderer renderer;
- QSignalSpy spy(&renderer, SIGNAL(zoomerChanged(TimelineZoomControl*)));
+ QSignalSpy spy(&renderer, &TimelineAbstractRenderer::zoomerChanged);
QCOMPARE(spy.count(), 0);
TimelineZoomControl zoomer;
QCOMPARE(renderer.zoomer(), static_cast<TimelineZoomControl *>(0));
diff --git a/tests/auto/tracing/timelinemodel/tst_timelinemodel.cpp b/tests/auto/tracing/timelinemodel/tst_timelinemodel.cpp
index 27b457ce3fd..559b827de2e 100644
--- a/tests/auto/tracing/timelinemodel/tst_timelinemodel.cpp
+++ b/tests/auto/tracing/timelinemodel/tst_timelinemodel.cpp
@@ -6,17 +6,19 @@
#include <tracing/timelinemodel_p.h>
#include <tracing/timelinemodelaggregator.h>
+using namespace Timeline;
+
static const int NumItems = 32;
static const qint64 ItemDuration = 1 << 19;
static const qint64 ItemSpacing = 1 << 20;
-class DummyModel : public Timeline::TimelineModel
+class DummyModel : public TimelineModel
{
Q_OBJECT
friend class tst_TimelineModel;
public:
- DummyModel(Timeline::TimelineModelAggregator *parent);
- DummyModel(QString displayName, Timeline::TimelineModelAggregator *parent);
+ DummyModel(TimelineModelAggregator *parent);
+ DummyModel(QString displayName, TimelineModelAggregator *parent);
int expandedRow(int) const { return 2; }
int collapsedRow(int) const { return 1; }
@@ -57,15 +59,15 @@ private slots:
void parentingOfEqualStarts();
private:
- Timeline::TimelineModelAggregator aggregator;
+ TimelineModelAggregator aggregator;
};
-DummyModel::DummyModel(Timeline::TimelineModelAggregator *parent) :
- Timeline::TimelineModel(parent)
+DummyModel::DummyModel(TimelineModelAggregator *parent) :
+ TimelineModel(parent)
{
}
-DummyModel::DummyModel(QString displayName, Timeline::TimelineModelAggregator *parent) :
+DummyModel::DummyModel(QString displayName, TimelineModelAggregator *parent) :
TimelineModel(parent)
{
setDisplayName(displayName);
@@ -90,7 +92,7 @@ void DummyModel::loadData()
}
tst_TimelineModel::tst_TimelineModel() :
- DefaultRowHeight(Timeline::TimelineModel::defaultRowHeight())
+ DefaultRowHeight(TimelineModel::defaultRowHeight())
{
}
@@ -147,7 +149,7 @@ void tst_TimelineModel::rowHeight()
QCOMPARE(dummy.rowHeight(0), 100);
QCOMPARE(dummy.rowHeight(1), 50);
- QSignalSpy expandedSpy(&dummy, SIGNAL(expandedRowHeightChanged(int,int)));
+ QSignalSpy expandedSpy(&dummy, &TimelineModel::expandedRowHeightChanged);
dummy.clear();
QCOMPARE(expandedSpy.count(), 1);
}
@@ -194,7 +196,7 @@ void tst_TimelineModel::height()
DummyModel dummy(&aggregator);
int heightAfterLastSignal = 0;
int heightChangedSignals = 0;
- connect(&dummy, &Timeline::TimelineModel::heightChanged, [&](){
+ connect(&dummy, &TimelineModel::heightChanged, [&] {
++heightChangedSignals;
heightAfterLastSignal = dummy.height();
});
@@ -237,7 +239,7 @@ void tst_TimelineModel::count()
QCOMPARE(dummy.count(), 0);
dummy.loadData();
QCOMPARE(dummy.count(), NumItems);
- QSignalSpy emptySpy(&dummy, SIGNAL(contentChanged()));
+ QSignalSpy emptySpy(&dummy, &TimelineModel::contentChanged);
dummy.clear();
QCOMPARE(emptySpy.count(), 1);
QCOMPARE(dummy.count(), 0);
@@ -283,7 +285,7 @@ void tst_TimelineModel::firstLast()
void tst_TimelineModel::expand()
{
DummyModel dummy(&aggregator);
- QSignalSpy spy(&dummy, SIGNAL(expandedChanged()));
+ QSignalSpy spy(&dummy, &TimelineModel::expandedChanged);
QVERIFY(!dummy.expanded());
dummy.setExpanded(true);
QVERIFY(dummy.expanded());
@@ -302,7 +304,7 @@ void tst_TimelineModel::expand()
void tst_TimelineModel::hide()
{
DummyModel dummy(&aggregator);
- QSignalSpy spy(&dummy, SIGNAL(hiddenChanged()));
+ QSignalSpy spy(&dummy, &TimelineModel::hiddenChanged);
QVERIFY(!dummy.hidden());
dummy.setHidden(true);
QVERIFY(dummy.hidden());
@@ -322,7 +324,7 @@ void tst_TimelineModel::displayName()
{
QLatin1String name("testest");
DummyModel dummy(name, &aggregator);
- QSignalSpy spy(&dummy, SIGNAL(displayNameChanged()));
+ QSignalSpy spy(&dummy, &TimelineModel::displayNameChanged);
QCOMPARE(dummy.displayName(), name);
QCOMPARE(spy.count(), 0);
dummy.setDisplayName(name);
@@ -336,7 +338,7 @@ void tst_TimelineModel::displayName()
void tst_TimelineModel::defaultValues()
{
- Timeline::TimelineModel dummy(&aggregator);
+ TimelineModel dummy(&aggregator);
QCOMPARE(dummy.location(0), QVariantMap());
QCOMPARE(dummy.handlesTypeId(0), false);
QCOMPARE(dummy.relativeHeight(0), 1.0);
@@ -361,7 +363,7 @@ void tst_TimelineModel::row()
void tst_TimelineModel::colorByHue()
{
- Timeline::TimelineModelAggregator aggregator;
+ TimelineModelAggregator aggregator;
DummyModel dummy(&aggregator);
QCOMPARE(dummy.colorByHue(10), QColor::fromHsl(10, 150, 166).rgb());
QCOMPARE(dummy.colorByHue(500), QColor::fromHsl(140, 150, 166).rgb());
@@ -410,7 +412,7 @@ void tst_TimelineModel::insertStartEnd()
void tst_TimelineModel::rowCount()
{
DummyModel dummy(&aggregator);
- QSignalSpy contentSpy(&dummy, SIGNAL(contentChanged()));
+ QSignalSpy contentSpy(&dummy, &TimelineModel::contentChanged);
QCOMPARE(dummy.rowCount(), 1);
dummy.setExpanded(true);
QCOMPARE(dummy.rowCount(), 1);
diff --git a/tests/auto/tracing/timelinemodelaggregator/tst_timelinemodelaggregator.cpp b/tests/auto/tracing/timelinemodelaggregator/tst_timelinemodelaggregator.cpp
index 77c9c155e58..ac30ff0ad32 100644
--- a/tests/auto/tracing/timelinemodelaggregator/tst_timelinemodelaggregator.cpp
+++ b/tests/auto/tracing/timelinemodelaggregator/tst_timelinemodelaggregator.cpp
@@ -4,6 +4,8 @@
#include <QtTest>
#include <tracing/timelinemodelaggregator.h>
+using namespace Timeline;
+
class tst_TimelineModelAggregator : public QObject
{
Q_OBJECT
@@ -14,9 +16,9 @@ private slots:
void prevNext();
};
-class HeightTestModel : public Timeline::TimelineModel {
+class HeightTestModel : public TimelineModel {
public:
- HeightTestModel(Timeline::TimelineModelAggregator *parent) : TimelineModel(parent)
+ HeightTestModel(TimelineModelAggregator *parent) : TimelineModel(parent)
{
insert(0, 1, 1);
}
@@ -24,11 +26,11 @@ public:
void tst_TimelineModelAggregator::height()
{
- Timeline::TimelineModelAggregator aggregator;
+ TimelineModelAggregator aggregator;
QCOMPARE(aggregator.height(), 0);
- QSignalSpy heightSpy(&aggregator, SIGNAL(heightChanged()));
- Timeline::TimelineModel *model = new Timeline::TimelineModel(&aggregator);
+ QSignalSpy heightSpy(&aggregator, &TimelineModelAggregator::heightChanged);
+ TimelineModel *model = new TimelineModel(&aggregator);
aggregator.addModel(model);
QCOMPARE(aggregator.height(), 0);
QCOMPARE(heightSpy.count(), 0);
@@ -42,15 +44,15 @@ void tst_TimelineModelAggregator::height()
void tst_TimelineModelAggregator::addRemoveModel()
{
- Timeline::TimelineNotesModel notes;
- Timeline::TimelineModelAggregator aggregator;
+ TimelineNotesModel notes;
+ TimelineModelAggregator aggregator;
aggregator.setNotes(&notes);
- QSignalSpy spy(&aggregator, SIGNAL(modelsChanged()));
+ QSignalSpy spy(&aggregator, &TimelineModelAggregator::modelsChanged);
QCOMPARE(aggregator.notes(), &notes);
- Timeline::TimelineModel *model1 = new Timeline::TimelineModel(&aggregator);
- Timeline::TimelineModel *model2 = new Timeline::TimelineModel(&aggregator);
+ TimelineModel *model1 = new TimelineModel(&aggregator);
+ TimelineModel *model2 = new TimelineModel(&aggregator);
aggregator.addModel(model1);
QCOMPARE(spy.count(), 1);
QCOMPARE(aggregator.modelCount(), 1);
@@ -75,10 +77,10 @@ void tst_TimelineModelAggregator::addRemoveModel()
QCOMPARE(aggregator.modelCount(), 0);
}
-class PrevNextTestModel : public Timeline::TimelineModel
+class PrevNextTestModel : public TimelineModel
{
public:
- PrevNextTestModel(Timeline::TimelineModelAggregator *parent) : TimelineModel(parent)
+ PrevNextTestModel(TimelineModelAggregator *parent) : TimelineModel(parent)
{
for (int i = 0; i < 20; ++i)
insert(i + modelId(), i * modelId(), modelId());
@@ -87,14 +89,14 @@ public:
void tst_TimelineModelAggregator::prevNext()
{
- Timeline::TimelineModelAggregator aggregator;
+ TimelineModelAggregator aggregator;
aggregator.generateModelId(); // start modelIds at 1
aggregator.addModel(new PrevNextTestModel(&aggregator));
aggregator.addModel(new PrevNextTestModel(&aggregator));
aggregator.addModel(new PrevNextTestModel(&aggregator));
// Add an empty model to trigger the special code paths that skip it
- aggregator.addModel(new Timeline::TimelineModel(&aggregator));
+ aggregator.addModel(new TimelineModel(&aggregator));
QLatin1String item("item");
QLatin1String model("model");
QVariantMap result;
diff --git a/tests/auto/tracing/timelinenotesmodel/tst_timelinenotesmodel.cpp b/tests/auto/tracing/timelinenotesmodel/tst_timelinenotesmodel.cpp
index d01db8f12e6..d00ba47154e 100644
--- a/tests/auto/tracing/timelinenotesmodel/tst_timelinenotesmodel.cpp
+++ b/tests/auto/tracing/timelinenotesmodel/tst_timelinenotesmodel.cpp
@@ -63,7 +63,7 @@ void tst_TimelineNotesModel::addRemove()
TestModel model(&aggregator);
notes.addTimelineModel(&model);
- QSignalSpy spy(&notes, SIGNAL(changed(int,int,int)));
+ QSignalSpy spy(&notes, &TestNotesModel::changed);
int id = notes.add(model.modelId(), 0, QLatin1String("xyz"));
QCOMPARE(spy.count(), 1);
QCOMPARE(notes.isModified(), true);
@@ -129,7 +129,7 @@ void tst_TimelineNotesModel::modify()
TestNotesModel notes;
TestModel model(&aggregator);
notes.addTimelineModel(&model);
- QSignalSpy spy(&notes, SIGNAL(changed(int,int,int)));
+ QSignalSpy spy(&notes, &TestNotesModel::changed);
int id = notes.add(model.modelId(), 0, QLatin1String("a"));
QCOMPARE(spy.count(), 1);
notes.resetModified();
diff --git a/tests/auto/tracing/timelinezoomcontrol/tst_timelinezoomcontrol.cpp b/tests/auto/tracing/timelinezoomcontrol/tst_timelinezoomcontrol.cpp
index 4e00fc26d5d..3e1c936c0d5 100644
--- a/tests/auto/tracing/timelinezoomcontrol/tst_timelinezoomcontrol.cpp
+++ b/tests/auto/tracing/timelinezoomcontrol/tst_timelinezoomcontrol.cpp
@@ -5,11 +5,13 @@
#include <QColor>
#include <tracing/timelinezoomcontrol.h>
+using namespace Timeline;
+
class tst_TimelineZoomControl : public QObject
{
Q_OBJECT
private:
- void verifyWindow(const Timeline::TimelineZoomControl &zoomControl);
+ void verifyWindow(const TimelineZoomControl &zoomControl);
private slots:
void trace();
@@ -18,7 +20,7 @@ private slots:
void selection();
};
-void tst_TimelineZoomControl::verifyWindow(const Timeline::TimelineZoomControl &zoomControl)
+void tst_TimelineZoomControl::verifyWindow(const TimelineZoomControl &zoomControl)
{
QVERIFY(zoomControl.windowStart() <= zoomControl.rangeStart());
QVERIFY(zoomControl.windowEnd() >= zoomControl.rangeEnd());
@@ -28,8 +30,8 @@ void tst_TimelineZoomControl::verifyWindow(const Timeline::TimelineZoomControl &
void tst_TimelineZoomControl::trace()
{
- Timeline::TimelineZoomControl zoomControl;
- QSignalSpy spy(&zoomControl, SIGNAL(traceChanged(qint64,qint64)));
+ TimelineZoomControl zoomControl;
+ QSignalSpy spy(&zoomControl, &TimelineZoomControl::traceChanged);
QCOMPARE(zoomControl.traceStart(), -1);
QCOMPARE(zoomControl.traceEnd(), -1);
QCOMPARE(zoomControl.traceDuration(), 0);
@@ -49,18 +51,18 @@ void tst_TimelineZoomControl::trace()
void tst_TimelineZoomControl::window()
{
- Timeline::TimelineZoomControl zoomControl;
+ TimelineZoomControl zoomControl;
QTimer timer;
timer.setSingleShot(true);
- connect(&timer, &QTimer::timeout, [&](){
+ connect(&timer, &QTimer::timeout, [&] {
QVERIFY(zoomControl.windowLocked());
zoomControl.setWindowLocked(false);
});
int numWindowChanges = 0;
- connect(&zoomControl, &Timeline::TimelineZoomControl::windowChanged,
+ connect(&zoomControl, &TimelineZoomControl::windowChanged,
[&](qint64, qint64) {
verifyWindow(zoomControl);
@@ -98,8 +100,7 @@ void tst_TimelineZoomControl::window()
zoomControl.setRange(152000, 152005); // move right
QMetaObject::Connection connection = connect(
- &zoomControl, &Timeline::TimelineZoomControl::windowMovingChanged,
- [&](bool moving) {
+ &zoomControl, &TimelineZoomControl::windowMovingChanged, [&](bool moving) {
if (moving)
return;
@@ -129,7 +130,7 @@ void tst_TimelineZoomControl::window()
disconnect(connection);
bool stopDetected = false;
- connect(&zoomControl, &Timeline::TimelineZoomControl::windowMovingChanged, [&](bool moving) {
+ connect(&zoomControl, &TimelineZoomControl::windowMovingChanged, [&](bool moving) {
if (!moving) {
QCOMPARE(stopDetected, false);
stopDetected = true;
@@ -148,8 +149,8 @@ void tst_TimelineZoomControl::window()
void tst_TimelineZoomControl::range()
{
- Timeline::TimelineZoomControl zoomControl;
- QSignalSpy spy(&zoomControl, SIGNAL(rangeChanged(qint64,qint64)));
+ TimelineZoomControl zoomControl;
+ QSignalSpy spy(&zoomControl, &TimelineZoomControl::rangeChanged);
QCOMPARE(zoomControl.rangeStart(), -1);
QCOMPARE(zoomControl.rangeEnd(), -1);
QCOMPARE(zoomControl.rangeDuration(), 0);
@@ -175,8 +176,8 @@ void tst_TimelineZoomControl::range()
void tst_TimelineZoomControl::selection()
{
- Timeline::TimelineZoomControl zoomControl;
- QSignalSpy spy(&zoomControl, SIGNAL(selectionChanged(qint64,qint64)));
+ TimelineZoomControl zoomControl;
+ QSignalSpy spy(&zoomControl, &TimelineZoomControl::selectionChanged);
QCOMPARE(zoomControl.selectionStart(), -1);
QCOMPARE(zoomControl.selectionEnd(), -1);
QCOMPARE(zoomControl.selectionDuration(), 0);
diff --git a/tests/auto/utils/CMakeLists.txt b/tests/auto/utils/CMakeLists.txt
index 29548a5680f..74fed43c104 100644
--- a/tests/auto/utils/CMakeLists.txt
+++ b/tests/auto/utils/CMakeLists.txt
@@ -3,6 +3,7 @@ add_subdirectory(asynctask)
add_subdirectory(commandline)
add_subdirectory(deviceshell)
add_subdirectory(expected)
+add_subdirectory(filepath)
add_subdirectory(fileutils)
add_subdirectory(fsengine)
add_subdirectory(fuzzymatcher)
@@ -16,3 +17,4 @@ add_subdirectory(stringutils)
add_subdirectory(tasktree)
add_subdirectory(templateengine)
add_subdirectory(treemodel)
+add_subdirectory(unixdevicefileaccess)
diff --git a/tests/auto/utils/asynctask/tst_asynctask.cpp b/tests/auto/utils/asynctask/tst_asynctask.cpp
index 83b3c82aed9..67a291172a1 100644
--- a/tests/auto/utils/asynctask/tst_asynctask.cpp
+++ b/tests/auto/utils/asynctask/tst_asynctask.cpp
@@ -1,9 +1,8 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-#include "utils/asynctask.h"
-
-#include "utils/algorithm.h"
+#include <utils/algorithm.h>
+#include <utils/asynctask.h>
#include <QtTest>
@@ -24,78 +23,109 @@ private:
QThreadPool m_threadPool;
};
-void report3(QFutureInterface<int> &fi)
+void report3(QPromise<int> &promise)
{
- fi.reportResults({0, 2, 1});
+ promise.addResult(0);
+ promise.addResult(2);
+ promise.addResult(1);
}
-void reportN(QFutureInterface<double> &fi, int n)
+void reportN(QPromise<double> &promise, int n)
{
- fi.reportResults(QVector<double>(n, 0));
+ for (int i = 0; i < n; ++i)
+ promise.addResult(0);
}
-void reportString1(QFutureInterface<QString> &fi, const QString &s)
+void reportString1(QPromise<QString> &promise, const QString &s)
{
- fi.reportResult(s);
+ promise.addResult(s);
}
-void reportString2(QFutureInterface<QString> &fi, QString s)
+void reportString2(QPromise<QString> &promise, QString s)
{
- fi.reportResult(s);
+ promise.addResult(s);
}
class Callable {
public:
- void operator()(QFutureInterface<double> &fi, int n) const
+ void operator()(QPromise<double> &promise, int n) const
{
- fi.reportResults(QVector<double>(n, 0));
+ for (int i = 0; i < n; ++i)
+ promise.addResult(0);
}
};
class MyObject {
public:
- static void staticMember0(QFutureInterface<double> &fi)
+ static void staticMember0(QPromise<double> &promise)
{
- fi.reportResults({0, 2, 1});
+ promise.addResult(0);
+ promise.addResult(2);
+ promise.addResult(1);
}
- static void staticMember1(QFutureInterface<double> &fi, int n)
+ static void staticMember1(QPromise<double> &promise, int n)
{
- fi.reportResults(QVector<double>(n, 0));
+ for (int i = 0; i < n; ++i)
+ promise.addResult(0);
}
- void member0(QFutureInterface<double> &fi) const
+ void member0(QPromise<double> &promise) const
{
- fi.reportResults({0, 2, 1});
+ promise.addResult(0);
+ promise.addResult(2);
+ promise.addResult(1);
}
- void member1(QFutureInterface<double> &fi, int n) const
+ void member1(QPromise<double> &promise, int n) const
{
- fi.reportResults(QVector<double>(n, 0));
+ for (int i = 0; i < n; ++i)
+ promise.addResult(0);
}
- void memberString1(QFutureInterface<QString> &fi, const QString &s) const
+ void memberString1(QPromise<QString> &promise, const QString &s) const
{
- fi.reportResult(s);
+ promise.addResult(s);
}
- void memberString2(QFutureInterface<QString> &fi, QString s) const
+ void memberString2(QPromise<QString> &promise, QString s) const
{
- fi.reportResult(s);
+ promise.addResult(s);
}
- void nonConstMember(QFutureInterface<double> &fi)
+ void nonConstMember(QPromise<double> &promise)
{
- fi.reportResults({0, 2, 1});
+ promise.addResult(0);
+ promise.addResult(2);
+ promise.addResult(1);
}
};
+template <typename...>
+struct FutureArgType;
+
+template <typename Arg>
+struct FutureArgType<QFuture<Arg>>
+{
+ using Type = Arg;
+};
+
+template <typename...>
+struct ConcurrentResultType;
+
+template<typename Function, typename ...Args>
+struct ConcurrentResultType<Function, Args...>
+{
+ using Type = typename FutureArgType<decltype(QtConcurrent::run(
+ std::declval<Function>(), std::declval<Args>()...))>::Type;
+};
+
template <typename Function, typename ...Args,
- typename ResultType = typename Internal::resultType<Function>::type>
-std::shared_ptr<AsyncTask<ResultType>> createAsyncTask(const Function &function, const Args &...args)
+ typename ResultType = typename ConcurrentResultType<Function, Args...>::Type>
+std::shared_ptr<AsyncTask<ResultType>> createAsyncTask(Function &&function, Args &&...args)
{
auto asyncTask = std::make_shared<AsyncTask<ResultType>>();
- asyncTask->setAsyncCallData(function, args...);
+ asyncTask->setConcurrentCallData(std::forward<Function>(function), std::forward<Args>(args)...);
asyncTask->start();
return asyncTask;
}
@@ -105,13 +135,21 @@ void tst_AsyncTask::runAsync()
// free function pointer
QCOMPARE(createAsyncTask(&report3)->results(),
QList<int>({0, 2, 1}));
+ QCOMPARE(Utils::asyncRun(&report3).results(),
+ QList<int>({0, 2, 1}));
QCOMPARE(createAsyncTask(report3)->results(),
QList<int>({0, 2, 1}));
+ QCOMPARE(Utils::asyncRun(report3).results(),
+ QList<int>({0, 2, 1}));
QCOMPARE(createAsyncTask(reportN, 4)->results(),
QList<double>({0, 0, 0, 0}));
+ QCOMPARE(Utils::asyncRun(reportN, 4).results(),
+ QList<double>({0, 0, 0, 0}));
QCOMPARE(createAsyncTask(reportN, 2)->results(),
QList<double>({0, 0}));
+ QCOMPARE(Utils::asyncRun(reportN, 2).results(),
+ QList<double>({0, 0}));
QString s = QLatin1String("string");
const QString &crs = QLatin1String("cr string");
@@ -119,122 +157,182 @@ void tst_AsyncTask::runAsync()
QCOMPARE(createAsyncTask(reportString1, s)->results(),
QList<QString>({s}));
+ QCOMPARE(Utils::asyncRun(reportString1, s).results(),
+ QList<QString>({s}));
QCOMPARE(createAsyncTask(reportString1, crs)->results(),
QList<QString>({crs}));
+ QCOMPARE(Utils::asyncRun(reportString1, crs).results(),
+ QList<QString>({crs}));
QCOMPARE(createAsyncTask(reportString1, cs)->results(),
QList<QString>({cs}));
+ QCOMPARE(Utils::asyncRun(reportString1, cs).results(),
+ QList<QString>({cs}));
QCOMPARE(createAsyncTask(reportString1, QString(QLatin1String("rvalue")))->results(),
QList<QString>({QString(QLatin1String("rvalue"))}));
+ QCOMPARE(Utils::asyncRun(reportString1, QString(QLatin1String("rvalue"))).results(),
+ QList<QString>({QString(QLatin1String("rvalue"))}));
QCOMPARE(createAsyncTask(reportString2, s)->results(),
QList<QString>({s}));
+ QCOMPARE(Utils::asyncRun(reportString2, s).results(),
+ QList<QString>({s}));
QCOMPARE(createAsyncTask(reportString2, crs)->results(),
QList<QString>({crs}));
+ QCOMPARE(Utils::asyncRun(reportString2, crs).results(),
+ QList<QString>({crs}));
QCOMPARE(createAsyncTask(reportString2, cs)->results(),
QList<QString>({cs}));
+ QCOMPARE(Utils::asyncRun(reportString2, cs).results(),
+ QList<QString>({cs}));
QCOMPARE(createAsyncTask(reportString2, QString(QLatin1String("rvalue")))->results(),
QList<QString>({QString(QLatin1String("rvalue"))}));
+ QCOMPARE(Utils::asyncRun(reportString2, QString(QLatin1String("rvalue"))).results(),
+ QList<QString>({QString(QLatin1String("rvalue"))}));
// lambda
- QCOMPARE(createAsyncTask([](QFutureInterface<double> &fi, int n) {
- fi.reportResults(QVector<double>(n, 0));
+ QCOMPARE(createAsyncTask([](QPromise<double> &promise, int n) {
+ for (int i = 0; i < n; ++i)
+ promise.addResult(0);
}, 3)->results(),
QList<double>({0, 0, 0}));
+ QCOMPARE(Utils::asyncRun([](QPromise<double> &promise, int n) {
+ for (int i = 0; i < n; ++i)
+ promise.addResult(0);
+ }, 3).results(),
+ QList<double>({0, 0, 0}));
// std::function
- const std::function<void(QFutureInterface<double>&,int)> fun = [](QFutureInterface<double> &fi, int n) {
- fi.reportResults(QVector<double>(n, 0));
+ const std::function<void(QPromise<double>&,int)> fun = [](QPromise<double> &promise, int n) {
+ for (int i = 0; i < n; ++i)
+ promise.addResult(0);
};
QCOMPARE(createAsyncTask(fun, 2)->results(),
QList<double>({0, 0}));
+ QCOMPARE(Utils::asyncRun(fun, 2).results(),
+ QList<double>({0, 0}));
// operator()
QCOMPARE(createAsyncTask(Callable(), 3)->results(),
QList<double>({0, 0, 0}));
+ QCOMPARE(Utils::asyncRun(Callable(), 3).results(),
+ QList<double>({0, 0, 0}));
const Callable c{};
QCOMPARE(createAsyncTask(c, 2)->results(),
QList<double>({0, 0}));
+ QCOMPARE(Utils::asyncRun(c, 2).results(),
+ QList<double>({0, 0}));
// static member functions
QCOMPARE(createAsyncTask(&MyObject::staticMember0)->results(),
QList<double>({0, 2, 1}));
+ QCOMPARE(Utils::asyncRun(&MyObject::staticMember0).results(),
+ QList<double>({0, 2, 1}));
QCOMPARE(createAsyncTask(&MyObject::staticMember1, 2)->results(),
QList<double>({0, 0}));
+ QCOMPARE(Utils::asyncRun(&MyObject::staticMember1, 2).results(),
+ QList<double>({0, 0}));
// member functions
const MyObject obj{};
QCOMPARE(createAsyncTask(&MyObject::member0, &obj)->results(),
QList<double>({0, 2, 1}));
+ QCOMPARE(Utils::asyncRun(&MyObject::member0, &obj).results(),
+ QList<double>({0, 2, 1}));
QCOMPARE(createAsyncTask(&MyObject::member1, &obj, 4)->results(),
QList<double>({0, 0, 0, 0}));
+ QCOMPARE(Utils::asyncRun(&MyObject::member1, &obj, 4).results(),
+ QList<double>({0, 0, 0, 0}));
QCOMPARE(createAsyncTask(&MyObject::memberString1, &obj, s)->results(),
QList<QString>({s}));
+ QCOMPARE(Utils::asyncRun(&MyObject::memberString1, &obj, s).results(),
+ QList<QString>({s}));
QCOMPARE(createAsyncTask(&MyObject::memberString1, &obj, crs)->results(),
QList<QString>({crs}));
+ QCOMPARE(Utils::asyncRun(&MyObject::memberString1, &obj, crs).results(),
+ QList<QString>({crs}));
QCOMPARE(createAsyncTask(&MyObject::memberString1, &obj, cs)->results(),
QList<QString>({cs}));
+ QCOMPARE(Utils::asyncRun(&MyObject::memberString1, &obj, cs).results(),
+ QList<QString>({cs}));
QCOMPARE(createAsyncTask(&MyObject::memberString1, &obj, QString(QLatin1String("rvalue")))->results(),
QList<QString>({QString(QLatin1String("rvalue"))}));
+ QCOMPARE(Utils::asyncRun(&MyObject::memberString1, &obj, QString(QLatin1String("rvalue"))).results(),
+ QList<QString>({QString(QLatin1String("rvalue"))}));
QCOMPARE(createAsyncTask(&MyObject::memberString2, &obj, s)->results(),
QList<QString>({s}));
+ QCOMPARE(Utils::asyncRun(&MyObject::memberString2, &obj, s).results(),
+ QList<QString>({s}));
QCOMPARE(createAsyncTask(&MyObject::memberString2, &obj, crs)->results(),
QList<QString>({crs}));
+ QCOMPARE(Utils::asyncRun(&MyObject::memberString2, &obj, crs).results(),
+ QList<QString>({crs}));
QCOMPARE(createAsyncTask(&MyObject::memberString2, &obj, cs)->results(),
QList<QString>({cs}));
+ QCOMPARE(Utils::asyncRun(&MyObject::memberString2, &obj, cs).results(),
+ QList<QString>({cs}));
QCOMPARE(createAsyncTask(&MyObject::memberString2, &obj, QString(QLatin1String("rvalue")))->results(),
QList<QString>({QString(QLatin1String("rvalue"))}));
+ QCOMPARE(Utils::asyncRun(&MyObject::memberString2, &obj, QString(QLatin1String("rvalue"))).results(),
+ QList<QString>({QString(QLatin1String("rvalue"))}));
MyObject nonConstObj{};
QCOMPARE(createAsyncTask(&MyObject::nonConstMember, &nonConstObj)->results(),
QList<double>({0, 2, 1}));
+ QCOMPARE(Utils::asyncRun(&MyObject::nonConstMember, &nonConstObj).results(),
+ QList<double>({0, 2, 1}));
}
void tst_AsyncTask::crefFunction()
{
- // free function pointer with future interface
+ // free function pointer with promise
auto fun = &report3;
QCOMPARE(createAsyncTask(std::cref(fun))->results(),
QList<int>({0, 2, 1}));
+ QCOMPARE(Utils::asyncRun(std::cref(fun)).results(),
+ QList<int>({0, 2, 1}));
- // lambda with future interface
- auto lambda = [](QFutureInterface<double> &fi, int n) {
- fi.reportResults(QVector<double>(n, 0));
+ // lambda with promise
+ auto lambda = [](QPromise<double> &promise, int n) {
+ for (int i = 0; i < n; ++i)
+ promise.addResult(0);
};
QCOMPARE(createAsyncTask(std::cref(lambda), 3)->results(),
QList<double>({0, 0, 0}));
+ QCOMPARE(Utils::asyncRun(std::cref(lambda), 3).results(),
+ QList<double>({0, 0, 0}));
- // std::function with future interface
- const std::function<void(QFutureInterface<double>&,int)> funObj = [](QFutureInterface<double> &fi, int n) {
- fi.reportResults(QVector<double>(n, 0));
+ // std::function with promise
+ const std::function<void(QPromise<double>&,int)> funObj = [](QPromise<double> &promise, int n) {
+ for (int i = 0; i < n; ++i)
+ promise.addResult(0);
};
QCOMPARE(createAsyncTask(std::cref(funObj), 2)->results(),
QList<double>({0, 0}));
+ QCOMPARE(Utils::asyncRun(std::cref(funObj), 2).results(),
+ QList<double>({0, 0}));
- // callable with future interface
+ // callable with promise
const Callable c{};
QCOMPARE(createAsyncTask(std::cref(c), 2)->results(),
QList<double>({0, 0}));
+ QCOMPARE(Utils::asyncRun(std::cref(c), 2).results(),
+ QList<double>({0, 0}));
- // member functions with future interface
+ // member functions with promise
auto member = &MyObject::member0;
const MyObject obj{};
QCOMPARE(createAsyncTask(std::cref(member), &obj)->results(),
QList<double>({0, 2, 1}));
-}
-
-template <typename Function, typename ...Args,
- typename ResultType = typename Internal::resultType<Function>::type>
-typename AsyncTask<ResultType>::StartHandler startHandler(const Function &function, const Args &...args)
-{
- return [=] { return Utils::runAsync(function, args...); };
+ QCOMPARE(Utils::asyncRun(std::cref(member), &obj).results(),
+ QList<double>({0, 2, 1}));
}
void tst_AsyncTask::futureSynchonizer()
{
- auto lambda = [](QFutureInterface<int> &fi) {
+ auto lambda = [](QPromise<int> &promise) {
while (true) {
- if (fi.isCanceled()) {
- fi.reportCanceled();
- fi.reportFinished();
+ if (promise.isCanceled()) {
+ promise.future().cancel();
+ promise.finish();
return;
}
QThread::msleep(100);
@@ -244,7 +342,7 @@ void tst_AsyncTask::futureSynchonizer()
FutureSynchronizer synchronizer;
{
AsyncTask<int> task;
- task.setAsyncCallData(lambda);
+ task.setConcurrentCallData(lambda);
task.setFutureSynchronizer(&synchronizer);
task.start();
QThread::msleep(10);
@@ -257,7 +355,7 @@ void tst_AsyncTask::futureSynchonizer()
// The destructor of synchronizer should wait for about 90 ms for worker thread to be canceled
}
-void multiplyBy2(QFutureInterface<int> &fi, int input) { fi.reportResult(input * 2); }
+void multiplyBy2(QPromise<int> &promise, int input) { promise.addResult(input * 2); }
void tst_AsyncTask::taskTree()
{
@@ -266,7 +364,7 @@ void tst_AsyncTask::taskTree()
int value = 1;
const auto setupIntAsync = [&](AsyncTask<int> &task) {
- task.setAsyncCallData(multiplyBy2, value);
+ task.setConcurrentCallData(multiplyBy2, value);
};
const auto handleIntAsync = [&](const AsyncTask<int> &task) {
value = task.result();
@@ -294,9 +392,9 @@ static int returnxx(int x)
return x * x;
}
-static void returnxxWithFI(QFutureInterface<int> &fi, int x)
+static void returnxxWithPromise(QPromise<int> &promise, int x)
{
- fi.reportResult(x * x);
+ promise.addResult(x * x);
}
static double s_sum = 0;
@@ -315,13 +413,13 @@ void tst_AsyncTask::mapReduce_data()
s_results.append(s_sum);
};
const auto setupAsync = [](AsyncTask<int> &task, int input) {
- task.setAsyncCallData(returnxx, input);
+ task.setConcurrentCallData(returnxx, input);
};
const auto setupAsyncWithFI = [](AsyncTask<int> &task, int input) {
- task.setAsyncCallData(returnxxWithFI, input);
+ task.setConcurrentCallData(returnxxWithPromise, input);
};
const auto setupAsyncWithTP = [this](AsyncTask<int> &task, int input) {
- task.setAsyncCallData(returnxx, input);
+ task.setConcurrentCallData(returnxx, input);
task.setThreadPool(&m_threadPool);
};
const auto handleAsync = [](const AsyncTask<int> &task) {
@@ -377,7 +475,7 @@ void tst_AsyncTask::mapReduce_data()
QTest::newRow("SequentialWithThreadPool") << sequentialRootWithTP << defaultSum << defaultResult;
const auto setupSimpleAsync = [](AsyncTask<int> &task, int input) {
- task.setAsyncCallData([](int input) { return input * 2; }, input);
+ task.setConcurrentCallData([](int input) { return input * 2; }, input);
};
const auto handleSimpleAsync = [](const AsyncTask<int> &task) {
s_sum += task.result() / 4.;
@@ -393,7 +491,7 @@ void tst_AsyncTask::mapReduce_data()
QTest::newRow("Simple") << simpleRoot << 3.0 << QList<double>({.5, 1.5, 3.});
const auto setupStringAsync = [](AsyncTask<int> &task, const QString &input) {
- task.setAsyncCallData([](const QString &input) -> int { return input.size(); }, input);
+ task.setConcurrentCallData([](const QString &input) -> int { return input.size(); }, input);
};
const auto handleStringAsync = [](const AsyncTask<int> &task) {
s_sum /= task.result();
diff --git a/tests/auto/utils/commandline/tst_commandline.cpp b/tests/auto/utils/commandline/tst_commandline.cpp
index 05357d2526e..6972ae6ba39 100644
--- a/tests/auto/utils/commandline/tst_commandline.cpp
+++ b/tests/auto/utils/commandline/tst_commandline.cpp
@@ -7,6 +7,7 @@
#include <utils/environment.h>
#include <utils/hostosinfo.h>
#include <utils/launcherinterface.h>
+#include <utils/macroexpander.h>
#include <utils/processinterface.h>
#include <utils/qtcprocess.h>
#include <utils/temporarydirectory.h>
@@ -123,6 +124,82 @@ private slots:
QString actual = run(shell);
QCOMPARE(actual, expected);
}
+
+ void testFromUserInput_data()
+ {
+ QTest::addColumn<QString>("input");
+ QTest::addColumn<QString>("executable");
+ QTest::addColumn<QString>("arguments");
+
+ QTest::newRow("empty") << ""
+ << ""
+ << "";
+ QTest::newRow("command") << "command"
+ << "command"
+ << "";
+ QTest::newRow("command-with-args") << "command and args"
+ << "command"
+ << "and args";
+
+ if (!HostOsInfo::isWindowsHost()) {
+ QTest::newRow("command-with-space-slash") << "command\\ with-space and args"
+ << "command with-space"
+ << "and args";
+ QTest::newRow("command-with-space-single-quote") << "'command with-space' and args"
+ << "command with-space"
+ << "and args";
+ }
+ QTest::newRow("command-with-space-double-quote") << "\"command with-space\" and args"
+ << "command with-space"
+ << "and args";
+
+ QTest::newRow("command-with-space-double-quote-in-name")
+ << "\"command\\\"with-quote\" and args"
+ << "command\"with-quote"
+ << "and args";
+
+ QTest::newRow("inside-space-quoted") << "command\" \"withspace args here"
+ << "command withspace"
+ << "args here";
+ }
+
+ void testFromUserInput()
+ {
+ QFETCH(QString, input);
+ QFETCH(QString, executable);
+ QFETCH(QString, arguments);
+
+ CommandLine cmd = CommandLine::fromUserInput(input);
+ QCOMPARE(cmd.executable(), FilePath::fromUserInput(executable));
+ QCOMPARE(cmd.arguments(), arguments);
+ }
+
+ void testFromInputFails()
+ {
+ if (HostOsInfo::isWindowsHost())
+ QSKIP("The test does not work on Windows.");
+
+ CommandLine cmd = CommandLine::fromUserInput("command\\\\\\ with-space and args");
+ QEXPECT_FAIL("",
+ "CommandLine::fromUserInput (and FilePath::fromUserInput) does not handle "
+ "backslashes correctly",
+ Continue);
+ QCOMPARE(cmd.executable().fileName(), "command\\ with-space");
+ QCOMPARE(cmd.arguments(), "and args");
+ }
+
+ void testFromInputWithMacro()
+ {
+ MacroExpander expander;
+ expander.registerVariable("hello", "world var", [] { return "hello world"; });
+ CommandLine cmd = CommandLine::fromUserInput("command macroarg: %{hello}", &expander);
+ QCOMPARE(cmd.executable(), "command");
+
+ if (HostOsInfo::isWindowsHost())
+ QEXPECT_FAIL("", "Windows does not correctly quote macro arguments", Continue);
+
+ QCOMPARE(cmd.arguments(), "macroarg: 'hello world'");
+ }
};
int main(int argc, char *argv[])
diff --git a/tests/auto/utils/deviceshell/tst_deviceshell.cpp b/tests/auto/utils/deviceshell/tst_deviceshell.cpp
index 6080f99bf7c..1d95bde4091 100644
--- a/tests/auto/utils/deviceshell/tst_deviceshell.cpp
+++ b/tests/auto/utils/deviceshell/tst_deviceshell.cpp
@@ -3,16 +3,16 @@
#include <app/app_version.h>
+#include <utils/algorithm.h>
#include <utils/deviceshell.h>
#include <utils/environment.h>
#include <utils/hostosinfo.h>
#include <utils/launcherinterface.h>
-#include <utils/mapreduce.h>
#include <utils/qtcprocess.h>
-#include <utils/runextensions.h>
#include <utils/temporarydirectory.h>
#include <QObject>
+#include <QtConcurrent>
#include <QtTest>
using namespace Utils;
@@ -312,15 +312,14 @@ private slots:
TestShell shell(cmdLine);
QCOMPARE(shell.state(), DeviceShell::State::Succeeded);
- QList<int> runs{1,2,3,4,5,6,7,8,9};
-
int maxDepth = 4;
int numMs = 0;
while (true) {
QElapsedTimer t;
t.start();
- RunResult result = shell.runInShell({"find", {"/usr", "-maxdepth", QString::number(maxDepth)}});
+ const RunResult result = shell.runInShell({"find", {"/usr", "-maxdepth",
+ QString::number(maxDepth)}});
numMs = t.elapsed();
qDebug() << "adjusted maxDepth" << maxDepth << "took" << numMs << "ms";
if (numMs < 100 || maxDepth == 1) {
@@ -329,15 +328,17 @@ private slots:
maxDepth--;
}
- QList<QByteArray> results = Utils::mapped<QList>(runs, [&shell, maxDepth](const int i) -> QByteArray{
+ const auto find = [&shell, maxDepth](int i) {
QElapsedTimer t;
t.start();
- RunResult result = shell.runInShell({"find", {"/usr", "-maxdepth", QString::number(maxDepth)}});
+ const RunResult result = shell.runInShell({"find", {"/usr", "-maxdepth",
+ QString::number(maxDepth)}});
qDebug() << i << "took" << t.elapsed() << "ms";
return result.stdOut;
- });
-
- QVERIFY (!Utils::anyOf(results, [&results](const QByteArray r){ return r != results[0]; }));
+ };
+ const QList<int> runs{1,2,3,4,5,6,7,8,9};
+ const QList<QByteArray> results = QtConcurrent::blockingMapped(runs, find);
+ QVERIFY(!Utils::anyOf(results, [&results](const QByteArray r) { return r != results[0]; }));
}
void testNoScript_data()
diff --git a/tests/auto/utils/filepath/CMakeLists.txt b/tests/auto/utils/filepath/CMakeLists.txt
new file mode 100644
index 00000000000..3d85eb0c64e
--- /dev/null
+++ b/tests/auto/utils/filepath/CMakeLists.txt
@@ -0,0 +1,4 @@
+add_qtc_test(tst_utils_filepath
+ DEPENDS Utils
+ SOURCES tst_filepath.cpp
+)
diff --git a/tests/auto/utils/filepath/filepath.qbs b/tests/auto/utils/filepath/filepath.qbs
new file mode 100644
index 00000000000..73c106b2e8c
--- /dev/null
+++ b/tests/auto/utils/filepath/filepath.qbs
@@ -0,0 +1,11 @@
+import qbs
+
+QtcAutotest {
+ name: "FilePath autotest"
+ Depends { name: "Utils" }
+ Properties {
+ condition: qbs.toolchain.contains("gcc")
+ cpp.cxxFlags: base.concat(["-Wno-trigraphs"])
+ }
+ files: "tst_filepath.cpp"
+}
diff --git a/tests/auto/utils/filepath/tst_filepath.cpp b/tests/auto/utils/filepath/tst_filepath.cpp
new file mode 100644
index 00000000000..fa586ad5bcd
--- /dev/null
+++ b/tests/auto/utils/filepath/tst_filepath.cpp
@@ -0,0 +1,1662 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include <QRandomGenerator>
+#include <QtTest>
+
+#include <utils/filepath.h>
+#include <utils/hostosinfo.h>
+#include <utils/link.h>
+
+using namespace Utils;
+
+namespace QTest {
+template<>
+char *toString(const FilePath &filePath)
+{
+ return qstrdup(filePath.toString().toLocal8Bit().constData());
+}
+} // namespace QTest
+
+class tst_filepath : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+
+ void isEmpty_data();
+ void isEmpty();
+
+ void parentDir_data();
+ void parentDir();
+
+ void isChildOf_data();
+ void isChildOf();
+
+ void fileName_data();
+ void fileName();
+
+ void calcRelativePath_data();
+ void calcRelativePath();
+
+ void relativePath_specials();
+ void relativePath_data();
+ void relativePath();
+
+ void absolute_data();
+ void absolute();
+
+ void fromToString_data();
+ void fromToString();
+
+ void fromString_data();
+ void fromString();
+
+ void fromUserInput_data();
+ void fromUserInput();
+
+ void toString_data();
+ void toString();
+
+ void toFSPathString_data();
+ void toFSPathString();
+
+ void comparison_data();
+ void comparison();
+
+ void linkFromString_data();
+ void linkFromString();
+
+ void pathAppended_data();
+ void pathAppended();
+
+ void resolvePath_data();
+ void resolvePath();
+
+ void relativeChildPath_data();
+ void relativeChildPath();
+
+ void rootLength_data();
+ void rootLength();
+
+ void schemeAndHostLength_data();
+ void schemeAndHostLength();
+
+ void asyncLocalCopy();
+ void startsWithDriveLetter();
+ void startsWithDriveLetter_data();
+
+ void onDevice_data();
+ void onDevice();
+
+ void stringAppended();
+ void stringAppended_data();
+ void url();
+ void url_data();
+
+ void cleanPath_data();
+ void cleanPath();
+
+ void isSameFile_data();
+ void isSameFile();
+
+ void hostSpecialChars_data();
+ void hostSpecialChars();
+
+ void tmp();
+ void tmp_data();
+
+ void searchInWithFilter();
+
+private:
+ QTemporaryDir tempDir;
+ QString rootPath;
+ QString exeExt;
+};
+
+static void touch(const QDir &dir, const QString &filename, bool fill, bool executable = false)
+{
+ QFile file(dir.absoluteFilePath(filename));
+ file.open(QIODevice::WriteOnly);
+ if (executable)
+ file.setPermissions(file.permissions() | QFileDevice::ExeUser);
+
+ if (fill) {
+ QRandomGenerator *random = QRandomGenerator::global();
+ for (int i = 0; i < 10; ++i)
+ file.write(QString::number(random->generate(), 16).toUtf8());
+ }
+ file.close();
+}
+
+void tst_filepath::initTestCase()
+{
+ // initialize test for tst_filepath::relativePath*()
+ QVERIFY(tempDir.isValid());
+ rootPath = tempDir.path();
+ QDir dir(rootPath);
+ dir.mkpath("a/b/c/d");
+ dir.mkpath("a/x/y/z");
+ dir.mkpath("a/b/x/y/z");
+ dir.mkpath("x/y/z");
+ touch(dir, "a/b/c/d/file1.txt", false);
+ touch(dir, "a/x/y/z/file2.txt", false);
+ touch(dir, "a/file3.txt", false);
+ touch(dir, "x/y/file4.txt", false);
+
+ // initialize test for tst_filepath::asyncLocalCopy()
+ touch(dir, "x/y/fileToCopy.txt", true);
+
+// initialize test for tst_filepath::searchIn()
+#ifdef Q_OS_WIN
+ exeExt = ".exe";
+#endif
+
+ dir.mkpath("s/1");
+ dir.mkpath("s/2");
+ touch(dir, "s/1/testexe" + exeExt, false, true);
+ touch(dir, "s/2/testexe" + exeExt, false, true);
+}
+
+void tst_filepath::searchInWithFilter()
+{
+ const FilePaths dirs = {FilePath::fromUserInput(rootPath) / "s" / "1",
+ FilePath::fromUserInput(rootPath) / "s" / "2"};
+
+ FilePath exe = FilePath::fromUserInput("testexe" + exeExt)
+ .searchInDirectories(dirs, [](const FilePath &path) {
+ return path.path().contains("/2/");
+ });
+
+ QVERIFY(!exe.path().endsWith("/1/testexe" + exeExt)
+ && exe.path().endsWith("/2/testexe" + exeExt));
+
+ FilePath exe2 = FilePath::fromUserInput("testexe" + exeExt)
+ .searchInDirectories(dirs, [](const FilePath &path) {
+ return path.path().contains("/1/");
+ });
+
+ QVERIFY(!exe2.path().endsWith("/2/testexe" + exeExt)
+ && exe2.path().endsWith("/1/testexe" + exeExt));
+}
+
+void tst_filepath::isEmpty_data()
+{
+ QTest::addColumn<QString>("path");
+ QTest::addColumn<bool>("result");
+
+ QTest::newRow("empty path") << "" << true;
+ QTest::newRow("root only") << "/" << false;
+ QTest::newRow("//") << "//" << false;
+ QTest::newRow("scheme://host") << "scheme://host" << true; // Intentional (for now?)
+ QTest::newRow("scheme://host/") << "scheme://host/" << false;
+ QTest::newRow("scheme://host/a") << "scheme://host/a" << false;
+ QTest::newRow("scheme://host/.") << "scheme://host/." << false;
+}
+
+void tst_filepath::isEmpty()
+{
+ QFETCH(QString, path);
+ QFETCH(bool, result);
+
+ FilePath filePath = FilePath::fromString(path);
+ QCOMPARE(filePath.isEmpty(), result);
+}
+
+void tst_filepath::parentDir_data()
+{
+ QTest::addColumn<QString>("path");
+ QTest::addColumn<QString>("parentPath");
+ QTest::addColumn<QString>("expectFailMessage");
+
+ QTest::newRow("empty path") << ""
+ << ""
+ << "";
+ QTest::newRow("root only") << "/"
+ << ""
+ << "";
+ QTest::newRow("//") << "//"
+ << ""
+ << "";
+ QTest::newRow("/tmp/dir") << "/tmp/dir"
+ << "/tmp"
+ << "";
+ QTest::newRow("relative/path") << "relative/path"
+ << "relative"
+ << "";
+ QTest::newRow("relativepath") << "relativepath"
+ << "."
+ << "";
+
+ // Windows stuff:
+ QTest::newRow("C:/data") << "C:/data"
+ << "C:/"
+ << "";
+ QTest::newRow("C:/") << "C:/"
+ << ""
+ << "";
+ QTest::newRow("//./com1") << "//./com1"
+ << "//./"
+ << "";
+ QTest::newRow("//?/path") << "//?/path"
+ << "/"
+ << "Qt 4 cannot handle this path.";
+ QTest::newRow("/Global?\?/UNC/host") << "/Global?\?/UNC/host"
+ << "/Global?\?/UNC/host"
+ << "Qt 4 cannot handle this path.";
+ QTest::newRow("//server/directory/file") << "//server/directory/file"
+ << "//server/directory"
+ << "";
+ QTest::newRow("//server/directory") << "//server/directory"
+ << "//server/"
+ << "";
+ QTest::newRow("//server") << "//server"
+ << ""
+ << "";
+
+ QTest::newRow("qrc") << ":/foo/bar.txt"
+ << ":/foo"
+ << "";
+}
+
+void tst_filepath::parentDir()
+{
+ QFETCH(QString, path);
+ QFETCH(QString, parentPath);
+ QFETCH(QString, expectFailMessage);
+
+ FilePath result = FilePath::fromUserInput(path).parentDir();
+ if (!expectFailMessage.isEmpty())
+ QEXPECT_FAIL("", expectFailMessage.toUtf8().constData(), Continue);
+ QCOMPARE(result.toString(), parentPath);
+}
+
+void tst_filepath::isChildOf_data()
+{
+ QTest::addColumn<QString>("path");
+ QTest::addColumn<QString>("childPath");
+ QTest::addColumn<bool>("result");
+
+ QTest::newRow("empty path") << ""
+ << "/tmp" << false;
+ QTest::newRow("root only") << "/"
+ << "/tmp" << true;
+ QTest::newRow("/tmp/dir") << "/tmp"
+ << "/tmp/dir" << true;
+ QTest::newRow("relative/path") << "relative"
+ << "relative/path" << true;
+ QTest::newRow("/tmpdir") << "/tmp"
+ << "/tmpdir" << false;
+ QTest::newRow("same") << "/tmp/dir"
+ << "/tmp/dir" << false;
+
+ // Windows stuff:
+ QTest::newRow("C:/data") << "C:/"
+ << "C:/data" << true;
+ QTest::newRow("C:/") << ""
+ << "C:/" << false;
+ QTest::newRow("com-port") << "//./"
+ << "//./com1" << true;
+ QTest::newRow("extended-length-path") << "\\\\?\\C:\\"
+ << "\\\\?\\C:\\path" << true;
+ QTest::newRow("/Global?\?/UNC/host") << "/Global?\?/UNC/host"
+ << "/Global?\?/UNC/host/file" << true;
+ QTest::newRow("//server/directory/file") << "//server/directory"
+ << "//server/directory/file" << true;
+ QTest::newRow("//server/directory") << "//server"
+ << "//server/directory" << true;
+
+ QTest::newRow("qrc") << ":/foo/bar"
+ << ":/foo/bar/blah" << true;
+}
+
+void tst_filepath::isChildOf()
+{
+ QFETCH(QString, path);
+ QFETCH(QString, childPath);
+ QFETCH(bool, result);
+
+ const FilePath child = FilePath::fromUserInput(childPath);
+ const FilePath parent = FilePath::fromUserInput(path);
+
+ QCOMPARE(child.isChildOf(parent), result);
+}
+
+void tst_filepath::fileName_data()
+{
+ QTest::addColumn<QString>("path");
+ QTest::addColumn<int>("components");
+ QTest::addColumn<QString>("result");
+
+ QTest::newRow("empty 1") << "" << 0 << "";
+ QTest::newRow("empty 2") << "" << 1 << "";
+ QTest::newRow("basic") << "/foo/bar/baz" << 0 << "baz";
+ QTest::newRow("2 parts") << "/foo/bar/baz" << 1 << "bar/baz";
+ QTest::newRow("root no depth") << "/foo" << 0 << "foo";
+ QTest::newRow("root full") << "/foo" << 1 << "/foo";
+ QTest::newRow("root included") << "/foo/bar/baz" << 2 << "/foo/bar/baz";
+ QTest::newRow("too many parts") << "/foo/bar/baz" << 5 << "/foo/bar/baz";
+ QTest::newRow("windows root") << "C:/foo/bar/baz" << 2 << "C:/foo/bar/baz";
+ QTest::newRow("smb share") << "//server/share/file" << 2 << "//server/share/file";
+ QTest::newRow("no slashes") << "foobar" << 0 << "foobar";
+ QTest::newRow("no slashes with depth") << "foobar" << 1 << "foobar";
+ QTest::newRow("multiple slashes 1") << "/foo/bar////baz" << 0 << "baz";
+ QTest::newRow("multiple slashes 2") << "/foo/bar////baz" << 1 << "bar////baz";
+ QTest::newRow("multiple slashes 3") << "/foo////bar/baz" << 2 << "/foo////bar/baz";
+ QTest::newRow("single char 1") << "/a/b/c" << 0 << "c";
+ QTest::newRow("single char 2") << "/a/b/c" << 1 << "b/c";
+ QTest::newRow("single char 3") << "/a/b/c" << 2 << "/a/b/c";
+ QTest::newRow("slash at end 1") << "/a/b/" << 0 << "";
+ QTest::newRow("slash at end 2") << "/a/b/" << 1 << "b/";
+ QTest::newRow("slashes at end 1") << "/a/b//" << 0 << "";
+ QTest::newRow("slashes at end 2") << "/a/b//" << 1 << "b//";
+ QTest::newRow("root only 1") << "/" << 0 << "";
+ QTest::newRow("root only 2") << "/" << 1 << "/";
+ QTest::newRow("qrc 0") << ":/foo/bar" << 0 << "bar";
+ QTest::newRow("qrc with root") << ":/foo/bar" << 1 << ":/foo/bar";
+}
+
+void tst_filepath::fileName()
+{
+ QFETCH(QString, path);
+ QFETCH(int, components);
+ QFETCH(QString, result);
+ QCOMPARE(FilePath::fromString(path).fileNameWithPathComponents(components), result);
+}
+
+void tst_filepath::calcRelativePath_data()
+{
+ QTest::addColumn<QString>("absolutePath");
+ QTest::addColumn<QString>("anchorPath");
+ QTest::addColumn<QString>("result");
+
+ QTest::newRow("empty") << ""
+ << ""
+ << "";
+ QTest::newRow("leftempty") << ""
+ << "/"
+ << "";
+ QTest::newRow("rightempty") << "/"
+ << ""
+ << "";
+ QTest::newRow("root") << "/"
+ << "/"
+ << ".";
+ QTest::newRow("simple1") << "/a"
+ << "/"
+ << "a";
+ QTest::newRow("simple2") << "/"
+ << "/a"
+ << "..";
+ QTest::newRow("simple3") << "/a"
+ << "/a"
+ << ".";
+ QTest::newRow("extraslash1") << "/a/b/c"
+ << "/a/b/c"
+ << ".";
+ QTest::newRow("extraslash2") << "/a/b/c"
+ << "/a/b/c/"
+ << ".";
+ QTest::newRow("extraslash3") << "/a/b/c/"
+ << "/a/b/c"
+ << ".";
+ QTest::newRow("normal1") << "/a/b/c"
+ << "/a/x"
+ << "../b/c";
+ QTest::newRow("normal2") << "/a/b/c"
+ << "/a/x/y"
+ << "../../b/c";
+ QTest::newRow("normal3") << "/a/b/c"
+ << "/x/y"
+ << "../../a/b/c";
+}
+
+void tst_filepath::calcRelativePath()
+{
+ QFETCH(QString, absolutePath);
+ QFETCH(QString, anchorPath);
+ QFETCH(QString, result);
+ QString relativePath = Utils::FilePath::calcRelativePath(absolutePath, anchorPath);
+ QCOMPARE(relativePath, result);
+}
+
+void tst_filepath::relativePath_specials()
+{
+ QString path = FilePath("").relativePathFrom("").toString();
+ QCOMPARE(path, "");
+}
+
+void tst_filepath::relativePath_data()
+{
+ QTest::addColumn<QString>("relative");
+ QTest::addColumn<QString>("anchor");
+ QTest::addColumn<QString>("result");
+
+ QTest::newRow("samedir") << "/"
+ << "/"
+ << ".";
+ QTest::newRow("samedir_but_file") << "a/b/c/d/file1.txt"
+ << "a/b/c/d"
+ << "file1.txt";
+ QTest::newRow("samedir_but_file2") << "a/b/c/d"
+ << "a/b/c/d/file1.txt"
+ << ".";
+ QTest::newRow("dir2dir_1") << "a/b/c/d"
+ << "a/x/y/z"
+ << "../../../b/c/d";
+ QTest::newRow("dir2dir_2") << "a/b"
+ << "a/b/c"
+ << "..";
+ QTest::newRow("file2file_1") << "a/b/c/d/file1.txt"
+ << "a/file3.txt"
+ << "b/c/d/file1.txt";
+ QTest::newRow("dir2file_1") << "a/b/c"
+ << "a/x/y/z/file2.txt"
+ << "../../../b/c";
+ QTest::newRow("file2dir_1") << "a/b/c/d/file1.txt"
+ << "x/y"
+ << "../../a/b/c/d/file1.txt";
+}
+
+void tst_filepath::relativePath()
+{
+ QFETCH(QString, relative);
+ QFETCH(QString, anchor);
+ QFETCH(QString, result);
+ FilePath actualPath = FilePath::fromString(rootPath + "/" + relative)
+ .relativePathFrom(FilePath::fromString(rootPath + "/" + anchor));
+ QCOMPARE(actualPath.toString(), result);
+}
+
+void tst_filepath::rootLength_data()
+{
+ QTest::addColumn<QString>("path");
+ QTest::addColumn<int>("result");
+
+ QTest::newRow("empty") << "" << 0;
+ QTest::newRow("slash") << "/" << 1;
+ QTest::newRow("slash-rest") << "/abc" << 1;
+ QTest::newRow("rest") << "abc" << 0;
+ QTest::newRow("drive-slash") << "x:/" << 3;
+ QTest::newRow("drive-rest") << "x:abc" << 0;
+ QTest::newRow("drive-slash-rest") << "x:/abc" << 3;
+
+ QTest::newRow("unc-root") << "//" << 2;
+ QTest::newRow("unc-localhost-unfinished") << "//localhost" << 11;
+ QTest::newRow("unc-localhost") << "//localhost/" << 12;
+ QTest::newRow("unc-localhost-rest") << "//localhost/abs" << 12;
+ QTest::newRow("unc-localhost-drive") << "//localhost/c$" << 12;
+ QTest::newRow("unc-localhost-drive-slash") << "//localhost//c$/" << 12;
+ QTest::newRow("unc-localhost-drive-slash-rest") << "//localhost//c$/x" << 12;
+}
+
+void tst_filepath::rootLength()
+{
+ QFETCH(QString, path);
+ QFETCH(int, result);
+
+ int actual = FilePath::rootLength(path);
+ QCOMPARE(actual, result);
+}
+
+void tst_filepath::schemeAndHostLength_data()
+{
+ QTest::addColumn<QString>("path");
+ QTest::addColumn<int>("result");
+
+ QTest::newRow("empty") << "" << 0;
+ QTest::newRow("drive-slash-rest") << "x:/abc" << 0;
+ QTest::newRow("rest") << "abc" << 0;
+ QTest::newRow("slash-rest") << "/abc" << 0;
+ QTest::newRow("dev-empty") << "dev://" << 6;
+ QTest::newRow("dev-localhost-unfinished") << "dev://localhost" << 15;
+ QTest::newRow("dev-localhost") << "dev://localhost/" << 16;
+ QTest::newRow("dev-localhost-rest") << "dev://localhost/abs" << 16;
+ QTest::newRow("dev-localhost-drive") << "dev://localhost/c$" << 16;
+ QTest::newRow("dev-localhost-drive-slash") << "dev://localhost//c$/" << 16;
+ QTest::newRow("dev-localhost-drive-slash-rest") << "dev://localhost//c$/x" << 16;
+}
+
+void tst_filepath::schemeAndHostLength()
+{
+ QFETCH(QString, path);
+ QFETCH(int, result);
+
+ int actual = FilePath::schemeAndHostLength(path);
+ QCOMPARE(actual, result);
+}
+
+void tst_filepath::absolute_data()
+{
+ QTest::addColumn<FilePath>("path");
+ QTest::addColumn<FilePath>("absoluteFilePath");
+ QTest::addColumn<FilePath>("absolutePath");
+
+ QTest::newRow("absolute1") << FilePath::fromString("/") << FilePath::fromString("/")
+ << FilePath::fromString("/");
+ QTest::newRow("absolute2") << FilePath::fromString("C:/a/b") << FilePath::fromString("C:/a/b")
+ << FilePath::fromString("C:/a");
+ QTest::newRow("absolute3") << FilePath::fromString("/a/b") << FilePath::fromString("/a/b")
+ << FilePath::fromString("/a");
+ QTest::newRow("absolute4") << FilePath::fromString("/a/b/..") << FilePath::fromString("/a")
+ << FilePath::fromString("/");
+ QTest::newRow("absolute5") << FilePath::fromString("/a/b/c/../d")
+ << FilePath::fromString("/a/b/d") << FilePath::fromString("/a/b");
+ QTest::newRow("absolute6") << FilePath::fromString("/a/../b/c/d")
+ << FilePath::fromString("/b/c/d") << FilePath::fromString("/b/c");
+ QTest::newRow("default-constructed") << FilePath() << FilePath() << FilePath();
+ QTest::newRow("relative") << FilePath::fromString("a/b")
+ << FilePath::fromString(QDir::currentPath() + "/a/b")
+ << FilePath::fromString(QDir::currentPath() + "/a");
+ QTest::newRow("qrc") << FilePath::fromString(":/foo/bar.txt")
+ << FilePath::fromString(":/foo/bar.txt") << FilePath::fromString(":/foo");
+}
+
+void tst_filepath::absolute()
+{
+ QFETCH(FilePath, path);
+ QFETCH(FilePath, absoluteFilePath);
+ QFETCH(FilePath, absolutePath);
+ QCOMPARE(path.absoluteFilePath(), absoluteFilePath);
+ QCOMPARE(path.absolutePath(), absolutePath);
+}
+
+void tst_filepath::toString_data()
+{
+ QTest::addColumn<QString>("scheme");
+ QTest::addColumn<QString>("host");
+ QTest::addColumn<QString>("path");
+ QTest::addColumn<QString>("result");
+ QTest::addColumn<QString>("userResult");
+
+ QTest::newRow("empty") << ""
+ << ""
+ << ""
+ << ""
+ << "";
+ QTest::newRow("scheme") << "http"
+ << ""
+ << ""
+ << "https://"
+ << "https://";
+ QTest::newRow("scheme-and-host") << "http"
+ << "127.0.0.1"
+ << ""
+ << "http://127.0.0.1"
+ << "http://127.0.0.1";
+ QTest::newRow("root") << "http"
+ << "127.0.0.1"
+ << "/"
+ << "http://127.0.0.1/"
+ << "http://127.0.0.1/";
+
+ QTest::newRow("root-folder") << ""
+ << ""
+ << "/"
+ << "/"
+ << "/";
+ QTest::newRow("qtc-dev-root-folder-linux") << ""
+ << ""
+ << "/__qtc_devices__"
+ << "/__qtc_devices__"
+ << "/__qtc_devices__";
+ QTest::newRow("qtc-dev-root-folder-win") << ""
+ << ""
+ << "c:/__qtc_devices__"
+ << "c:/__qtc_devices__"
+ << "c:/__qtc_devices__";
+ QTest::newRow("qtc-dev-type-root-folder-linux") << ""
+ << ""
+ << "/__qtc_devices__/docker"
+ << "/__qtc_devices__/docker"
+ << "/__qtc_devices__/docker";
+ QTest::newRow("qtc-dev-type-root-folder-win") << ""
+ << ""
+ << "c:/__qtc_devices__/docker"
+ << "c:/__qtc_devices__/docker"
+ << "c:/__qtc_devices__/docker";
+ QTest::newRow("qtc-root-folder") << "docker"
+ << "alpine:latest"
+ << "/"
+ << "docker://alpine:latest/"
+ << "docker://alpine:latest/";
+ QTest::newRow("qtc-root-folder-rel") << "docker"
+ << "alpine:latest"
+ << ""
+ << "docker://alpine:latest"
+ << "docker://alpine:latest";
+}
+
+void tst_filepath::toString()
+{
+ QFETCH(QString, scheme);
+ QFETCH(QString, host);
+ QFETCH(QString, path);
+ QFETCH(QString, result);
+ QFETCH(QString, userResult);
+
+ FilePath filePath = FilePath::fromParts(scheme, host, path);
+ QCOMPARE(filePath.toString(), result);
+ QString cleanedOutput = filePath.needsDevice() ? filePath.toUserOutput()
+ : QDir::cleanPath(filePath.toUserOutput());
+ QCOMPARE(cleanedOutput, userResult);
+}
+
+void tst_filepath::toFSPathString_data()
+{
+ QTest::addColumn<QString>("scheme");
+ QTest::addColumn<QString>("host");
+ QTest::addColumn<QString>("path");
+ QTest::addColumn<QString>("result");
+ QTest::addColumn<QString>("userResult");
+
+ QTest::newRow("empty") << ""
+ << ""
+ << ""
+ << ""
+ << "";
+ QTest::newRow("scheme") << "http"
+ << ""
+ << "" << QDir::rootPath() + "__qtc_devices__/http/"
+ << "https://";
+ QTest::newRow("scheme-and-host") << "http"
+ << "127.0.0.1"
+ << "" << QDir::rootPath() + "__qtc_devices__/http/127.0.0.1"
+ << "http://127.0.0.1";
+ QTest::newRow("root") << "http"
+ << "127.0.0.1"
+ << "/" << QDir::rootPath() + "__qtc_devices__/http/127.0.0.1/"
+ << "http://127.0.0.1/";
+
+ QTest::newRow("root-folder") << ""
+ << ""
+ << "/"
+ << "/"
+ << "/";
+ QTest::newRow("qtc-dev-root-folder")
+ << ""
+ << "" << QDir::rootPath() + "__qtc_devices__" << QDir::rootPath() + "__qtc_devices__"
+ << QDir::rootPath() + "__qtc_devices__";
+ QTest::newRow("qtc-dev-type-root-folder") << ""
+ << "" << QDir::rootPath() + "__qtc_devices__/docker"
+ << QDir::rootPath() + "__qtc_devices__/docker"
+ << QDir::rootPath() + "__qtc_devices__/docker";
+ QTest::newRow("qtc-root-folder")
+ << "docker"
+ << "alpine:latest"
+ << "/" << QDir::rootPath() + "__qtc_devices__/docker/alpine:latest/"
+ << "docker://alpine:latest/";
+ QTest::newRow("qtc-root-folder-rel")
+ << "docker"
+ << "alpine:latest"
+ << "" << QDir::rootPath() + "__qtc_devices__/docker/alpine:latest"
+ << "docker://alpine:latest";
+}
+
+void tst_filepath::toFSPathString()
+{
+ QFETCH(QString, scheme);
+ QFETCH(QString, host);
+ QFETCH(QString, path);
+ QFETCH(QString, result);
+ QFETCH(QString, userResult);
+
+ FilePath filePath = FilePath::fromParts(scheme, host, path);
+ QCOMPARE(filePath.toFSPathString(), result);
+ QString cleanedOutput = filePath.needsDevice() ? filePath.toUserOutput()
+ : QDir::cleanPath(filePath.toUserOutput());
+ QCOMPARE(cleanedOutput, userResult);
+}
+
+enum ExpectedPass { PassEverywhere = 0, FailOnWindows = 1, FailOnLinux = 2, FailEverywhere = 3 };
+
+class FromStringData
+{
+public:
+ FromStringData(const QString &input,
+ const QString &scheme,
+ const QString &host,
+ const QString &path,
+ ExpectedPass expectedPass = PassEverywhere)
+ : input(input)
+ , scheme(scheme)
+ , host(host)
+ , path(path)
+ , expectedPass(expectedPass)
+ {}
+
+ QString input;
+ QString scheme;
+ QString host;
+ QString path;
+ ExpectedPass expectedPass = PassEverywhere;
+};
+
+Q_DECLARE_METATYPE(FromStringData);
+
+void tst_filepath::fromString_data()
+{
+ using D = FromStringData;
+ QTest::addColumn<D>("data");
+
+ QTest::newRow("empty") << D("", "", "", "");
+ QTest::newRow("single-colon") << D(":", "", "", ":");
+ QTest::newRow("single-slash") << D("/", "", "", "/");
+ QTest::newRow("single-char") << D("a", "", "", "a");
+ QTest::newRow("relative") << D("./rel", "", "", "./rel");
+ QTest::newRow("qrc") << D(":/test.txt", "", "", ":/test.txt");
+ QTest::newRow("qrc-no-slash") << D(":test.txt", "", "", ":test.txt");
+
+ QTest::newRow("unc-incomplete") << D("//", "", "", "//");
+ QTest::newRow("unc-incomplete-only-server") << D("//server", "", "", "//server");
+ QTest::newRow("unc-incomplete-only-server-2") << D("//server/", "", "", "//server/");
+ QTest::newRow("unc-server-and-share") << D("//server/share", "", "", "//server/share");
+ QTest::newRow("unc-server-and-share-2") << D("//server/share/", "", "", "//server/share/");
+ QTest::newRow("unc-full") << D("//server/share/test.txt", "", "", "//server/share/test.txt");
+
+ QTest::newRow("unix-root") << D("/", "", "", "/");
+ QTest::newRow("unix-folder") << D("/tmp", "", "", "/tmp");
+ QTest::newRow("unix-folder-with-trailing-slash") << D("/tmp/", "", "", "/tmp/");
+
+ QTest::newRow("windows-root") << D("c:", "", "", "c:");
+ QTest::newRow("windows-folder") << D("c:/Windows", "", "", "c:/Windows");
+ QTest::newRow("windows-folder-with-trailing-slash") << D("c:/Windows/", "", "", "c:/Windows/");
+ QTest::newRow("windows-folder-slash") << D("C:/Windows", "", "", "C:/Windows");
+
+ QTest::newRow("docker-root-url") << D("docker://1234/", "docker", "1234", "/");
+ QTest::newRow("docker-root-url-special-linux")
+ << D("/__qtc_devices__/docker/1234/", "docker", "1234", "/");
+ QTest::newRow("docker-root-url-special-win")
+ << D("c:/__qtc_devices__/docker/1234/", "docker", "1234", "/");
+ QTest::newRow("docker-relative-path") << D("docker://1234/./rel", "docker", "1234", "rel");
+
+ QTest::newRow("qtc-dev-linux") << D("/__qtc_devices__", "", "", "/__qtc_devices__");
+ QTest::newRow("qtc-dev-win") << D("c:/__qtc_devices__", "", "", "c:/__qtc_devices__");
+ QTest::newRow("qtc-dev-type-linux")
+ << D("/__qtc_devices__/docker", "", "", "/__qtc_devices__/docker");
+ QTest::newRow("qtc-dev-type-win")
+ << D("c:/__qtc_devices__/docker", "", "", "c:/__qtc_devices__/docker");
+ QTest::newRow("qtc-dev-type-dev-linux")
+ << D("/__qtc_devices__/docker/1234", "docker", "1234", "/");
+ QTest::newRow("qtc-dev-type-dev-win")
+ << D("c:/__qtc_devices__/docker/1234", "docker", "1234", "/");
+
+ // "Remote Windows" is currently truly not supported.
+ QTest::newRow("cross-os-linux") << D("/__qtc_devices__/docker/1234/c:/test.txt",
+ "docker",
+ "1234",
+ "c:/test.txt",
+ FailEverywhere);
+ QTest::newRow("cross-os-win") << D("c:/__qtc_devices__/docker/1234/c:/test.txt",
+ "docker",
+ "1234",
+ "c:/test.txt",
+ FailEverywhere);
+ QTest::newRow("cross-os-unclean-linux") << D("/__qtc_devices__/docker/1234/c:\\test.txt",
+ "docker",
+ "1234",
+ "c:/test.txt",
+ FailEverywhere);
+ QTest::newRow("cross-os-unclean-win") << D("c:/__qtc_devices__/docker/1234/c:\\test.txt",
+ "docker",
+ "1234",
+ "c:/test.txt",
+ FailEverywhere);
+
+ QTest::newRow("unc-full-in-docker-linux")
+ << D("/__qtc_devices__/docker/1234//server/share/test.txt",
+ "docker",
+ "1234",
+ "//server/share/test.txt");
+ QTest::newRow("unc-full-in-docker-win")
+ << D("c:/__qtc_devices__/docker/1234//server/share/test.txt",
+ "docker",
+ "1234",
+ "//server/share/test.txt");
+
+ QTest::newRow("unc-dos-1") << D("//?/c:", "", "", "//?/c:");
+ QTest::newRow("unc-dos-com") << D("//./com1", "", "", "//./com1");
+}
+
+void tst_filepath::fromString()
+{
+ QFETCH(FromStringData, data);
+
+ FilePath filePath = FilePath::fromString(data.input);
+
+ bool expectFail = ((data.expectedPass & FailOnLinux) && !HostOsInfo::isWindowsHost())
+ || ((data.expectedPass & FailOnWindows) && HostOsInfo::isWindowsHost());
+
+ if (expectFail) {
+ QString actual = filePath.scheme() + '|' + filePath.host() + '|' + filePath.path();
+ QString expected = data.scheme + '|' + data.host + '|' + data.path;
+ QEXPECT_FAIL("", "", Continue);
+ QCOMPARE(actual, expected);
+ return;
+ }
+
+ QCOMPARE(filePath.scheme(), data.scheme);
+ QCOMPARE(filePath.host(), data.host);
+ QCOMPARE(filePath.path(), data.path);
+}
+
+void tst_filepath::fromUserInput_data()
+{
+ using D = FromStringData;
+ QTest::addColumn<D>("data");
+
+ QTest::newRow("empty") << D("", "", "", "");
+ QTest::newRow("single-colon") << D(":", "", "", ":");
+ QTest::newRow("single-slash") << D("/", "", "", "/");
+ QTest::newRow("single-char") << D("a", "", "", "a");
+ QTest::newRow("relative") << D("./rel", "", "", "rel");
+ QTest::newRow("qrc") << D(":/test.txt", "", "", ":/test.txt");
+ QTest::newRow("qrc-no-slash") << D(":test.txt", "", "", ":test.txt");
+ QTest::newRow("tilde") << D("~/", "", "", QDir::homePath());
+ QTest::newRow("tilde-with-path") << D("~/foo", "", "", QDir::homePath() + "/foo");
+ QTest::newRow("tilde-only") << D("~", "", "", "~");
+
+ QTest::newRow("unc-incomplete") << D("//", "", "", "//");
+ QTest::newRow("unc-incomplete-only-server") << D("//server", "", "", "//server");
+ QTest::newRow("unc-incomplete-only-server-2") << D("//server/", "", "", "//server/");
+ QTest::newRow("unc-server-and-share") << D("//server/share", "", "", "//server/share");
+ QTest::newRow("unc-server-and-share-2") << D("//server/share/", "", "", "//server/share");
+ QTest::newRow("unc-full") << D("//server/share/test.txt", "", "", "//server/share/test.txt");
+
+ QTest::newRow("unix-root") << D("/", "", "", "/");
+ QTest::newRow("unix-folder") << D("/tmp", "", "", "/tmp");
+ QTest::newRow("unix-folder-with-trailing-slash") << D("/tmp/", "", "", "/tmp");
+
+ QTest::newRow("windows-root") << D("c:", "", "", "c:");
+ QTest::newRow("windows-folder") << D("c:/Windows", "", "", "c:/Windows");
+ QTest::newRow("windows-folder-with-trailing-slash") << D("c:\\Windows\\", "", "", "c:/Windows");
+ QTest::newRow("windows-folder-slash") << D("C:/Windows", "", "", "C:/Windows");
+
+ QTest::newRow("docker-root-url") << D("docker://1234/", "docker", "1234", "/");
+ QTest::newRow("docker-root-url-special-linux")
+ << D("/__qtc_devices__/docker/1234/", "docker", "1234", "/");
+ QTest::newRow("docker-root-url-special-win")
+ << D("c:/__qtc_devices__/docker/1234/", "docker", "1234", "/");
+ QTest::newRow("docker-relative-path")
+ << D("docker://1234/./rel", "docker", "1234", "rel", FailEverywhere);
+
+ QTest::newRow("qtc-dev-linux") << D("/__qtc_devices__", "", "", "/__qtc_devices__");
+ QTest::newRow("qtc-dev-win") << D("c:/__qtc_devices__", "", "", "c:/__qtc_devices__");
+ QTest::newRow("qtc-dev-type-linux")
+ << D("/__qtc_devices__/docker", "", "", "/__qtc_devices__/docker");
+ QTest::newRow("qtc-dev-type-win")
+ << D("c:/__qtc_devices__/docker", "", "", "c:/__qtc_devices__/docker");
+ QTest::newRow("qtc-dev-type-dev-linux")
+ << D("/__qtc_devices__/docker/1234", "docker", "1234", "/");
+ QTest::newRow("qtc-dev-type-dev-win")
+ << D("c:/__qtc_devices__/docker/1234", "docker", "1234", "/");
+
+ // "Remote Windows" is currently truly not supported.
+ QTest::newRow("cross-os-linux") << D("/__qtc_devices__/docker/1234/c:/test.txt",
+ "docker",
+ "1234",
+ "c:/test.txt",
+ FailEverywhere);
+ QTest::newRow("cross-os-win") << D("c:/__qtc_devices__/docker/1234/c:/test.txt",
+ "docker",
+ "1234",
+ "c:/test.txt",
+ FailEverywhere);
+ QTest::newRow("cross-os-unclean-linux") << D("/__qtc_devices__/docker/1234/c:\\test.txt",
+ "docker",
+ "1234",
+ "c:/test.txt",
+ FailEverywhere);
+ QTest::newRow("cross-os-unclean-win") << D("c:/__qtc_devices__/docker/1234/c:\\test.txt",
+ "docker",
+ "1234",
+ "c:/test.txt",
+ FailEverywhere);
+
+ QTest::newRow("unc-full-in-docker-linux")
+ << D("/__qtc_devices__/docker/1234//server/share/test.txt",
+ "docker",
+ "1234",
+ "//server/share/test.txt",
+ FailEverywhere);
+ QTest::newRow("unc-full-in-docker-win")
+ << D("c:/__qtc_devices__/docker/1234//server/share/test.txt",
+ "docker",
+ "1234",
+ "//server/share/test.txt",
+ FailEverywhere);
+
+ QTest::newRow("unc-dos-1") << D("//?/c:", "", "", "c:");
+ QTest::newRow("unc-dos-com") << D("//./com1", "", "", "//./com1");
+}
+
+void tst_filepath::fromUserInput()
+{
+ QFETCH(FromStringData, data);
+
+ FilePath filePath = FilePath::fromUserInput(data.input);
+
+ bool expectFail = ((data.expectedPass & FailOnLinux) && !HostOsInfo::isWindowsHost())
+ || ((data.expectedPass & FailOnWindows) && HostOsInfo::isWindowsHost());
+
+ if (expectFail) {
+ QString actual = filePath.scheme() + '|' + filePath.host() + '|' + filePath.path();
+ QString expected = data.scheme + '|' + data.host + '|' + data.path;
+ QEXPECT_FAIL("", "", Continue);
+ QCOMPARE(actual, expected);
+ return;
+ }
+
+ QCOMPARE(filePath.scheme(), data.scheme);
+ QCOMPARE(filePath.host(), data.host);
+ QCOMPARE(filePath.path(), data.path);
+}
+
+void tst_filepath::fromToString_data()
+{
+ QTest::addColumn<QString>("scheme");
+ QTest::addColumn<QString>("host");
+ QTest::addColumn<QString>("path");
+ QTest::addColumn<QString>("full");
+
+ QTest::newRow("s0") << ""
+ << ""
+ << ""
+ << "";
+ QTest::newRow("s1") << ""
+ << ""
+ << "/"
+ << "/";
+ QTest::newRow("s2") << ""
+ << ""
+ << "a/b/c/d"
+ << "a/b/c/d";
+ QTest::newRow("s3") << ""
+ << ""
+ << "/a/b"
+ << "/a/b";
+
+ QTest::newRow("s4") << "docker"
+ << "1234abcdef"
+ << "/bin/ls"
+ << "docker://1234abcdef/bin/ls";
+ QTest::newRow("s5") << "docker"
+ << "1234"
+ << "/bin/ls"
+ << "docker://1234/bin/ls";
+
+ // This is not a proper URL.
+ QTest::newRow("s6") << "docker"
+ << "1234"
+ << "somefile"
+ << "docker://1234/./somefile";
+
+ // Local Windows paths:
+ QTest::newRow("w1") << ""
+ << ""
+ << "C:/data"
+ << "C:/data";
+ QTest::newRow("w2") << ""
+ << ""
+ << "C:/"
+ << "C:/";
+ QTest::newRow("w3") << ""
+ << ""
+ << "/Global?\?/UNC/host"
+ << "/Global?\?/UNC/host";
+ QTest::newRow("w4") << ""
+ << ""
+ << "//server/dir/file"
+ << "//server/dir/file";
+}
+
+void tst_filepath::fromToString()
+{
+ QFETCH(QString, full);
+ QFETCH(QString, scheme);
+ QFETCH(QString, host);
+ QFETCH(QString, path);
+
+ FilePath filePath = FilePath::fromString(full);
+
+ QCOMPARE(filePath.toString(), full);
+
+ QCOMPARE(filePath.scheme(), scheme);
+ QCOMPARE(filePath.host(), host);
+ QCOMPARE(filePath.path(), path);
+
+ FilePath copy = FilePath::fromParts(scheme, host, path);
+ QCOMPARE(copy.toString(), full);
+}
+
+void tst_filepath::comparison()
+{
+ QFETCH(QString, left);
+ QFETCH(QString, right);
+ QFETCH(bool, hostSensitive);
+ QFETCH(bool, expected);
+
+ HostOsInfo::setOverrideFileNameCaseSensitivity(hostSensitive ? Qt::CaseSensitive
+ : Qt::CaseInsensitive);
+
+ FilePath l = FilePath::fromUserInput(left);
+ FilePath r = FilePath::fromUserInput(right);
+ QCOMPARE(l == r, expected);
+}
+
+void tst_filepath::comparison_data()
+{
+ QTest::addColumn<QString>("left");
+ QTest::addColumn<QString>("right");
+ QTest::addColumn<bool>("hostSensitive");
+ QTest::addColumn<bool>("expected");
+
+ QTest::newRow("r1") << "Abc"
+ << "abc" << true << false;
+ QTest::newRow("r2") << "Abc"
+ << "abc" << false << true;
+ QTest::newRow("r3") << "x://y/Abc"
+ << "x://y/abc" << true << false;
+ QTest::newRow("r4") << "x://y/Abc"
+ << "x://y/abc" << false << false;
+
+ QTest::newRow("s1") << "abc"
+ << "abc" << true << true;
+ QTest::newRow("s2") << "abc"
+ << "abc" << false << true;
+ QTest::newRow("s3") << "x://y/abc"
+ << "x://y/abc" << true << true;
+ QTest::newRow("s4") << "x://y/abc"
+ << "x://y/abc" << false << true;
+}
+
+void tst_filepath::linkFromString()
+{
+ QFETCH(QString, testFile);
+ QFETCH(Utils::FilePath, filePath);
+ QFETCH(int, line);
+ QFETCH(int, column);
+ const Link link = Link::fromString(testFile, true);
+ QCOMPARE(link.targetFilePath, filePath);
+ QCOMPARE(link.targetLine, line);
+ QCOMPARE(link.targetColumn, column);
+}
+
+void tst_filepath::linkFromString_data()
+{
+ QTest::addColumn<QString>("testFile");
+ QTest::addColumn<Utils::FilePath>("filePath");
+ QTest::addColumn<int>("line");
+ QTest::addColumn<int>("column");
+
+ QTest::newRow("no-line-no-column")
+ << QString("someFile.txt") << FilePath("someFile.txt") << -1 << -1;
+ QTest::newRow(": at end") << QString::fromLatin1("someFile.txt:") << FilePath("someFile.txt")
+ << 0 << -1;
+ QTest::newRow("+ at end") << QString::fromLatin1("someFile.txt+") << FilePath("someFile.txt")
+ << 0 << -1;
+ QTest::newRow(": for column") << QString::fromLatin1("someFile.txt:10:")
+ << FilePath("someFile.txt") << 10 << -1;
+ QTest::newRow("+ for column") << QString::fromLatin1("someFile.txt:10+")
+ << FilePath("someFile.txt") << 10 << -1;
+ QTest::newRow(": and + at end")
+ << QString::fromLatin1("someFile.txt:+") << FilePath("someFile.txt") << 0 << -1;
+ QTest::newRow("empty line") << QString::fromLatin1("someFile.txt:+10")
+ << FilePath("someFile.txt") << 0 << 9;
+ QTest::newRow(":line-no-column") << QString::fromLatin1("/some/path/file.txt:42")
+ << FilePath("/some/path/file.txt") << 42 << -1;
+ QTest::newRow("+line-no-column") << QString::fromLatin1("/some/path/file.txt+42")
+ << FilePath("/some/path/file.txt") << 42 << -1;
+ QTest::newRow(":line-:column") << QString::fromLatin1("/some/path/file.txt:42:3")
+ << FilePath("/some/path/file.txt") << 42 << 2;
+ QTest::newRow(":line-+column") << QString::fromLatin1("/some/path/file.txt:42+33")
+ << FilePath("/some/path/file.txt") << 42 << 32;
+ QTest::newRow("+line-:column") << QString::fromLatin1("/some/path/file.txt+142:30")
+ << FilePath("/some/path/file.txt") << 142 << 29;
+ QTest::newRow("+line-+column") << QString::fromLatin1("/some/path/file.txt+142+33")
+ << FilePath("/some/path/file.txt") << 142 << 32;
+ QTest::newRow("( at end") << QString::fromLatin1("/some/path/file.txt(")
+ << FilePath("/some/path/file.txt") << -1 << -1;
+ QTest::newRow("(42 at end") << QString::fromLatin1("/some/path/file.txt(42")
+ << FilePath("/some/path/file.txt") << 42 << -1;
+ QTest::newRow("(42) at end") << QString::fromLatin1("/some/path/file.txt(42)")
+ << FilePath("/some/path/file.txt") << 42 << -1;
+}
+
+void tst_filepath::pathAppended()
+{
+ QFETCH(QString, left);
+ QFETCH(QString, right);
+ QFETCH(QString, expected);
+
+ const FilePath fleft = FilePath::fromString(left);
+ const FilePath fexpected = FilePath::fromString(expected);
+
+ const FilePath result = fleft.pathAppended(right);
+
+ QCOMPARE(result, fexpected);
+}
+
+void tst_filepath::pathAppended_data()
+{
+ QTest::addColumn<QString>("left");
+ QTest::addColumn<QString>("right");
+ QTest::addColumn<QString>("expected");
+
+ QTest::newRow("p0") << ""
+ << ""
+ << "";
+ QTest::newRow("p1") << ""
+ << "/"
+ << "/";
+ QTest::newRow("p2") << ""
+ << "c/"
+ << "c/";
+ QTest::newRow("p3") << ""
+ << "/d"
+ << "/d";
+ QTest::newRow("p4") << ""
+ << "c/d"
+ << "c/d";
+
+ QTest::newRow("r0") << "/"
+ << ""
+ << "/";
+ QTest::newRow("r1") << "/"
+ << "/"
+ << "/";
+ QTest::newRow("r2") << "/"
+ << "c/"
+ << "/c/";
+ QTest::newRow("r3") << "/"
+ << "/d"
+ << "/d";
+ QTest::newRow("r4") << "/"
+ << "c/d"
+ << "/c/d";
+
+ QTest::newRow("s0") << "/b"
+ << ""
+ << "/b";
+ QTest::newRow("s1") << "/b"
+ << "/"
+ << "/b/";
+ QTest::newRow("s2") << "/b"
+ << "c/"
+ << "/b/c/";
+ QTest::newRow("s3") << "/b"
+ << "/d"
+ << "/b/d";
+ QTest::newRow("s4") << "/b"
+ << "c/d"
+ << "/b/c/d";
+
+ QTest::newRow("t0") << "a/"
+ << ""
+ << "a/";
+ QTest::newRow("t1") << "a/"
+ << "/"
+ << "a/";
+ QTest::newRow("t2") << "a/"
+ << "c/"
+ << "a/c/";
+ QTest::newRow("t3") << "a/"
+ << "/d"
+ << "a/d";
+ QTest::newRow("t4") << "a/"
+ << "c/d"
+ << "a/c/d";
+
+ QTest::newRow("u0") << "a/b"
+ << ""
+ << "a/b";
+ QTest::newRow("u1") << "a/b"
+ << "/"
+ << "a/b/";
+ QTest::newRow("u2") << "a/b"
+ << "c/"
+ << "a/b/c/";
+ QTest::newRow("u3") << "a/b"
+ << "/d"
+ << "a/b/d";
+ QTest::newRow("u4") << "a/b"
+ << "c/d"
+ << "a/b/c/d";
+
+ if (HostOsInfo::isWindowsHost()) {
+ QTest::newRow("win-1") << "c:"
+ << "/a/b"
+ << "c:/a/b";
+ QTest::newRow("win-2") << "c:/"
+ << "/a/b"
+ << "c:/a/b";
+ QTest::newRow("win-3") << "c:/"
+ << "a/b"
+ << "c:/a/b";
+ }
+}
+
+void tst_filepath::resolvePath_data()
+{
+ QTest::addColumn<FilePath>("left");
+ QTest::addColumn<FilePath>("right");
+ QTest::addColumn<FilePath>("expected");
+
+ QTest::newRow("empty") << FilePath() << FilePath() << FilePath();
+ QTest::newRow("s0") << FilePath("/") << FilePath("b") << FilePath("/b");
+ QTest::newRow("s1") << FilePath() << FilePath("b") << FilePath("b");
+ QTest::newRow("s2") << FilePath("a") << FilePath() << FilePath("a");
+ QTest::newRow("s3") << FilePath("a") << FilePath("b") << FilePath("a/b");
+ QTest::newRow("s4") << FilePath("/a") << FilePath("/b") << FilePath("/b");
+ QTest::newRow("s5") << FilePath("a") << FilePath("/b") << FilePath("/b");
+ QTest::newRow("s6") << FilePath("/a") << FilePath("b") << FilePath("/a/b");
+ QTest::newRow("s7") << FilePath("/a") << FilePath(".") << FilePath("/a");
+ QTest::newRow("s8") << FilePath("/a") << FilePath("./b") << FilePath("/a/b");
+}
+
+void tst_filepath::resolvePath()
+{
+ QFETCH(FilePath, left);
+ QFETCH(FilePath, right);
+ QFETCH(FilePath, expected);
+
+ const FilePath result = left.resolvePath(right);
+
+ QCOMPARE(result, expected);
+}
+
+void tst_filepath::relativeChildPath_data()
+{
+ QTest::addColumn<FilePath>("parent");
+ QTest::addColumn<FilePath>("child");
+ QTest::addColumn<FilePath>("expected");
+
+ QTest::newRow("empty") << FilePath() << FilePath() << FilePath();
+
+ QTest::newRow("simple-0") << FilePath("/a") << FilePath("/a/b") << FilePath("b");
+ QTest::newRow("simple-1") << FilePath("/a/") << FilePath("/a/b") << FilePath("b");
+ QTest::newRow("simple-2") << FilePath("/a") << FilePath("/a/b/c/d/e/f")
+ << FilePath("b/c/d/e/f");
+
+ QTest::newRow("not-0") << FilePath("/x") << FilePath("/a/b") << FilePath();
+ QTest::newRow("not-1") << FilePath("/a/b/c") << FilePath("/a/b") << FilePath();
+}
+
+void tst_filepath::relativeChildPath()
+{
+ QFETCH(FilePath, parent);
+ QFETCH(FilePath, child);
+ QFETCH(FilePath, expected);
+
+ const FilePath result = child.relativeChildPath(parent);
+
+ QCOMPARE(result, expected);
+}
+
+void tst_filepath::asyncLocalCopy()
+{
+ const FilePath orig = FilePath::fromString(rootPath).pathAppended("x/y/fileToCopy.txt");
+ QVERIFY(orig.exists());
+ const FilePath dest = FilePath::fromString(rootPath).pathAppended("x/fileToCopyDest.txt");
+ bool wasCalled = false;
+ // When QTRY_VERIFY failed, don't call the continuation after we leave this method
+ QObject context;
+ auto afterCopy = [&orig, &dest, &wasCalled](expected_str<void> result) {
+ QVERIFY(result);
+ // check existence, size and content
+ QVERIFY(dest.exists());
+ QCOMPARE(dest.fileSize(), orig.fileSize());
+ QCOMPARE(dest.fileContents(), orig.fileContents());
+ wasCalled = true;
+ };
+ orig.asyncCopy(dest, &context, afterCopy);
+ QTRY_VERIFY(wasCalled);
+}
+
+void tst_filepath::startsWithDriveLetter_data()
+{
+ QTest::addColumn<FilePath>("path");
+ QTest::addColumn<bool>("expected");
+
+ QTest::newRow("empty") << FilePath() << false;
+ QTest::newRow("simple-win") << FilePath::fromString("c:/a") << true;
+ QTest::newRow("simple-linux") << FilePath::fromString("/c:/a") << false;
+ QTest::newRow("relative") << FilePath("a/b") << false;
+}
+
+void tst_filepath::startsWithDriveLetter()
+{
+ QFETCH(FilePath, path);
+ QFETCH(bool, expected);
+
+ QCOMPARE(path.startsWithDriveLetter(), expected);
+}
+
+void tst_filepath::onDevice_data()
+{
+ QTest::addColumn<FilePath>("path");
+ QTest::addColumn<FilePath>("templatePath");
+ QTest::addColumn<FilePath>("expected");
+
+ QTest::newRow("empty") << FilePath() << FilePath() << FilePath();
+ QTest::newRow("same-local") << FilePath("/a/b") << FilePath("/a/b") << FilePath("/a/b");
+ QTest::newRow("same-docker") << FilePath("docker://1234/a/b") << FilePath("docker://1234/e")
+ << FilePath("docker://1234/a/b");
+
+ QTest::newRow("docker-to-local")
+ << FilePath("docker://1234/a/b") << FilePath("/c/d") << FilePath("/a/b");
+ QTest::newRow("local-to-docker")
+ << FilePath("/a/b") << FilePath("docker://1234/c/d") << FilePath("docker://1234/a/b");
+}
+
+void tst_filepath::onDevice()
+{
+ QFETCH(FilePath, path);
+ QFETCH(FilePath, templatePath);
+ QFETCH(FilePath, expected);
+
+ QCOMPARE(path.onDevice(templatePath), expected);
+}
+
+void tst_filepath::stringAppended_data()
+{
+ QTest::addColumn<FilePath>("left");
+ QTest::addColumn<QString>("right");
+ QTest::addColumn<FilePath>("expected");
+
+ QTest::newRow("empty") << FilePath() << QString() << FilePath();
+ QTest::newRow("empty-left") << FilePath() << "a" << FilePath("a");
+ QTest::newRow("empty-right") << FilePath("a") << QString() << FilePath("a");
+ QTest::newRow("add-root") << FilePath() << QString("/") << FilePath("/");
+ QTest::newRow("add-root-and-more")
+ << FilePath() << QString("/test/blah") << FilePath("/test/blah");
+ QTest::newRow("add-extension")
+ << FilePath::fromString("/a") << QString(".txt") << FilePath("/a.txt");
+ QTest::newRow("trailing-slash")
+ << FilePath::fromString("/a") << QString("b/") << FilePath("/ab/");
+ QTest::newRow("slash-trailing-slash")
+ << FilePath::fromString("/a/") << QString("b/") << FilePath("/a/b/");
+}
+
+void tst_filepath::stringAppended()
+{
+ QFETCH(FilePath, left);
+ QFETCH(QString, right);
+ QFETCH(FilePath, expected);
+
+ const FilePath result = left.stringAppended(right);
+
+ QCOMPARE(expected, result);
+}
+
+void tst_filepath::url()
+{
+ QFETCH(QString, url);
+ QFETCH(QString, expectedScheme);
+ QFETCH(QString, expectedHost);
+ QFETCH(QString, expectedPath);
+
+ const FilePath result = FilePath::fromString(url);
+ QCOMPARE(result.scheme(), expectedScheme);
+ QCOMPARE(result.host(), expectedHost);
+ QCOMPARE(result.path(), expectedPath);
+}
+
+void tst_filepath::url_data()
+{
+ QTest::addColumn<QString>("url");
+ QTest::addColumn<QString>("expectedScheme");
+ QTest::addColumn<QString>("expectedHost");
+ QTest::addColumn<QString>("expectedPath");
+ QTest::newRow("empty") << QString() << QString() << QString() << QString();
+ QTest::newRow("simple-file") << QString("file:///a/b") << QString("file") << QString()
+ << QString("/a/b");
+ QTest::newRow("simple-file-root")
+ << QString("file:///") << QString("file") << QString() << QString("/");
+ QTest::newRow("simple-docker")
+ << QString("docker://1234/a/b") << QString("docker") << QString("1234") << QString("/a/b");
+ QTest::newRow("simple-ssh") << QString("ssh://user@host/a/b") << QString("ssh")
+ << QString("user@host") << QString("/a/b");
+ QTest::newRow("simple-ssh-with-port") << QString("ssh://user@host:1234/a/b") << QString("ssh")
+ << QString("user@host:1234") << QString("/a/b");
+ QTest::newRow("http-qt.io") << QString("http://qt.io") << QString("http") << QString("qt.io")
+ << QString();
+ QTest::newRow("http-qt.io-index.html") << QString("http://qt.io/index.html") << QString("http")
+ << QString("qt.io") << QString("/index.html");
+}
+
+void tst_filepath::cleanPath_data()
+{
+ QTest::addColumn<QString>("path");
+ QTest::addColumn<QString>("expected");
+
+ QTest::newRow("data0") << "/Users/sam/troll/qt4.0//.."
+ << "/Users/sam/troll";
+ QTest::newRow("data1") << "/Users/sam////troll/qt4.0//.."
+ << "/Users/sam/troll";
+ QTest::newRow("data2") << "/"
+ << "/";
+ QTest::newRow("data2-up") << "/path/.."
+ << "/";
+ QTest::newRow("data2-above-root") << "/.."
+ << "/..";
+ QTest::newRow("data3") << QDir::cleanPath("../.") << "..";
+ QTest::newRow("data4") << QDir::cleanPath("../..") << "../..";
+ QTest::newRow("data5") << "d:\\a\\bc\\def\\.."
+ << "d:/a/bc"; // QDir/Linux had: "d:\\a\\bc\\def\\.."
+ QTest::newRow("data6") << "d:\\a\\bc\\def\\../../.."
+ << "d:/"; // QDir/Linux had: ".."
+ QTest::newRow("data7") << ".//file1.txt"
+ << "file1.txt";
+ QTest::newRow("data8") << "/foo/bar/..//file1.txt"
+ << "/foo/file1.txt";
+ QTest::newRow("data9") << "//"
+ << "//"; // QDir had: "/"
+ QTest::newRow("data10w") << "c:\\"
+ << "c:/";
+ QTest::newRow("data10l") << "/:/"
+ << "/:";
+ QTest::newRow("data11") << "//foo//bar"
+ << "//foo/bar"; // QDir/Win had: "//foo/bar"
+ QTest::newRow("data12") << "ab/a/"
+ << "ab/a"; // Path item with length of 2
+ QTest::newRow("data13w") << "c:/"
+ << "c:/";
+ QTest::newRow("data13w2") << "c:\\"
+ << "c:/";
+ //QTest::newRow("data13l") << "c://" << "c:";
+
+ // QTest::newRow("data14") << "c://foo" << "c:/foo";
+ QTest::newRow("data15") << "//c:/foo"
+ << "//c:/foo"; // QDir/Lin had: "/c:/foo";
+ QTest::newRow("drive-up") << "A:/path/.."
+ << "A:/";
+ QTest::newRow("drive-above-root") << "A:/.."
+ << "A:/..";
+ QTest::newRow("unc-server-up") << "//server/path/.."
+ << "//server/";
+ QTest::newRow("unc-server-above-root") << "//server/.."
+ << "//server/..";
+
+ QTest::newRow("longpath") << "\\\\?\\d:\\"
+ << "d:/";
+ QTest::newRow("longpath-slash") << "//?/d:/"
+ << "d:/";
+ QTest::newRow("longpath-mixed-slashes") << "//?/d:\\"
+ << "d:/";
+ QTest::newRow("longpath-mixed-slashes-2") << "\\\\?\\d:/"
+ << "d:/";
+
+ QTest::newRow("unc-network-share") << "\\\\?\\UNC\\localhost\\c$\\tmp.txt"
+ << "//localhost/c$/tmp.txt";
+ QTest::newRow("unc-network-share-slash") << "//?/UNC/localhost/c$/tmp.txt"
+ << "//localhost/c$/tmp.txt";
+ QTest::newRow("unc-network-share-mixed-slashes") << "//?/UNC/localhost\\c$\\tmp.txt"
+ << "//localhost/c$/tmp.txt";
+ QTest::newRow("unc-network-share-mixed-slashes-2") << "\\\\?\\UNC\\localhost/c$/tmp.txt"
+ << "//localhost/c$/tmp.txt";
+
+ QTest::newRow("QTBUG-23892_0") << "foo/.."
+ << ".";
+ QTest::newRow("QTBUG-23892_1") << "foo/../"
+ << ".";
+
+ QTest::newRow("QTBUG-3472_0") << "/foo/./bar"
+ << "/foo/bar";
+ QTest::newRow("QTBUG-3472_1") << "./foo/.."
+ << ".";
+ QTest::newRow("QTBUG-3472_2") << "./foo/../"
+ << ".";
+
+ QTest::newRow("resource0") << ":/prefix/foo.bar"
+ << ":/prefix/foo.bar";
+ QTest::newRow("resource1") << ":/prefix/..//prefix/foo.bar"
+ << ":/prefix/foo.bar";
+
+ QTest::newRow("ssh") << "ssh://host/prefix/../foo.bar"
+ << "ssh://host/foo.bar";
+ QTest::newRow("ssh2") << "ssh://host/../foo.bar"
+ << "ssh://host/../foo.bar";
+}
+
+void tst_filepath::cleanPath()
+{
+ QFETCH(QString, path);
+ QFETCH(QString, expected);
+ QString cleaned = doCleanPath(path);
+ QCOMPARE(cleaned, expected);
+}
+
+void tst_filepath::isSameFile_data()
+{
+ QTest::addColumn<FilePath>("left");
+ QTest::addColumn<FilePath>("right");
+ QTest::addColumn<bool>("shouldBeEqual");
+
+ QTest::addRow("/==/") << FilePath::fromString("/") << FilePath::fromString("/") << true;
+ QTest::addRow("/!=tmp") << FilePath::fromString("/") << FilePath::fromString(tempDir.path())
+ << false;
+
+ QDir dir(tempDir.path());
+ touch(dir, "target-file", false);
+
+ QFile file(dir.absoluteFilePath("target-file"));
+ if (file.link(dir.absoluteFilePath("source-file"))) {
+ QTest::addRow("real==link")
+ << FilePath::fromString(file.fileName())
+ << FilePath::fromString(dir.absoluteFilePath("target-file")) << true;
+ }
+
+ QTest::addRow("/!=non-existing")
+ << FilePath::fromString("/") << FilePath::fromString("/this-path/does-not-exist") << false;
+
+ QTest::addRow("two-devices") << FilePath::fromString(
+ "docker://boot2qt-raspberrypi4-64:6.5.0/opt/toolchain/sysroots/aarch64-pokysdk-linux/usr/"
+ "bin/aarch64-poky-linux/aarch64-poky-linux-g++")
+ << FilePath::fromString("docker://qt-linux:6/usr/bin/g++")
+ << false;
+}
+
+void tst_filepath::isSameFile()
+{
+ QFETCH(FilePath, left);
+ QFETCH(FilePath, right);
+ QFETCH(bool, shouldBeEqual);
+
+ QCOMPARE(left.isSameFile(right), shouldBeEqual);
+}
+
+void tst_filepath::hostSpecialChars_data()
+{
+ QTest::addColumn<QString>("scheme");
+ QTest::addColumn<QString>("host");
+ QTest::addColumn<QString>("path");
+ QTest::addColumn<FilePath>("expected");
+
+ QTest::addRow("slash-in-host") << "device"
+ << "host/name"
+ << "/" << FilePath::fromString("device://host%2fname/");
+ QTest::addRow("percent-in-host") << "device"
+ << "host%name"
+ << "/" << FilePath::fromString("device://host%25name/");
+ QTest::addRow("percent-and-slash-in-host")
+ << "device"
+ << "host/%name"
+ << "/" << FilePath::fromString("device://host%2f%25name/");
+ QTest::addRow("qtc-dev-slash-in-host-linux")
+ << "device"
+ << "host/name"
+ << "/" << FilePath::fromString("/__qtc_devices__/device/host%2fname/");
+ QTest::addRow("qtc-dev-slash-in-host-windows")
+ << "device"
+ << "host/name"
+ << "/" << FilePath::fromString("c:/__qtc_devices__/device/host%2fname/");
+}
+
+void tst_filepath::hostSpecialChars()
+{
+ QFETCH(QString, scheme);
+ QFETCH(QString, host);
+ QFETCH(QString, path);
+ QFETCH(FilePath, expected);
+
+ FilePath fp;
+ fp.setParts(scheme, host, path);
+
+ // Check that setParts and fromString give the same result
+ QCOMPARE(fp, expected);
+ QCOMPARE(fp.host(), expected.host());
+ QCOMPARE(fp.host(), host);
+ QCOMPARE(expected.host(), host);
+
+ QString toStringExpected = expected.toString();
+ QString toStringActual = fp.toString();
+
+ // Check that toString gives the same result
+ QCOMPARE(toStringActual, toStringExpected);
+
+ // Check that fromString => toString => fromString gives the same result
+ FilePath toFromExpected = FilePath::fromString(expected.toString());
+ QCOMPARE(toFromExpected, expected);
+ QCOMPARE(toFromExpected, fp);
+
+ // Check that setParts => toString => fromString gives the same result
+ FilePath toFromActual = FilePath::fromString(fp.toString());
+ QCOMPARE(toFromActual, fp);
+ QCOMPARE(toFromExpected, expected);
+}
+
+void tst_filepath::tmp_data()
+{
+ QTest::addColumn<QString>("templatepath");
+ QTest::addColumn<bool>("expected");
+
+ QTest::addRow("empty") << "" << true;
+ QTest::addRow("no-template") << "foo" << true;
+ QTest::addRow("realtive-template") << "my-file-XXXXXXXX" << true;
+ QTest::addRow("absolute-template") << QDir::tempPath() + "/my-file-XXXXXXXX" << true;
+ QTest::addRow("non-existing-dir") << "/this/path/does/not/exist/my-file-XXXXXXXX" << false;
+
+ QTest::addRow("on-device") << "device://test/./my-file-XXXXXXXX" << true;
+}
+
+void tst_filepath::tmp()
+{
+ QFETCH(QString, templatepath);
+ QFETCH(bool, expected);
+
+ FilePath fp = FilePath::fromString(templatepath);
+
+ const auto result = fp.createTempFile();
+ QCOMPARE(result.has_value(), expected);
+
+ if (result.has_value()) {
+ QVERIFY(result->exists());
+ QVERIFY(result->removeFile());
+ }
+}
+
+QTEST_GUILESS_MAIN(tst_filepath)
+
+#include "tst_filepath.moc"
diff --git a/tests/auto/utils/fileutils/tst_fileutils.cpp b/tests/auto/utils/fileutils/tst_fileutils.cpp
index 033cf680bea..f4596cb1a5c 100644
--- a/tests/auto/utils/fileutils/tst_fileutils.cpp
+++ b/tests/auto/utils/fileutils/tst_fileutils.cpp
@@ -1,13 +1,10 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-#include <QDebug>
-#include <QRandomGenerator>
#include <QtTest>
#include <utils/fileutils.h>
#include <utils/hostosinfo.h>
-#include <utils/link.h>
//TESTED_COMPONENT=src/libs/utils
using namespace Utils;
@@ -24,948 +21,20 @@ class tst_fileutils : public QObject
{
Q_OBJECT
-signals:
- void asyncTestDone(); // test internal helper signal
-
private slots:
void initTestCase();
- void isEmpty_data();
- void isEmpty();
-
- void parentDir_data();
- void parentDir();
-
- void isChildOf_data();
- void isChildOf();
-
- void fileName_data();
- void fileName();
-
- void calcRelativePath_data();
- void calcRelativePath();
-
- void relativePath_specials();
- void relativePath_data();
- void relativePath();
-
- void absolute_data();
- void absolute();
-
- void fromToString_data();
- void fromToString();
-
- void fromString_data();
- void fromString();
-
- void fromUserInput_data();
- void fromUserInput();
-
- void toString_data();
- void toString();
-
- void toFSPathString_data();
- void toFSPathString();
-
- void comparison_data();
- void comparison();
-
- void linkFromString_data();
- void linkFromString();
-
- void pathAppended_data();
- void pathAppended();
-
void commonPath_data();
void commonPath();
- void resolvePath_data();
- void resolvePath();
-
- void relativeChildPath_data();
- void relativeChildPath();
-
void bytesAvailableFromDF_data();
void bytesAvailableFromDF();
- void rootLength_data();
- void rootLength();
-
- void schemeAndHostLength_data();
- void schemeAndHostLength();
-
- void asyncLocalCopy();
- void startsWithDriveLetter();
- void startsWithDriveLetter_data();
-
- void onDevice_data();
- void onDevice();
-
- void stringAppended();
- void stringAppended_data();
- void url();
- void url_data();
-
- void cleanPath_data();
- void cleanPath();
-
- void isSameFile_data();
- void isSameFile();
-
- void hostSpecialChars_data();
- void hostSpecialChars();
-
- void tmp_data();
- void tmp();
-
void filePathInfoFromTriple_data();
void filePathInfoFromTriple();
-
-private:
- QTemporaryDir tempDir;
- QString rootPath;
};
-static void touch(const QDir &dir, const QString &filename, bool fill)
-{
- QFile file(dir.absoluteFilePath(filename));
- file.open(QIODevice::WriteOnly);
- if (fill) {
- QRandomGenerator *random = QRandomGenerator::global();
- for (int i = 0; i < 10; ++i)
- file.write(QString::number(random->generate(), 16).toUtf8());
- }
- file.close();
-}
-
-void tst_fileutils::initTestCase()
-{
- // initialize test for tst_fileutiles::relativePath*()
- QVERIFY(tempDir.isValid());
- rootPath = tempDir.path();
- QDir dir(rootPath);
- dir.mkpath("a/b/c/d");
- dir.mkpath("a/x/y/z");
- dir.mkpath("a/b/x/y/z");
- dir.mkpath("x/y/z");
- touch(dir, "a/b/c/d/file1.txt", false);
- touch(dir, "a/x/y/z/file2.txt", false);
- touch(dir, "a/file3.txt", false);
- touch(dir, "x/y/file4.txt", false);
-
- // initialize test for tst_fileutils::asyncLocalCopy()
- touch(dir, "x/y/fileToCopy.txt", true);
-}
-
-void tst_fileutils::isEmpty_data()
-{
- QTest::addColumn<QString>("path");
- QTest::addColumn<bool>("result");
-
- QTest::newRow("empty path") << "" << true;
- QTest::newRow("root only") << "/" << false;
- QTest::newRow("//") << "//" << false;
- QTest::newRow("scheme://host") << "scheme://host" << true; // Intentional (for now?)
- QTest::newRow("scheme://host/") << "scheme://host/" << false;
- QTest::newRow("scheme://host/a") << "scheme://host/a" << false;
- QTest::newRow("scheme://host/.") << "scheme://host/." << false;
-}
-
-void tst_fileutils::isEmpty()
-{
- QFETCH(QString, path);
- QFETCH(bool, result);
-
- FilePath filePath = FilePath::fromString(path);
- QCOMPARE(filePath.isEmpty(), result);
-}
-
-void tst_fileutils::parentDir_data()
-{
- QTest::addColumn<QString>("path");
- QTest::addColumn<QString>("parentPath");
- QTest::addColumn<QString>("expectFailMessage");
-
- QTest::newRow("empty path") << "" << "" << "";
- QTest::newRow("root only") << "/" << "" << "";
- QTest::newRow("//") << "//" << "" << "";
- QTest::newRow("/tmp/dir") << "/tmp/dir" << "/tmp" << "";
- QTest::newRow("relative/path") << "relative/path" << "relative" << "";
- QTest::newRow("relativepath") << "relativepath" << "." << "";
-
- // Windows stuff:
- QTest::newRow("C:/data") << "C:/data" << "C:/" << "";
- QTest::newRow("C:/") << "C:/" << "" << "";
- QTest::newRow("//./com1") << "//./com1" << "//./" << "";
- QTest::newRow("//?/path") << "//?/path" << "/" << "Qt 4 cannot handle this path.";
- QTest::newRow("/Global?\?/UNC/host") << "/Global?\?/UNC/host" << "/Global?\?/UNC/host"
- << "Qt 4 cannot handle this path.";
- QTest::newRow("//server/directory/file")
- << "//server/directory/file" << "//server/directory" << "";
- QTest::newRow("//server/directory") << "//server/directory" << "//server/" << "";
- QTest::newRow("//server") << "//server" << "" << "";
-
- QTest::newRow("qrc") << ":/foo/bar.txt" << ":/foo" << "";
-}
-
-void tst_fileutils::parentDir()
-{
- QFETCH(QString, path);
- QFETCH(QString, parentPath);
- QFETCH(QString, expectFailMessage);
-
- FilePath result = FilePath::fromUserInput(path).parentDir();
- if (!expectFailMessage.isEmpty())
- QEXPECT_FAIL("", expectFailMessage.toUtf8().constData(), Continue);
- QCOMPARE(result.toString(), parentPath);
-}
-
-void tst_fileutils::isChildOf_data()
-{
- QTest::addColumn<QString>("path");
- QTest::addColumn<QString>("childPath");
- QTest::addColumn<bool>("result");
-
- QTest::newRow("empty path") << "" << "/tmp" << false;
- QTest::newRow("root only") << "/" << "/tmp" << true;
- QTest::newRow("/tmp/dir") << "/tmp" << "/tmp/dir" << true;
- QTest::newRow("relative/path") << "relative" << "relative/path" << true;
- QTest::newRow("/tmpdir") << "/tmp" << "/tmpdir" << false;
- QTest::newRow("same") << "/tmp/dir" << "/tmp/dir" << false;
-
- // Windows stuff:
- QTest::newRow("C:/data") << "C:/" << "C:/data" << true;
- QTest::newRow("C:/") << "" << "C:/" << false;
- QTest::newRow("com-port") << "//./" << "//./com1" << true;
- QTest::newRow("extended-length-path") << "\\\\?\\C:\\" << "\\\\?\\C:\\path" << true;
- QTest::newRow("/Global?\?/UNC/host") << "/Global?\?/UNC/host"
- << "/Global?\?/UNC/host/file" << true;
- QTest::newRow("//server/directory/file")
- << "//server/directory" << "//server/directory/file" << true;
- QTest::newRow("//server/directory")
- << "//server" << "//server/directory" << true;
-
- QTest::newRow("qrc") << ":/foo/bar" << ":/foo/bar/blah" << true;
-}
-
-void tst_fileutils::isChildOf()
-{
- QFETCH(QString, path);
- QFETCH(QString, childPath);
- QFETCH(bool, result);
-
- const FilePath child = FilePath::fromUserInput(childPath);
- const FilePath parent = FilePath::fromUserInput(path);
-
- QCOMPARE(child.isChildOf(parent), result);
-}
-
-void tst_fileutils::fileName_data()
-{
- QTest::addColumn<QString>("path");
- QTest::addColumn<int>("components");
- QTest::addColumn<QString>("result");
-
- QTest::newRow("empty 1") << "" << 0 << "";
- QTest::newRow("empty 2") << "" << 1 << "";
- QTest::newRow("basic") << "/foo/bar/baz" << 0 << "baz";
- QTest::newRow("2 parts") << "/foo/bar/baz" << 1 << "bar/baz";
- QTest::newRow("root no depth") << "/foo" << 0 << "foo";
- QTest::newRow("root full") << "/foo" << 1 << "/foo";
- QTest::newRow("root included") << "/foo/bar/baz" << 2 << "/foo/bar/baz";
- QTest::newRow("too many parts") << "/foo/bar/baz" << 5 << "/foo/bar/baz";
- QTest::newRow("windows root") << "C:/foo/bar/baz" << 2 << "C:/foo/bar/baz";
- QTest::newRow("smb share") << "//server/share/file" << 2 << "//server/share/file";
- QTest::newRow("no slashes") << "foobar" << 0 << "foobar";
- QTest::newRow("no slashes with depth") << "foobar" << 1 << "foobar";
- QTest::newRow("multiple slashes 1") << "/foo/bar////baz" << 0 << "baz";
- QTest::newRow("multiple slashes 2") << "/foo/bar////baz" << 1 << "bar////baz";
- QTest::newRow("multiple slashes 3") << "/foo////bar/baz" << 2 << "/foo////bar/baz";
- QTest::newRow("single char 1") << "/a/b/c" << 0 << "c";
- QTest::newRow("single char 2") << "/a/b/c" << 1 << "b/c";
- QTest::newRow("single char 3") << "/a/b/c" << 2 << "/a/b/c";
- QTest::newRow("slash at end 1") << "/a/b/" << 0 << "";
- QTest::newRow("slash at end 2") << "/a/b/" << 1 << "b/";
- QTest::newRow("slashes at end 1") << "/a/b//" << 0 << "";
- QTest::newRow("slashes at end 2") << "/a/b//" << 1 << "b//";
- QTest::newRow("root only 1") << "/" << 0 << "";
- QTest::newRow("root only 2") << "/" << 1 << "/";
- QTest::newRow("qrc 0") << ":/foo/bar" << 0 << "bar";
- QTest::newRow("qrc with root") << ":/foo/bar" << 1 << ":/foo/bar";
-}
-
-void tst_fileutils::fileName()
-{
- QFETCH(QString, path);
- QFETCH(int, components);
- QFETCH(QString, result);
- QCOMPARE(FilePath::fromString(path).fileNameWithPathComponents(components), result);
-}
-
-void tst_fileutils::calcRelativePath_data()
-{
- QTest::addColumn<QString>("absolutePath");
- QTest::addColumn<QString>("anchorPath");
- QTest::addColumn<QString>("result");
-
- QTest::newRow("empty") << "" << "" << "";
- QTest::newRow("leftempty") << "" << "/" << "";
- QTest::newRow("rightempty") << "/" << "" << "";
- QTest::newRow("root") << "/" << "/" << ".";
- QTest::newRow("simple1") << "/a" << "/" << "a";
- QTest::newRow("simple2") << "/" << "/a" << "..";
- QTest::newRow("simple3") << "/a" << "/a" << ".";
- QTest::newRow("extraslash1") << "/a/b/c" << "/a/b/c" << ".";
- QTest::newRow("extraslash2") << "/a/b/c" << "/a/b/c/" << ".";
- QTest::newRow("extraslash3") << "/a/b/c/" << "/a/b/c" << ".";
- QTest::newRow("normal1") << "/a/b/c" << "/a/x" << "../b/c";
- QTest::newRow("normal2") << "/a/b/c" << "/a/x/y" << "../../b/c";
- QTest::newRow("normal3") << "/a/b/c" << "/x/y" << "../../a/b/c";
-}
-
-void tst_fileutils::calcRelativePath()
-{
- QFETCH(QString, absolutePath);
- QFETCH(QString, anchorPath);
- QFETCH(QString, result);
- QString relativePath = Utils::FilePath::calcRelativePath(absolutePath, anchorPath);
- QCOMPARE(relativePath, result);
-}
-
-void tst_fileutils::relativePath_specials()
-{
- QString path = FilePath("").relativePathFrom("").toString();
- QCOMPARE(path, "");
-}
-
-void tst_fileutils::relativePath_data()
-{
- QTest::addColumn<QString>("relative");
- QTest::addColumn<QString>("anchor");
- QTest::addColumn<QString>("result");
-
- QTest::newRow("samedir") << "/" << "/" << ".";
- QTest::newRow("samedir_but_file") << "a/b/c/d/file1.txt" << "a/b/c/d" << "file1.txt";
- QTest::newRow("samedir_but_file2") << "a/b/c/d" << "a/b/c/d/file1.txt" << ".";
- QTest::newRow("dir2dir_1") << "a/b/c/d" << "a/x/y/z" << "../../../b/c/d";
- QTest::newRow("dir2dir_2") << "a/b" <<"a/b/c" << "..";
- QTest::newRow("file2file_1") << "a/b/c/d/file1.txt" << "a/file3.txt" << "b/c/d/file1.txt";
- QTest::newRow("dir2file_1") << "a/b/c" << "a/x/y/z/file2.txt" << "../../../b/c";
- QTest::newRow("file2dir_1") << "a/b/c/d/file1.txt" << "x/y" << "../../a/b/c/d/file1.txt";
-}
-
-void tst_fileutils::relativePath()
-{
- QFETCH(QString, relative);
- QFETCH(QString, anchor);
- QFETCH(QString, result);
- FilePath actualPath = FilePath::fromString(rootPath + "/" + relative)
- .relativePathFrom(FilePath::fromString(rootPath + "/" + anchor));
- QCOMPARE(actualPath.toString(), result);
-}
-
-void tst_fileutils::rootLength_data()
-{
- QTest::addColumn<QString>("path");
- QTest::addColumn<int>("result");
-
- QTest::newRow("empty") << "" << 0;
- QTest::newRow("slash") << "/" << 1;
- QTest::newRow("slash-rest") << "/abc" << 1;
- QTest::newRow("rest") << "abc" << 0;
- QTest::newRow("drive-slash") << "x:/" << 3;
- QTest::newRow("drive-rest") << "x:abc" << 0;
- QTest::newRow("drive-slash-rest") << "x:/abc" << 3;
-
- QTest::newRow("unc-root") << "//" << 2;
- QTest::newRow("unc-localhost-unfinished") << "//localhost" << 11;
- QTest::newRow("unc-localhost") << "//localhost/" << 12;
- QTest::newRow("unc-localhost-rest") << "//localhost/abs" << 12;
- QTest::newRow("unc-localhost-drive") << "//localhost/c$" << 12;
- QTest::newRow("unc-localhost-drive-slash") << "//localhost//c$/" << 12;
- QTest::newRow("unc-localhost-drive-slash-rest") << "//localhost//c$/x" << 12;
-}
-
-void tst_fileutils::rootLength()
-{
- QFETCH(QString, path);
- QFETCH(int, result);
-
- int actual = FilePath::rootLength(path);
- QCOMPARE(actual, result);
-}
-
-void tst_fileutils::schemeAndHostLength_data()
-{
- QTest::addColumn<QString>("path");
- QTest::addColumn<int>("result");
-
- QTest::newRow("empty") << "" << 0;
- QTest::newRow("drive-slash-rest") << "x:/abc" << 0;
- QTest::newRow("rest") << "abc" << 0;
- QTest::newRow("slash-rest") << "/abc" << 0;
- QTest::newRow("dev-empty") << "dev://" << 6;
- QTest::newRow("dev-localhost-unfinished") << "dev://localhost" << 15;
- QTest::newRow("dev-localhost") << "dev://localhost/" << 16;
- QTest::newRow("dev-localhost-rest") << "dev://localhost/abs" << 16;
- QTest::newRow("dev-localhost-drive") << "dev://localhost/c$" << 16;
- QTest::newRow("dev-localhost-drive-slash") << "dev://localhost//c$/" << 16;
- QTest::newRow("dev-localhost-drive-slash-rest") << "dev://localhost//c$/x" << 16;
-}
-
-void tst_fileutils::schemeAndHostLength()
-{
- QFETCH(QString, path);
- QFETCH(int, result);
-
- int actual = FilePath::schemeAndHostLength(path);
- QCOMPARE(actual, result);
-}
-
-void tst_fileutils::absolute_data()
-{
- QTest::addColumn<FilePath>("path");
- QTest::addColumn<FilePath>("absoluteFilePath");
- QTest::addColumn<FilePath>("absolutePath");
-
- QTest::newRow("absolute1") << FilePath::fromString("/")
- << FilePath::fromString("/")
- << FilePath::fromString("/");
- QTest::newRow("absolute2") << FilePath::fromString("C:/a/b")
- << FilePath::fromString("C:/a/b")
- << FilePath::fromString("C:/a");
- QTest::newRow("absolute3") << FilePath::fromString("/a/b")
- << FilePath::fromString("/a/b")
- << FilePath::fromString("/a");
- QTest::newRow("absolute4") << FilePath::fromString("/a/b/..")
- << FilePath::fromString("/a")
- << FilePath::fromString("/");
- QTest::newRow("absolute5") << FilePath::fromString("/a/b/c/../d")
- << FilePath::fromString("/a/b/d")
- << FilePath::fromString("/a/b");
- QTest::newRow("absolute6") << FilePath::fromString("/a/../b/c/d")
- << FilePath::fromString("/b/c/d")
- << FilePath::fromString("/b/c");
- QTest::newRow("default-constructed") << FilePath() << FilePath() << FilePath();
- QTest::newRow("relative") << FilePath::fromString("a/b")
- << FilePath::fromString(QDir::currentPath() + "/a/b")
- << FilePath::fromString(QDir::currentPath() + "/a");
- QTest::newRow("qrc") << FilePath::fromString(":/foo/bar.txt")
- << FilePath::fromString(":/foo/bar.txt")
- << FilePath::fromString(":/foo");
-}
-
-void tst_fileutils::absolute()
-{
- QFETCH(FilePath, path);
- QFETCH(FilePath, absoluteFilePath);
- QFETCH(FilePath, absolutePath);
- QCOMPARE(path.absoluteFilePath(), absoluteFilePath);
- QCOMPARE(path.absolutePath(), absolutePath);
-}
-
-void tst_fileutils::toString_data()
-{
- QTest::addColumn<QString>("scheme");
- QTest::addColumn<QString>("host");
- QTest::addColumn<QString>("path");
- QTest::addColumn<QString>("result");
- QTest::addColumn<QString>("userResult");
-
- QTest::newRow("empty") << "" << "" << "" << "" << "";
- QTest::newRow("scheme") << "http" << "" << "" << "http:///./" << "http:///./";
- QTest::newRow("scheme-and-host") << "http" << "127.0.0.1" << "" << "http://127.0.0.1/./" << "http://127.0.0.1/./";
- QTest::newRow("root") << "http" << "127.0.0.1" << "/" << "http://127.0.0.1/" << "http://127.0.0.1/";
-
- QTest::newRow("root-folder") << "" << "" << "/" << "/" << "/";
- QTest::newRow("qtc-dev-root-folder-linux") << "" << "" << "/__qtc_devices__" << "/__qtc_devices__" << "/__qtc_devices__";
- QTest::newRow("qtc-dev-root-folder-win") << "" << "" << "c:/__qtc_devices__" << "c:/__qtc_devices__" << "c:/__qtc_devices__";
- QTest::newRow("qtc-dev-type-root-folder-linux") << "" << "" << "/__qtc_devices__/docker" << "/__qtc_devices__/docker" << "/__qtc_devices__/docker";
- QTest::newRow("qtc-dev-type-root-folder-win") << "" << "" << "c:/__qtc_devices__/docker" << "c:/__qtc_devices__/docker" << "c:/__qtc_devices__/docker";
- QTest::newRow("qtc-root-folder") << "docker" << "alpine:latest" << "/" << "docker://alpine:latest/" << "docker://alpine:latest/";
- QTest::newRow("qtc-root-folder-rel") << "docker" << "alpine:latest" << "" << "docker://alpine:latest/./" << "docker://alpine:latest/./";
-}
-
-void tst_fileutils::toString()
-{
- QFETCH(QString, scheme);
- QFETCH(QString, host);
- QFETCH(QString, path);
- QFETCH(QString, result);
- QFETCH(QString, userResult);
-
- FilePath filePath = FilePath::fromParts(scheme, host, path);
- QCOMPARE(filePath.toString(), result);
- QString cleanedOutput = filePath.needsDevice() ? filePath.toUserOutput() : QDir::cleanPath(filePath.toUserOutput());
- QCOMPARE(cleanedOutput, userResult);
-}
-
-void tst_fileutils::toFSPathString_data()
-{
- QTest::addColumn<QString>("scheme");
- QTest::addColumn<QString>("host");
- QTest::addColumn<QString>("path");
- QTest::addColumn<QString>("result");
- QTest::addColumn<QString>("userResult");
-
- QTest::newRow("empty") << "" << "" << "" << "" << "";
- QTest::newRow("scheme") << "http" << "" << "" << QDir::rootPath() + "__qtc_devices__/http//./" << "http:///./";
- QTest::newRow("scheme-and-host") << "http" << "127.0.0.1" << "" << QDir::rootPath() + "__qtc_devices__/http/127.0.0.1/./" << "http://127.0.0.1/./";
- QTest::newRow("root") << "http" << "127.0.0.1" << "/" << QDir::rootPath() + "__qtc_devices__/http/127.0.0.1/" << "http://127.0.0.1/";
-
- QTest::newRow("root-folder") << "" << "" << "/" << "/" << "/";
- QTest::newRow("qtc-dev-root-folder") << "" << "" << QDir::rootPath() + "__qtc_devices__" << QDir::rootPath() + "__qtc_devices__" << QDir::rootPath() + "__qtc_devices__";
- QTest::newRow("qtc-dev-type-root-folder") << "" << "" << QDir::rootPath() + "__qtc_devices__/docker" << QDir::rootPath() + "__qtc_devices__/docker" << QDir::rootPath() + "__qtc_devices__/docker";
- QTest::newRow("qtc-root-folder") << "docker" << "alpine:latest" << "/" << QDir::rootPath() + "__qtc_devices__/docker/alpine:latest/" << "docker://alpine:latest/";
- QTest::newRow("qtc-root-folder-rel") << "docker" << "alpine:latest" << "" << QDir::rootPath() + "__qtc_devices__/docker/alpine:latest/./" << "docker://alpine:latest/./";
-}
-
-void tst_fileutils::toFSPathString()
-{
- QFETCH(QString, scheme);
- QFETCH(QString, host);
- QFETCH(QString, path);
- QFETCH(QString, result);
- QFETCH(QString, userResult);
-
- FilePath filePath = FilePath::fromParts(scheme, host, path);
- QCOMPARE(filePath.toFSPathString(), result);
- QString cleanedOutput = filePath.needsDevice() ? filePath.toUserOutput() : QDir::cleanPath(filePath.toUserOutput());
- QCOMPARE(cleanedOutput, userResult);
-}
-
-enum ExpectedPass
-{
- PassEverywhere = 0,
- FailOnWindows = 1,
- FailOnLinux = 2,
- FailEverywhere = 3
-};
-
-class FromStringData
-{
-public:
- FromStringData(const QString &input, const QString &scheme, const QString &host,
- const QString &path, ExpectedPass expectedPass = PassEverywhere)
- : input(input), scheme(scheme), host(host),
- path(path), expectedPass(expectedPass)
- {}
-
- QString input;
- QString scheme;
- QString host;
- QString path;
- ExpectedPass expectedPass = PassEverywhere;
-};
-
-Q_DECLARE_METATYPE(FromStringData);
-
-void tst_fileutils::fromString_data()
-{
- using D = FromStringData;
- QTest::addColumn<D>("data");
-
- QTest::newRow("empty") << D("", "", "", "");
- QTest::newRow("single-colon") << D(":", "", "", ":");
- QTest::newRow("single-slash") << D("/", "", "", "/");
- QTest::newRow("single-char") << D("a", "", "", "a");
- QTest::newRow("relative") << D("./rel", "", "", "./rel");
- QTest::newRow("qrc") << D(":/test.txt", "", "", ":/test.txt");
- QTest::newRow("qrc-no-slash") << D(":test.txt", "", "", ":test.txt");
-
- QTest::newRow("unc-incomplete") << D("//", "", "", "//");
- QTest::newRow("unc-incomplete-only-server") << D("//server", "", "", "//server");
- QTest::newRow("unc-incomplete-only-server-2") << D("//server/", "", "", "//server/");
- QTest::newRow("unc-server-and-share") << D("//server/share", "", "", "//server/share");
- QTest::newRow("unc-server-and-share-2") << D("//server/share/", "", "", "//server/share/");
- QTest::newRow("unc-full") << D("//server/share/test.txt", "", "", "//server/share/test.txt");
-
- QTest::newRow("unix-root") << D("/", "", "", "/");
- QTest::newRow("unix-folder") << D("/tmp", "", "", "/tmp");
- QTest::newRow("unix-folder-with-trailing-slash") << D("/tmp/", "", "", "/tmp/");
-
- QTest::newRow("windows-root") << D("c:", "", "", "c:");
- QTest::newRow("windows-folder") << D("c:/Windows", "", "", "c:/Windows");
- QTest::newRow("windows-folder-with-trailing-slash") << D("c:/Windows/", "", "", "c:/Windows/");
- QTest::newRow("windows-folder-slash") << D("C:/Windows", "", "", "C:/Windows");
-
- QTest::newRow("docker-root-url") << D("docker://1234/", "docker", "1234", "/");
- QTest::newRow("docker-root-url-special-linux") << D("/__qtc_devices__/docker/1234/", "docker", "1234", "/");
- QTest::newRow("docker-root-url-special-win") << D("c:/__qtc_devices__/docker/1234/", "docker", "1234", "/");
- QTest::newRow("docker-relative-path") << D("docker://1234/./rel", "docker", "1234", "rel");
-
- QTest::newRow("qtc-dev-linux") << D("/__qtc_devices__", "", "", "/__qtc_devices__");
- QTest::newRow("qtc-dev-win") << D("c:/__qtc_devices__", "", "", "c:/__qtc_devices__");
- QTest::newRow("qtc-dev-type-linux") << D("/__qtc_devices__/docker", "", "", "/__qtc_devices__/docker");
- QTest::newRow("qtc-dev-type-win") << D("c:/__qtc_devices__/docker", "", "", "c:/__qtc_devices__/docker");
- QTest::newRow("qtc-dev-type-dev-linux") << D("/__qtc_devices__/docker/1234", "docker", "1234", "/");
- QTest::newRow("qtc-dev-type-dev-win") << D("c:/__qtc_devices__/docker/1234", "docker", "1234", "/");
-
- // "Remote Windows" is currently truly not supported.
- QTest::newRow("cross-os-linux")
- << D("/__qtc_devices__/docker/1234/c:/test.txt", "docker", "1234", "c:/test.txt", FailEverywhere);
- QTest::newRow("cross-os-win")
- << D("c:/__qtc_devices__/docker/1234/c:/test.txt", "docker", "1234", "c:/test.txt", FailEverywhere);
- QTest::newRow("cross-os-unclean-linux")
- << D("/__qtc_devices__/docker/1234/c:\\test.txt", "docker", "1234", "c:/test.txt", FailEverywhere);
- QTest::newRow("cross-os-unclean-win")
- << D("c:/__qtc_devices__/docker/1234/c:\\test.txt", "docker", "1234", "c:/test.txt", FailEverywhere);
-
- QTest::newRow("unc-full-in-docker-linux")
- << D("/__qtc_devices__/docker/1234//server/share/test.txt", "docker", "1234", "//server/share/test.txt");
- QTest::newRow("unc-full-in-docker-win")
- << D("c:/__qtc_devices__/docker/1234//server/share/test.txt", "docker", "1234", "//server/share/test.txt");
-
- QTest::newRow("unc-dos-1") << D("//?/c:", "", "", "//?/c:");
- QTest::newRow("unc-dos-com") << D("//./com1", "", "", "//./com1");
-}
-
-void tst_fileutils::fromString()
-{
- QFETCH(FromStringData, data);
-
- FilePath filePath = FilePath::fromString(data.input);
-
- bool expectFail = ((data.expectedPass & FailOnLinux) && !HostOsInfo::isWindowsHost())
- || ((data.expectedPass & FailOnWindows) && HostOsInfo::isWindowsHost());
-
- if (expectFail) {
- QString actual = filePath.scheme() + '|' + filePath.host() + '|' + filePath.path();
- QString expected = data.scheme + '|' + data.host + '|' + data.path;
- QEXPECT_FAIL("", "", Continue);
- QCOMPARE(actual, expected);
- return;
- }
-
- QCOMPARE(filePath.scheme(), data.scheme);
- QCOMPARE(filePath.host(), data.host);
- QCOMPARE(filePath.path(), data.path);
-}
-
-void tst_fileutils::fromUserInput_data()
-{
- using D = FromStringData;
- QTest::addColumn<D>("data");
-
- QTest::newRow("empty") << D("", "", "", "");
- QTest::newRow("single-colon") << D(":", "", "", ":");
- QTest::newRow("single-slash") << D("/", "", "", "/");
- QTest::newRow("single-char") << D("a", "", "", "a");
- QTest::newRow("relative") << D("./rel", "", "", "rel");
- QTest::newRow("qrc") << D(":/test.txt", "", "", ":/test.txt");
- QTest::newRow("qrc-no-slash") << D(":test.txt", "", "", ":test.txt");
-
- QTest::newRow("unc-incomplete") << D("//", "", "", "//");
- QTest::newRow("unc-incomplete-only-server") << D("//server", "", "", "//server");
- QTest::newRow("unc-incomplete-only-server-2") << D("//server/", "", "", "//server/");
- QTest::newRow("unc-server-and-share") << D("//server/share", "", "", "//server/share");
- QTest::newRow("unc-server-and-share-2") << D("//server/share/", "", "", "//server/share");
- QTest::newRow("unc-full") << D("//server/share/test.txt", "", "", "//server/share/test.txt");
-
- QTest::newRow("unix-root") << D("/", "", "", "/");
- QTest::newRow("unix-folder") << D("/tmp", "", "", "/tmp");
- QTest::newRow("unix-folder-with-trailing-slash") << D("/tmp/", "", "", "/tmp");
-
- QTest::newRow("windows-root") << D("c:", "", "", "c:");
- QTest::newRow("windows-folder") << D("c:/Windows", "", "", "c:/Windows");
- QTest::newRow("windows-folder-with-trailing-slash") << D("c:\\Windows\\", "", "", "c:/Windows");
- QTest::newRow("windows-folder-slash") << D("C:/Windows", "", "", "C:/Windows");
-
- QTest::newRow("docker-root-url") << D("docker://1234/", "docker", "1234", "/");
- QTest::newRow("docker-root-url-special-linux") << D("/__qtc_devices__/docker/1234/", "docker", "1234", "/");
- QTest::newRow("docker-root-url-special-win") << D("c:/__qtc_devices__/docker/1234/", "docker", "1234", "/");
- QTest::newRow("docker-relative-path") << D("docker://1234/./rel", "docker", "1234", "rel", FailEverywhere);
-
- QTest::newRow("qtc-dev-linux") << D("/__qtc_devices__", "", "", "/__qtc_devices__");
- QTest::newRow("qtc-dev-win") << D("c:/__qtc_devices__", "", "", "c:/__qtc_devices__");
- QTest::newRow("qtc-dev-type-linux") << D("/__qtc_devices__/docker", "", "", "/__qtc_devices__/docker");
- QTest::newRow("qtc-dev-type-win") << D("c:/__qtc_devices__/docker", "", "", "c:/__qtc_devices__/docker");
- QTest::newRow("qtc-dev-type-dev-linux") << D("/__qtc_devices__/docker/1234", "docker", "1234", "/");
- QTest::newRow("qtc-dev-type-dev-win") << D("c:/__qtc_devices__/docker/1234", "docker", "1234", "/");
-
- // "Remote Windows" is currently truly not supported.
- QTest::newRow("cross-os-linux")
- << D("/__qtc_devices__/docker/1234/c:/test.txt", "docker", "1234", "c:/test.txt", FailEverywhere);
- QTest::newRow("cross-os-win")
- << D("c:/__qtc_devices__/docker/1234/c:/test.txt", "docker", "1234", "c:/test.txt", FailEverywhere);
- QTest::newRow("cross-os-unclean-linux")
- << D("/__qtc_devices__/docker/1234/c:\\test.txt", "docker", "1234", "c:/test.txt", FailEverywhere);
- QTest::newRow("cross-os-unclean-win")
- << D("c:/__qtc_devices__/docker/1234/c:\\test.txt", "docker", "1234", "c:/test.txt", FailEverywhere);
-
- QTest::newRow("unc-full-in-docker-linux")
- << D("/__qtc_devices__/docker/1234//server/share/test.txt", "docker", "1234", "//server/share/test.txt", FailEverywhere);
- QTest::newRow("unc-full-in-docker-win")
- << D("c:/__qtc_devices__/docker/1234//server/share/test.txt", "docker", "1234", "//server/share/test.txt", FailEverywhere);
-
- QTest::newRow("unc-dos-1") << D("//?/c:", "", "", "c:");
- QTest::newRow("unc-dos-com") << D("//./com1", "", "", "//./com1");
-}
-
-void tst_fileutils::fromUserInput()
-{
- QFETCH(FromStringData, data);
-
- FilePath filePath = FilePath::fromUserInput(data.input);
-
- bool expectFail = ((data.expectedPass & FailOnLinux) && !HostOsInfo::isWindowsHost())
- || ((data.expectedPass & FailOnWindows) && HostOsInfo::isWindowsHost());
-
- if (expectFail) {
- QString actual = filePath.scheme() + '|' + filePath.host() + '|' + filePath.path();
- QString expected = data.scheme + '|' + data.host + '|' + data.path;
- QEXPECT_FAIL("", "", Continue);
- QCOMPARE(actual, expected);
- return;
- }
-
- QCOMPARE(filePath.scheme(), data.scheme);
- QCOMPARE(filePath.host(), data.host);
- QCOMPARE(filePath.path(), data.path);
-}
-
-void tst_fileutils::fromToString_data()
-{
- QTest::addColumn<QString>("scheme");
- QTest::addColumn<QString>("host");
- QTest::addColumn<QString>("path");
- QTest::addColumn<QString>("full");
-
- QTest::newRow("s0") << "" << "" << "" << "";
- QTest::newRow("s1") << "" << "" << "/" << "/";
- QTest::newRow("s2") << "" << "" << "a/b/c/d" << "a/b/c/d";
- QTest::newRow("s3") << "" << "" << "/a/b" << "/a/b";
-
- QTest::newRow("s4") << "docker" << "1234abcdef" << "/bin/ls" << "docker://1234abcdef/bin/ls";
- QTest::newRow("s5") << "docker" << "1234" << "/bin/ls" << "docker://1234/bin/ls";
-
- // This is not a proper URL.
- QTest::newRow("s6") << "docker" << "1234" << "somefile" << "docker://1234/./somefile";
-
- // Local Windows paths:
- QTest::newRow("w1") << "" << "" << "C:/data" << "C:/data";
- QTest::newRow("w2") << "" << "" << "C:/" << "C:/";
- QTest::newRow("w3") << "" << "" << "/Global?\?/UNC/host" << "/Global?\?/UNC/host";
- QTest::newRow("w4") << "" << "" << "//server/dir/file" << "//server/dir/file";
-}
-
-void tst_fileutils::fromToString()
-{
- QFETCH(QString, full);
- QFETCH(QString, scheme);
- QFETCH(QString, host);
- QFETCH(QString, path);
-
- FilePath filePath = FilePath::fromString(full);
-
- QCOMPARE(filePath.toString(), full);
-
- QCOMPARE(filePath.scheme(), scheme);
- QCOMPARE(filePath.host(), host);
- QCOMPARE(filePath.path(), path);
-
- FilePath copy = FilePath::fromParts(scheme, host, path);
- QCOMPARE(copy.toString(), full);
-}
-
-void tst_fileutils::comparison()
-{
- QFETCH(QString, left);
- QFETCH(QString, right);
- QFETCH(bool, hostSensitive);
- QFETCH(bool, expected);
-
- HostOsInfo::setOverrideFileNameCaseSensitivity(
- hostSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
-
- FilePath l = FilePath::fromUserInput(left);
- FilePath r = FilePath::fromUserInput(right);
- QCOMPARE(l == r, expected);
-}
-
-void tst_fileutils::comparison_data()
-{
- QTest::addColumn<QString>("left");
- QTest::addColumn<QString>("right");
- QTest::addColumn<bool>("hostSensitive");
- QTest::addColumn<bool>("expected");
-
- QTest::newRow("r1") << "Abc" << "abc" << true << false;
- QTest::newRow("r2") << "Abc" << "abc" << false << true;
- QTest::newRow("r3") << "x://y/Abc" << "x://y/abc" << true << false;
- QTest::newRow("r4") << "x://y/Abc" << "x://y/abc" << false << false;
-
- QTest::newRow("s1") << "abc" << "abc" << true << true;
- QTest::newRow("s2") << "abc" << "abc" << false << true;
- QTest::newRow("s3") << "x://y/abc" << "x://y/abc" << true << true;
- QTest::newRow("s4") << "x://y/abc" << "x://y/abc" << false << true;
-}
-
-void tst_fileutils::linkFromString()
-{
- QFETCH(QString, testFile);
- QFETCH(Utils::FilePath, filePath);
- QFETCH(int, line);
- QFETCH(int, column);
- const Link link = Link::fromString(testFile, true);
- QCOMPARE(link.targetFilePath, filePath);
- QCOMPARE(link.targetLine, line);
- QCOMPARE(link.targetColumn, column);
-}
-
-void tst_fileutils::linkFromString_data()
-{
- QTest::addColumn<QString>("testFile");
- QTest::addColumn<Utils::FilePath>("filePath");
- QTest::addColumn<int>("line");
- QTest::addColumn<int>("column");
-
- QTest::newRow("no-line-no-column") << QString("someFile.txt")
- << FilePath("someFile.txt") << -1 << -1;
- QTest::newRow(": at end") << QString::fromLatin1("someFile.txt:")
- << FilePath("someFile.txt") << 0 << -1;
- QTest::newRow("+ at end") << QString::fromLatin1("someFile.txt+")
- << FilePath("someFile.txt") << 0 << -1;
- QTest::newRow(": for column") << QString::fromLatin1("someFile.txt:10:")
- << FilePath("someFile.txt") << 10 << -1;
- QTest::newRow("+ for column") << QString::fromLatin1("someFile.txt:10+")
- << FilePath("someFile.txt") << 10 << -1;
- QTest::newRow(": and + at end") << QString::fromLatin1("someFile.txt:+")
- << FilePath("someFile.txt") << 0 << -1;
- QTest::newRow("empty line") << QString::fromLatin1("someFile.txt:+10")
- << FilePath("someFile.txt") << 0 << 9;
- QTest::newRow(":line-no-column") << QString::fromLatin1("/some/path/file.txt:42")
- << FilePath("/some/path/file.txt") << 42 << -1;
- QTest::newRow("+line-no-column") << QString::fromLatin1("/some/path/file.txt+42")
- << FilePath("/some/path/file.txt") << 42 << -1;
- QTest::newRow(":line-:column") << QString::fromLatin1("/some/path/file.txt:42:3")
- << FilePath("/some/path/file.txt") << 42 << 2;
- QTest::newRow(":line-+column") << QString::fromLatin1("/some/path/file.txt:42+33")
- << FilePath("/some/path/file.txt") << 42 << 32;
- QTest::newRow("+line-:column") << QString::fromLatin1("/some/path/file.txt+142:30")
- << FilePath("/some/path/file.txt") << 142 << 29;
- QTest::newRow("+line-+column") << QString::fromLatin1("/some/path/file.txt+142+33")
- << FilePath("/some/path/file.txt") << 142 << 32;
- QTest::newRow("( at end") << QString::fromLatin1("/some/path/file.txt(")
- << FilePath("/some/path/file.txt") << -1 << -1;
- QTest::newRow("(42 at end") << QString::fromLatin1("/some/path/file.txt(42")
- << FilePath("/some/path/file.txt") << 42 << -1;
- QTest::newRow("(42) at end") << QString::fromLatin1("/some/path/file.txt(42)")
- << FilePath("/some/path/file.txt") << 42 << -1;
-}
-
-void tst_fileutils::pathAppended()
-{
- QFETCH(QString, left);
- QFETCH(QString, right);
- QFETCH(QString, expected);
-
- const FilePath fleft = FilePath::fromString(left);
- const FilePath fexpected = FilePath::fromString(expected);
- const FilePath result = fleft.pathAppended(right);
-
- QCOMPARE(result, fexpected);
-}
-
-void tst_fileutils::pathAppended_data()
-{
- QTest::addColumn<QString>("left");
- QTest::addColumn<QString>("right");
- QTest::addColumn<QString>("expected");
-
- QTest::newRow("p0") << "" << "" << "";
- QTest::newRow("p1") << "" << "/" << "/";
- QTest::newRow("p2") << "" << "c/" << "c/";
- QTest::newRow("p3") << "" << "/d" << "/d";
- QTest::newRow("p4") << "" << "c/d" << "c/d";
-
- QTest::newRow("r0") << "/" << "" << "/";
- QTest::newRow("r1") << "/" << "/" << "/";
- QTest::newRow("r2") << "/" << "c/" << "/c/";
- QTest::newRow("r3") << "/" << "/d" << "/d";
- QTest::newRow("r4") << "/" << "c/d" << "/c/d";
-
- QTest::newRow("s0") << "/b" << "" << "/b";
- QTest::newRow("s1") << "/b" << "/" << "/b/";
- QTest::newRow("s2") << "/b" << "c/" << "/b/c/";
- QTest::newRow("s3") << "/b" << "/d" << "/b/d";
- QTest::newRow("s4") << "/b" << "c/d" << "/b/c/d";
-
- QTest::newRow("t0") << "a/" << "" << "a/";
- QTest::newRow("t1") << "a/" << "/" << "a/";
- QTest::newRow("t2") << "a/" << "c/" << "a/c/";
- QTest::newRow("t3") << "a/" << "/d" << "a/d";
- QTest::newRow("t4") << "a/" << "c/d" << "a/c/d";
-
- QTest::newRow("u0") << "a/b" << "" << "a/b";
- QTest::newRow("u1") << "a/b" << "/" << "a/b/";
- QTest::newRow("u2") << "a/b" << "c/" << "a/b/c/";
- QTest::newRow("u3") << "a/b" << "/d" << "a/b/d";
- QTest::newRow("u4") << "a/b" << "c/d" << "a/b/c/d";
-
- if (HostOsInfo::isWindowsHost()) {
- QTest::newRow("win-1") << "c:" << "/a/b" << "c:/a/b";
- QTest::newRow("win-2") << "c:/" << "/a/b" << "c:/a/b";
- QTest::newRow("win-3") << "c:/" << "a/b" << "c:/a/b";
- }
-}
-
-void tst_fileutils::resolvePath_data()
-{
- QTest::addColumn<FilePath>("left");
- QTest::addColumn<FilePath>("right");
- QTest::addColumn<FilePath>("expected");
-
- QTest::newRow("empty") << FilePath() << FilePath() << FilePath();
- QTest::newRow("s0") << FilePath("/") << FilePath("b") << FilePath("/b");
- QTest::newRow("s1") << FilePath() << FilePath("b") << FilePath("b");
- QTest::newRow("s2") << FilePath("a") << FilePath() << FilePath("a");
- QTest::newRow("s3") << FilePath("a") << FilePath("b") << FilePath("a/b");
- QTest::newRow("s4") << FilePath("/a") << FilePath("/b") << FilePath("/b");
- QTest::newRow("s5") << FilePath("a") << FilePath("/b") << FilePath("/b");
- QTest::newRow("s6") << FilePath("/a") << FilePath("b") << FilePath("/a/b");
- QTest::newRow("s7") << FilePath("/a") << FilePath(".") << FilePath("/a");
- QTest::newRow("s8") << FilePath("/a") << FilePath("./b") << FilePath("/a/b");
-}
-
-void tst_fileutils::resolvePath()
-{
- QFETCH(FilePath, left);
- QFETCH(FilePath, right);
- QFETCH(FilePath, expected);
-
- const FilePath result = left.resolvePath(right);
-
- QCOMPARE(result, expected);
-}
-
-void tst_fileutils::relativeChildPath_data()
-{
- QTest::addColumn<FilePath>("parent");
- QTest::addColumn<FilePath>("child");
- QTest::addColumn<FilePath>("expected");
-
- QTest::newRow("empty") << FilePath() << FilePath() << FilePath();
-
- QTest::newRow("simple-0") << FilePath("/a") << FilePath("/a/b") << FilePath("b");
- QTest::newRow("simple-1") << FilePath("/a/") << FilePath("/a/b") << FilePath("b");
- QTest::newRow("simple-2") << FilePath("/a") << FilePath("/a/b/c/d/e/f") << FilePath("b/c/d/e/f");
-
- QTest::newRow("not-0") << FilePath("/x") << FilePath("/a/b") << FilePath();
- QTest::newRow("not-1") << FilePath("/a/b/c") << FilePath("/a/b") << FilePath();
-
-}
-
-void tst_fileutils::relativeChildPath()
-{
- QFETCH(FilePath, parent);
- QFETCH(FilePath, child);
- QFETCH(FilePath, expected);
-
- const FilePath result = child.relativeChildPath(parent);
-
- QCOMPARE(result, expected);
-}
+void tst_fileutils::initTestCase() {}
void tst_fileutils::commonPath()
{
@@ -996,133 +65,15 @@ void tst_fileutils::commonPath_data()
const FilePath url6 = FilePath::fromString("http:///./");
QTest::newRow("Zero paths") << FilePaths{} << FilePath();
- QTest::newRow("Single path") << FilePaths{ p1 } << p1;
- QTest::newRow("3 identical paths") << FilePaths{ p1, p1, p1 } << p1;
- QTest::newRow("3 paths, common path") << FilePaths{ p1, p2, p3 } << p1;
- QTest::newRow("3 paths, no common path") << FilePaths{ p1, p2, p4 } << p5;
- QTest::newRow("3 paths, first is part of second") << FilePaths{ p4, p1, p3 } << p5;
- QTest::newRow("Common scheme") << FilePaths{ url1, url3 } << url6;
- QTest::newRow("Different scheme") << FilePaths{ url1, url2 } << FilePath();
- QTest::newRow("Common host") << FilePaths{ url4, url5 } << url4;
- QTest::newRow("Different host") << FilePaths{ url1, url3 } << url6;
-}
-
-void tst_fileutils::asyncLocalCopy()
-{
- const FilePath orig = FilePath::fromString(rootPath).pathAppended("x/y/fileToCopy.txt");
- QVERIFY(orig.exists());
- const FilePath dest = FilePath::fromString(rootPath).pathAppended("x/fileToCopyDest.txt");
- auto afterCopy = [&orig, &dest, this](expected_str<void> result) {
- QVERIFY(result);
- // check existence, size and content
- QVERIFY(dest.exists());
- QCOMPARE(dest.fileSize(), orig.fileSize());
- QCOMPARE(dest.fileContents(), orig.fileContents());
- emit asyncTestDone();
- };
- QSignalSpy spy(this, &tst_fileutils::asyncTestDone);
- orig.asyncCopyFile(afterCopy, dest);
- // we usually have already received the signal, but if it fails wait 3s
- QVERIFY(spy.count() == 1 || spy.wait(3000));
-}
-
-void tst_fileutils::startsWithDriveLetter_data()
-{
- QTest::addColumn<FilePath>("path");
- QTest::addColumn<bool>("expected");
-
- QTest::newRow("empty") << FilePath() << false;
- QTest::newRow("simple-win") << FilePath::fromString("c:/a") << true;
- QTest::newRow("simple-linux") << FilePath::fromString("/c:/a") << false;
- QTest::newRow("relative") << FilePath("a/b") << false;
-}
-
-void tst_fileutils::startsWithDriveLetter()
-{
- QFETCH(FilePath, path);
- QFETCH(bool, expected);
-
- QCOMPARE(path.startsWithDriveLetter(), expected);
-}
-
-void tst_fileutils::onDevice_data()
-{
- QTest::addColumn<FilePath>("path");
- QTest::addColumn<FilePath>("templatePath");
- QTest::addColumn<FilePath>("expected");
-
- QTest::newRow("empty") << FilePath() << FilePath() << FilePath();
- QTest::newRow("same-local") << FilePath("/a/b") << FilePath("/a/b") << FilePath("/a/b");
- QTest::newRow("same-docker") << FilePath("docker://1234/a/b") << FilePath("docker://1234/e") << FilePath("docker://1234/a/b");
-
- QTest::newRow("docker-to-local") << FilePath("docker://1234/a/b") << FilePath("/c/d") << FilePath("/a/b");
- QTest::newRow("local-to-docker") << FilePath("/a/b") << FilePath("docker://1234/c/d") << FilePath("docker://1234/a/b");
-
-}
-
-void tst_fileutils::onDevice()
-{
- QFETCH(FilePath, path);
- QFETCH(FilePath, templatePath);
- QFETCH(FilePath, expected);
-
- QCOMPARE(path.onDevice(templatePath), expected);
-}
-
-void tst_fileutils::stringAppended_data()
-{
- QTest::addColumn<FilePath>("left");
- QTest::addColumn<QString>("right");
- QTest::addColumn<FilePath>("expected");
-
- QTest::newRow("empty") << FilePath() << QString() << FilePath();
- QTest::newRow("empty-left") << FilePath() << "a" << FilePath("a");
- QTest::newRow("empty-right") << FilePath("a") << QString() << FilePath("a");
- QTest::newRow("add-root") << FilePath() << QString("/") << FilePath("/");
- QTest::newRow("add-root-and-more") << FilePath() << QString("/test/blah") << FilePath("/test/blah");
- QTest::newRow("add-extension") << FilePath::fromString("/a") << QString(".txt") << FilePath("/a.txt");
- QTest::newRow("trailing-slash") << FilePath::fromString("/a") << QString("b/") << FilePath("/ab/");
- QTest::newRow("slash-trailing-slash") << FilePath::fromString("/a/") << QString("b/") << FilePath("/a/b/");
-}
-
-void tst_fileutils::stringAppended()
-{
- QFETCH(FilePath, left);
- QFETCH(QString, right);
- QFETCH(FilePath, expected);
-
- const FilePath result = left.stringAppended(right);
-
- QCOMPARE(expected, result);
-}
-
-void tst_fileutils::url()
-{
- QFETCH(QString, url);
- QFETCH(QString, expectedScheme);
- QFETCH(QString, expectedHost);
- QFETCH(QString, expectedPath);
-
- const FilePath result = FilePath::fromString(url);
- QCOMPARE(result.scheme(), expectedScheme);
- QCOMPARE(result.host(), expectedHost);
- QCOMPARE(result.path(), expectedPath);
-}
-
-void tst_fileutils::url_data()
-{
- QTest::addColumn<QString>("url");
- QTest::addColumn<QString>("expectedScheme");
- QTest::addColumn<QString>("expectedHost");
- QTest::addColumn<QString>("expectedPath");
- QTest::newRow("empty") << QString() << QString() << QString() << QString();
- QTest::newRow("simple-file") << QString("file:///a/b") << QString("file") << QString() << QString("/a/b");
- QTest::newRow("simple-file-root") << QString("file:///") << QString("file") << QString() << QString("/");
- QTest::newRow("simple-docker") << QString("docker://1234/a/b") << QString("docker") << QString("1234") << QString("/a/b");
- QTest::newRow("simple-ssh") << QString("ssh://user@host/a/b") << QString("ssh") << QString("user@host") << QString("/a/b");
- QTest::newRow("simple-ssh-with-port") << QString("ssh://user@host:1234/a/b") << QString("ssh") << QString("user@host:1234") << QString("/a/b");
- QTest::newRow("http-qt.io") << QString("http://qt.io") << QString("http") << QString("qt.io") << QString();
- QTest::newRow("http-qt.io-index.html") << QString("http://qt.io/index.html") << QString("http") << QString("qt.io") << QString("/index.html");
+ QTest::newRow("Single path") << FilePaths{p1} << p1;
+ QTest::newRow("3 identical paths") << FilePaths{p1, p1, p1} << p1;
+ QTest::newRow("3 paths, common path") << FilePaths{p1, p2, p3} << p1;
+ QTest::newRow("3 paths, no common path") << FilePaths{p1, p2, p4} << p5;
+ QTest::newRow("3 paths, first is part of second") << FilePaths{p4, p1, p3} << p5;
+ QTest::newRow("Common scheme") << FilePaths{url1, url3} << url6;
+ QTest::newRow("Different scheme") << FilePaths{url1, url2} << FilePath();
+ QTest::newRow("Common host") << FilePaths{url4, url5} << url4;
+ QTest::newRow("Different host") << FilePaths{url1, url3} << url6;
}
void tst_fileutils::bytesAvailableFromDF_data()
@@ -1131,13 +82,33 @@ void tst_fileutils::bytesAvailableFromDF_data()
QTest::addColumn<qint64>("expected");
QTest::newRow("empty") << QByteArray("") << qint64(-1);
- QTest::newRow("mac") << QByteArray("Filesystem 1024-blocks Used Available Capacity iused ifree %iused Mounted on\n/dev/disk3s5 971350180 610014564 342672532 65% 4246780 3426725320 0% /System/Volumes/Data\n") << qint64(342672532);
- QTest::newRow("alpine") << QByteArray("Filesystem 1K-blocks Used Available Use% Mounted on\noverlay 569466448 163526072 376983360 30% /\n") << qint64(376983360);
- QTest::newRow("alpine-no-trailing-br") << QByteArray("Filesystem 1K-blocks Used Available Use% Mounted on\noverlay 569466448 163526072 376983360 30% /") << qint64(376983360);
- QTest::newRow("alpine-missing-line") << QByteArray("Filesystem 1K-blocks Used Available Use% Mounted on\n") << qint64(-1);
- QTest::newRow("wrong-header") << QByteArray("Filesystem 1K-blocks Used avail Use% Mounted on\noverlay 569466448 163526072 376983360 30% /\n") << qint64(-1);
- QTest::newRow("not-enough-fields") << QByteArray("Filesystem 1K-blocks Used avail Use% Mounted on\noverlay 569466448\n") << qint64(-1);
- QTest::newRow("not-enough-header-fields") << QByteArray("Filesystem 1K-blocks Used \noverlay 569466448 163526072 376983360 30% /\n") << qint64(-1);
+ QTest::newRow("mac") << QByteArray(
+ "Filesystem 1024-blocks Used Available Capacity iused ifree %iused Mounted "
+ "on\n/dev/disk3s5 971350180 610014564 342672532 65% 4246780 3426725320 0% "
+ "/System/Volumes/Data\n")
+ << qint64(342672532);
+ QTest::newRow("alpine") << QByteArray(
+ "Filesystem 1K-blocks Used Available Use% Mounted on\noverlay "
+ "569466448 163526072 376983360 30% /\n")
+ << qint64(376983360);
+ QTest::newRow("alpine-no-trailing-br")
+ << QByteArray("Filesystem 1K-blocks Used Available Use% Mounted on\noverlay "
+ " 569466448 163526072 376983360 30% /")
+ << qint64(376983360);
+ QTest::newRow("alpine-missing-line")
+ << QByteArray("Filesystem 1K-blocks Used Available Use% Mounted on\n")
+ << qint64(-1);
+ QTest::newRow("wrong-header") << QByteArray(
+ "Filesystem 1K-blocks Used avail Use% Mounted on\noverlay "
+ "569466448 163526072 376983360 30% /\n")
+ << qint64(-1);
+ QTest::newRow("not-enough-fields") << QByteArray(
+ "Filesystem 1K-blocks Used avail Use% Mounted on\noverlay 569466448\n")
+ << qint64(-1);
+ QTest::newRow("not-enough-header-fields")
+ << QByteArray("Filesystem 1K-blocks Used \noverlay 569466448 "
+ "163526072 376983360 30% /\n")
+ << qint64(-1);
}
void tst_fileutils::bytesAvailableFromDF()
@@ -1152,194 +123,6 @@ void tst_fileutils::bytesAvailableFromDF()
QCOMPARE(result, expected);
}
-void tst_fileutils::cleanPath_data()
-{
- QTest::addColumn<QString>("path");
- QTest::addColumn<QString>("expected");
-
- QTest::newRow("data0") << "/Users/sam/troll/qt4.0//.." << "/Users/sam/troll";
- QTest::newRow("data1") << "/Users/sam////troll/qt4.0//.." << "/Users/sam/troll";
- QTest::newRow("data2") << "/" << "/";
- QTest::newRow("data2-up") << "/path/.." << "/";
- QTest::newRow("data2-above-root") << "/.." << "/..";
- QTest::newRow("data3") << QDir::cleanPath("../.") << "..";
- QTest::newRow("data4") << QDir::cleanPath("../..") << "../..";
- QTest::newRow("data5") << "d:\\a\\bc\\def\\.." << "d:/a/bc"; // QDir/Linux had: "d:\\a\\bc\\def\\.."
- QTest::newRow("data6") << "d:\\a\\bc\\def\\../../.." << "d:/"; // QDir/Linux had: ".."
- QTest::newRow("data7") << ".//file1.txt" << "file1.txt";
- QTest::newRow("data8") << "/foo/bar/..//file1.txt" << "/foo/file1.txt";
- QTest::newRow("data9") << "//" << "//"; // QDir had: "/"
- QTest::newRow("data10w") << "c:\\" << "c:/";
- QTest::newRow("data10l") << "/:/" << "/:";
- QTest::newRow("data11") << "//foo//bar" << "//foo/bar"; // QDir/Win had: "//foo/bar"
- QTest::newRow("data12") << "ab/a/" << "ab/a"; // Path item with length of 2
- QTest::newRow("data13w") << "c:/" << "c:/";
- QTest::newRow("data13w2") << "c:\\" << "c:/";
- //QTest::newRow("data13l") << "c://" << "c:";
-
-// QTest::newRow("data14") << "c://foo" << "c:/foo";
- QTest::newRow("data15") << "//c:/foo" << "//c:/foo"; // QDir/Lin had: "/c:/foo";
- QTest::newRow("drive-up") << "A:/path/.." << "A:/";
- QTest::newRow("drive-above-root") << "A:/.." << "A:/..";
- QTest::newRow("unc-server-up") << "//server/path/.." << "//server/";
- QTest::newRow("unc-server-above-root") << "//server/.." << "//server/..";
-
- QTest::newRow("longpath") << "\\\\?\\d:\\" << "d:/";
- QTest::newRow("longpath-slash") << "//?/d:/" << "d:/";
- QTest::newRow("longpath-mixed-slashes") << "//?/d:\\" << "d:/";
- QTest::newRow("longpath-mixed-slashes-2") << "\\\\?\\d:/" << "d:/";
-
- QTest::newRow("unc-network-share") << "\\\\?\\UNC\\localhost\\c$\\tmp.txt"
- << "//localhost/c$/tmp.txt";
- QTest::newRow("unc-network-share-slash") << "//?/UNC/localhost/c$/tmp.txt"
- << "//localhost/c$/tmp.txt";
- QTest::newRow("unc-network-share-mixed-slashes") << "//?/UNC/localhost\\c$\\tmp.txt"
- << "//localhost/c$/tmp.txt";
- QTest::newRow("unc-network-share-mixed-slashes-2") << "\\\\?\\UNC\\localhost/c$/tmp.txt"
- << "//localhost/c$/tmp.txt";
-
- QTest::newRow("QTBUG-23892_0") << "foo/.." << ".";
- QTest::newRow("QTBUG-23892_1") << "foo/../" << ".";
-
- QTest::newRow("QTBUG-3472_0") << "/foo/./bar" << "/foo/bar";
- QTest::newRow("QTBUG-3472_1") << "./foo/.." << ".";
- QTest::newRow("QTBUG-3472_2") << "./foo/../" << ".";
-
- QTest::newRow("resource0") << ":/prefix/foo.bar" << ":/prefix/foo.bar";
- QTest::newRow("resource1") << ":/prefix/..//prefix/foo.bar" << ":/prefix/foo.bar";
-
- QTest::newRow("ssh") << "ssh://host/prefix/../foo.bar" << "ssh://host/foo.bar";
- QTest::newRow("ssh2") << "ssh://host/../foo.bar" << "ssh://host/../foo.bar";
-}
-
-void tst_fileutils::cleanPath()
-{
- QFETCH(QString, path);
- QFETCH(QString, expected);
- QString cleaned = doCleanPath(path);
- QCOMPARE(cleaned, expected);
-}
-
-void tst_fileutils::isSameFile_data()
-{
- QTest::addColumn<FilePath>("left");
- QTest::addColumn<FilePath>("right");
- QTest::addColumn<bool>("shouldBeEqual");
-
- QTest::addRow("/==/")
- << FilePath::fromString("/") << FilePath::fromString("/") << true;
- QTest::addRow("/!=tmp")
- << FilePath::fromString("/") << FilePath::fromString(tempDir.path()) << false;
-
-
- QDir dir(tempDir.path());
- touch(dir, "target-file", false);
-
- QFile file(dir.absoluteFilePath("target-file"));
- if (file.link(dir.absoluteFilePath("source-file"))) {
- QTest::addRow("real==link")
- << FilePath::fromString(file.fileName())
- << FilePath::fromString(dir.absoluteFilePath("target-file"))
- << true;
- }
-
- QTest::addRow("/!=non-existing")
- << FilePath::fromString("/") << FilePath::fromString("/this-path/does-not-exist") << false;
-
- QTest::addRow("two-devices") << FilePath::fromString(
- "docker://boot2qt-raspberrypi4-64:6.5.0/opt/toolchain/sysroots/aarch64-pokysdk-linux/usr/"
- "bin/aarch64-poky-linux/aarch64-poky-linux-g++")
- << FilePath::fromString("docker://qt-linux:6/usr/bin/g++")
- << false;
-}
-
-void tst_fileutils::isSameFile()
-{
- QFETCH(FilePath, left);
- QFETCH(FilePath, right);
- QFETCH(bool, shouldBeEqual);
-
- QCOMPARE(left.isSameFile(right), shouldBeEqual);
-}
-
-void tst_fileutils::hostSpecialChars_data()
-{
- QTest::addColumn<QString>("scheme");
- QTest::addColumn<QString>("host");
- QTest::addColumn<QString>("path");
- QTest::addColumn<FilePath>("expected");
-
- QTest::addRow("slash-in-host") << "device" << "host/name" << "/" << FilePath::fromString("device://host%2fname/");
- QTest::addRow("percent-in-host") << "device" << "host%name" << "/" << FilePath::fromString("device://host%25name/");
- QTest::addRow("percent-and-slash-in-host") << "device" << "host/%name" << "/" << FilePath::fromString("device://host%2f%25name/");
- QTest::addRow("qtc-dev-slash-in-host-linux") << "device" << "host/name" << "/" << FilePath::fromString("/__qtc_devices__/device/host%2fname/");
- QTest::addRow("qtc-dev-slash-in-host-windows") << "device" << "host/name" << "/" << FilePath::fromString("c:/__qtc_devices__/device/host%2fname/");
-
-}
-
-void tst_fileutils::hostSpecialChars()
-{
- QFETCH(QString, scheme);
- QFETCH(QString, host);
- QFETCH(QString, path);
- QFETCH(FilePath, expected);
-
- FilePath fp;
- fp.setParts(scheme, host, path);
-
- // Check that setParts and fromString give the same result
- QCOMPARE(fp, expected);
- QCOMPARE(fp.host(), expected.host());
- QCOMPARE(fp.host(), host);
- QCOMPARE(expected.host(), host);
-
- QString toStringExpected = expected.toString();
- QString toStringActual = fp.toString();
-
- // Check that toString gives the same result
- QCOMPARE(toStringActual, toStringExpected);
-
- // Check that fromString => toString => fromString gives the same result
- FilePath toFromExpected = FilePath::fromString(expected.toString());
- QCOMPARE(toFromExpected, expected);
- QCOMPARE(toFromExpected, fp);
-
- // Check that setParts => toString => fromString gives the same result
- FilePath toFromActual = FilePath::fromString(fp.toString());
- QCOMPARE(toFromActual, fp);
- QCOMPARE(toFromExpected, expected);
-}
-
-void tst_fileutils::tmp_data()
-{
- QTest::addColumn<QString>("templatepath");
- QTest::addColumn<bool>("expected");
-
- QTest::addRow("empty") << "" << true;
- QTest::addRow("no-template") << "foo" << true;
- QTest::addRow("realtive-template") << "my-file-XXXXXXXX" << true;
- QTest::addRow("absolute-template") << QDir::tempPath() + "/my-file-XXXXXXXX" << true;
- QTest::addRow("non-existing-dir") << "/this/path/does/not/exist/my-file-XXXXXXXX" << false;
-
- QTest::addRow("on-device") << "device://test/./my-file-XXXXXXXX" << true;
-}
-
-void tst_fileutils::tmp()
-{
- QFETCH(QString, templatepath);
- QFETCH(bool, expected);
-
- FilePath fp = FilePath::fromString(templatepath);
-
- const auto result = fp.createTempFile();
- QCOMPARE(result.has_value(), expected);
-
- if (result.has_value()) {
- QVERIFY(result->exists());
- QVERIFY(result->removeFile());
- }
-}
-
void tst_fileutils::filePathInfoFromTriple_data()
{
QTest::addColumn<QString>("statoutput");
@@ -1389,7 +172,7 @@ void tst_fileutils::filePathInfoFromTriple()
QFETCH(QString, statoutput);
QFETCH(FilePathInfo, expected);
- const FilePathInfo result = FileUtils::filePathInfoFromTriple(statoutput);
+ const FilePathInfo result = FileUtils::filePathInfoFromTriple(statoutput, 16);
QCOMPARE(result, expected);
}
diff --git a/tests/auto/utils/fsengine/tst_fsengine.cpp b/tests/auto/utils/fsengine/tst_fsengine.cpp
index 8b0cdfc1b45..d516bda8c21 100644
--- a/tests/auto/utils/fsengine/tst_fsengine.cpp
+++ b/tests/auto/utils/fsengine/tst_fsengine.cpp
@@ -31,6 +31,8 @@ private slots:
void testBrokenWindowsPath();
void testRead();
void testWrite();
+ void testRootFromDotDot();
+ void testDirtyPaths();
private:
QString makeTestPath(QString path, bool asUrl = false);
@@ -93,6 +95,10 @@ void tst_fsengine::testRootPathContainsFakeDir()
const QStringList schemeList = schemes.entryList();
QVERIFY(schemeList.contains("device"));
+ QDir devices(FilePath::specialDeviceRootPath());
+ const QStringList deviceList = devices.entryList();
+ QVERIFY(deviceList.contains("test"));
+
QDir deviceRoot(FilePath::specialDeviceRootPath() + "/test" + startWithSlash(QDir::rootPath()));
const QStringList deviceRootList = deviceRoot.entryList();
QVERIFY(!deviceRootList.isEmpty());
@@ -129,7 +135,8 @@ QString tst_fsengine::makeTestPath(QString path, bool asUrl)
return QString("device://test%1/tst_fsengine/%2").arg(tempFolder, path);
return QString(FilePath::specialDeviceRootPath() + "/test%1/tst_fsengine/%2")
- .arg(startWithSlash(tempFolder), path);
+ .arg(startWithSlash(tempFolder))
+ .arg(path);
}
void tst_fsengine::testListDir()
@@ -209,5 +216,49 @@ void tst_fsengine::testWrite()
QCOMPARE(f.readAll(), data);
}
+void tst_fsengine::testRootFromDotDot()
+{
+ const QString path = QDir::rootPath() + "some-folder/..";
+ QFileInfo fInfo(path);
+
+ QCOMPARE(fInfo.fileName(), QString(".."));
+
+ QDir dRoot(path);
+ const auto dRootEntryList = dRoot.entryList();
+ QVERIFY(dRootEntryList.contains(FilePath::specialRootName()));
+
+ QFileInfo fInfo2(FilePath::specialRootPath() + "/xyz/..");
+ QCOMPARE(fInfo2.fileName(), "..");
+
+ QDir schemesWithDotDot(FilePath::specialRootPath() + "/xyz/..");
+ const QStringList schemeWithDotDotList = schemesWithDotDot.entryList();
+ QVERIFY(schemeWithDotDotList.contains("device"));
+
+ QFileInfo fInfo3(FilePath::specialDeviceRootPath() + "/xyz/..");
+ QCOMPARE(fInfo3.fileName(), "..");
+
+ QDir devicesWithDotDot(FilePath::specialDeviceRootPath() + "/test/..");
+ const QStringList deviceListWithDotDot = devicesWithDotDot.entryList();
+ QVERIFY(deviceListWithDotDot.contains("test"));
+
+ QFileInfo fInfo4(FilePath::specialDeviceRootPath() + "/test/tmp/..");
+ QCOMPARE(fInfo4.fileName(), "..");
+}
+
+void tst_fsengine::testDirtyPaths()
+{
+ // "//__qtc_devices"
+ QVERIFY(QFileInfo("/" + FilePath::specialRootPath()).exists());
+
+ // "///__qtc_devices/device"
+ QVERIFY(QFileInfo("//" + FilePath::specialDeviceRootPath()).exists());
+
+ // "////__qtc_devices/device////test"
+ QVERIFY(QFileInfo("///" + FilePath::specialDeviceRootPath() + "////test").exists());
+
+ // "/////__qtc_devices/device/test/..."
+ QVERIFY(QFileInfo("////" + makeTestPath("")).exists());
+}
+
QTEST_GUILESS_MAIN(tst_fsengine)
#include "tst_fsengine.moc"
diff --git a/tests/auto/utils/mathutils/tst_mathutils.cpp b/tests/auto/utils/mathutils/tst_mathutils.cpp
index b817f157436..a29f908748d 100644
--- a/tests/auto/utils/mathutils/tst_mathutils.cpp
+++ b/tests/auto/utils/mathutils/tst_mathutils.cpp
@@ -1,7 +1,7 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
-#include "utils/mathutils.h"
+#include <utils/mathutils.h>
#include <QtTest>
diff --git a/tests/auto/utils/qtcprocess/tst_qtcprocess.cpp b/tests/auto/utils/qtcprocess/tst_qtcprocess.cpp
index a26924089e0..bd8bb58af25 100644
--- a/tests/auto/utils/qtcprocess/tst_qtcprocess.cpp
+++ b/tests/auto/utils/qtcprocess/tst_qtcprocess.cpp
@@ -94,6 +94,33 @@ class tst_QtcProcess : public QObject
private slots:
void initTestCase();
+ void testEnv()
+ {
+ if (HostOsInfo::isWindowsHost())
+ QSKIP("Skipping env test on Windows");
+
+ QProcess qproc;
+ FilePath envPath = Environment::systemEnvironment().searchInPath("env");
+
+ qproc.setProgram(envPath.nativePath());
+ qproc.start();
+ qproc.waitForFinished();
+ QByteArray qoutput = qproc.readAllStandardOutput() + qproc.readAllStandardError();
+ qDebug() << "QProcess output:" << qoutput;
+ QCOMPARE(qproc.exitCode(), 0);
+
+ QtcProcess qtcproc;
+ qtcproc.setCommand({envPath, {}});
+ qtcproc.runBlocking();
+ QCOMPARE(qtcproc.exitCode(), 0);
+ QByteArray qtcoutput = qtcproc.readAllRawStandardOutput()
+ + qtcproc.readAllRawStandardError();
+
+ qDebug() << "QtcProcess output:" << qtcoutput;
+
+ QCOMPARE(qtcoutput.size() > 0, qoutput.size() > 0);
+ }
+
void multiRead();
void splitArgs_data();
@@ -698,11 +725,11 @@ void tst_QtcProcess::expandMacros_data()
title = vals[i].in;
} else {
char buf[80];
- sprintf(buf, "%s: %s", title, vals[i].in);
+ snprintf(buf, 80, "%s: %s", title, vals[i].in);
QTest::newRow(buf) << QString::fromLatin1(vals[i].in)
<< QString::fromLatin1(vals[i].out)
<< vals[i].os;
- sprintf(buf, "padded %s: %s", title, vals[i].in);
+ snprintf(buf, 80, "padded %s: %s", title, vals[i].in);
QTest::newRow(buf) << QString(sp + QString::fromLatin1(vals[i].in) + sp)
<< QString(sp + QString::fromLatin1(vals[i].out) + sp)
<< vals[i].os;
diff --git a/tests/auto/utils/stringutils/tst_stringutils.cpp b/tests/auto/utils/stringutils/tst_stringutils.cpp
index e13e8411a5b..4863bfd9508 100644
--- a/tests/auto/utils/stringutils/tst_stringutils.cpp
+++ b/tests/auto/utils/stringutils/tst_stringutils.cpp
@@ -79,6 +79,8 @@ private slots:
void testTrim();
void testWildcardToRegularExpression_data();
void testWildcardToRegularExpression();
+ void testSplitAtFirst_data();
+ void testSplitAtFirst();
private:
TestMacroExpander mx;
@@ -404,6 +406,38 @@ void tst_StringUtils::testWildcardToRegularExpression()
QCOMPARE(string.contains(re), matches);
}
+void tst_StringUtils::testSplitAtFirst_data()
+{
+ QTest::addColumn<QString>("string");
+ QTest::addColumn<QChar>("separator");
+ QTest::addColumn<QString>("left");
+ QTest::addColumn<QString>("right");
+
+ QTest::newRow("Empty") << QString{} << QChar{} << QString{} << QString{};
+ QTest::newRow("EmptyString") << QString{} << QChar{'a'} << QString{} << QString{};
+ QTest::newRow("EmptySeparator") << QString{"abc"} << QChar{} << QString{"abc"} << QString{};
+ QTest::newRow("NoSeparator") << QString{"abc"} << QChar{'d'} << QString{"abc"} << QString{};
+ QTest::newRow("SeparatorAtStart") << QString{"abc"} << QChar{'a'} << QString{} << QString{"bc"};
+ QTest::newRow("SeparatorAtEnd") << QString{"abc"} << QChar{'c'} << QString{"ab"} << QString{};
+ QTest::newRow("SeparatorInMiddle")
+ << QString{"abc"} << QChar{'b'} << QString{"a"} << QString{"c"};
+ QTest::newRow("SeparatorAtStartAndEnd")
+ << QString{"abca"} << QChar{'a'} << QString{} << QString{"bca"};
+}
+
+void tst_StringUtils::testSplitAtFirst()
+{
+ QFETCH(QString, string);
+ QFETCH(QChar, separator);
+ QFETCH(QString, left);
+ QFETCH(QString, right);
+
+ const auto [l, r] = Utils::splitAtFirst(string, separator);
+
+ QCOMPARE(l, left);
+ QCOMPARE(r, right);
+}
+
QTEST_GUILESS_MAIN(tst_StringUtils)
#include "tst_stringutils.moc"
diff --git a/tests/auto/utils/tasktree/tst_tasktree.cpp b/tests/auto/utils/tasktree/tst_tasktree.cpp
index 43bf0468c5d..37bbe498d7f 100644
--- a/tests/auto/utils/tasktree/tst_tasktree.cpp
+++ b/tests/auto/utils/tasktree/tst_tasktree.cpp
@@ -19,7 +19,9 @@ enum class Handler {
Error,
GroupSetup,
GroupDone,
- GroupError
+ GroupError,
+ Sync,
+ Activator,
};
using Log = QList<QPair<int, Handler>>;
@@ -172,6 +174,9 @@ void tst_TaskTree::processTree_data()
const auto groupError = [storage](int groupId) {
return [=] { storage->m_log.append({groupId, Handler::GroupError}); };
};
+ const auto setupSync = [storage](int syncId, bool success) {
+ return [=] { storage->m_log.append({syncId, Handler::Sync}); return success; };
+ };
const auto constructSimpleSequence = [=](const Workflow &policy) {
return Group {
@@ -988,6 +993,126 @@ void tst_TaskTree::processTree_data()
QTest::newRow("DeeplyNestedParallelError")
<< TestData{storage, root, log, 5, OnStart::Running, OnDone::Failure};
}
+
+ {
+ const Group root {
+ Storage(storage),
+ Sync(setupSync(1, true)),
+ Sync(setupSync(2, true)),
+ Sync(setupSync(3, true)),
+ Sync(setupSync(4, true)),
+ Sync(setupSync(5, true))
+ };
+ const Log log {
+ {1, Handler::Sync},
+ {2, Handler::Sync},
+ {3, Handler::Sync},
+ {4, Handler::Sync},
+ {5, Handler::Sync}
+ };
+ QTest::newRow("SyncSequential")
+ << TestData{storage, root, log, 0, OnStart::NotRunning, OnDone::Success};
+ }
+
+ {
+ const Group root {
+ Storage(storage),
+ parallel,
+ Sync(setupSync(1, true)),
+ Sync(setupSync(2, true)),
+ Sync(setupSync(3, true)),
+ Sync(setupSync(4, true)),
+ Sync(setupSync(5, true))
+ };
+ const Log log {
+ {1, Handler::Sync},
+ {2, Handler::Sync},
+ {3, Handler::Sync},
+ {4, Handler::Sync},
+ {5, Handler::Sync}
+ };
+ QTest::newRow("SyncParallel")
+ << TestData{storage, root, log, 0, OnStart::NotRunning, OnDone::Success};
+ }
+
+ {
+ const Group root {
+ Storage(storage),
+ parallel,
+ Sync(setupSync(1, true)),
+ Sync(setupSync(2, true)),
+ Sync(setupSync(3, false)),
+ Sync(setupSync(4, true)),
+ Sync(setupSync(5, true))
+ };
+ const Log log {
+ {1, Handler::Sync},
+ {2, Handler::Sync},
+ {3, Handler::Sync}
+ };
+ QTest::newRow("SyncError")
+ << TestData{storage, root, log, 0, OnStart::NotRunning, OnDone::Failure};
+ }
+
+ {
+ const Group root {
+ Storage(storage),
+ Sync(setupSync(1, true)),
+ Process(setupProcess(2)),
+ Sync(setupSync(3, true)),
+ Process(setupProcess(4)),
+ Sync(setupSync(5, true)),
+ OnGroupDone(groupDone(0))
+ };
+ const Log log {
+ {1, Handler::Sync},
+ {2, Handler::Setup},
+ {3, Handler::Sync},
+ {4, Handler::Setup},
+ {5, Handler::Sync},
+ {0, Handler::GroupDone}
+ };
+ QTest::newRow("SyncAndAsync")
+ << TestData{storage, root, log, 2, OnStart::Running, OnDone::Success};
+ }
+
+ {
+ Condition condition;
+
+ const auto setupProcessWithCondition
+ = [storage, condition, setupProcessHelper](int processId) {
+ return [storage, condition, setupProcessHelper, processId](QtcProcess &process) {
+ setupProcessHelper(process, {"-return", "0"}, processId);
+ CustomStorage *currentStorage = storage.activeStorage();
+ ConditionActivator *currentActivator = condition.activator();
+ connect(&process, &QtcProcess::started, [currentStorage, currentActivator, processId] {
+ currentStorage->m_log.append({processId, Handler::Activator});
+ currentActivator->activate();
+ });
+ };
+ };
+
+ const Group root {
+ parallel,
+ Storage(storage),
+ Process(setupProcessWithCondition(1)),
+ Group {
+ OnGroupSetup(groupSetup(2)),
+ WaitFor(condition),
+ Process(setupProcess(2)),
+ Process(setupProcess(3))
+ }
+ };
+ const Log log {
+ {1, Handler::Setup},
+ {2, Handler::GroupSetup},
+ {1, Handler::Activator},
+ {2, Handler::Setup},
+ {3, Handler::Setup}
+ };
+ QTest::newRow("WaitFor")
+ << TestData{storage, root, log, 3, OnStart::Running, OnDone::Success};
+ }
}
void tst_TaskTree::processTree()
diff --git a/tests/auto/utils/unixdevicefileaccess/CMakeLists.txt b/tests/auto/utils/unixdevicefileaccess/CMakeLists.txt
new file mode 100644
index 00000000000..0cf8d43c712
--- /dev/null
+++ b/tests/auto/utils/unixdevicefileaccess/CMakeLists.txt
@@ -0,0 +1,4 @@
+add_qtc_test(tst_utils_unixdevicefileaccess
+ DEPENDS Utils
+ SOURCES tst_unixdevicefileaccess.cpp
+)
diff --git a/tests/auto/utils/unixdevicefileaccess/tst_unixdevicefileaccess.cpp b/tests/auto/utils/unixdevicefileaccess/tst_unixdevicefileaccess.cpp
new file mode 100644
index 00000000000..b5aef12d06c
--- /dev/null
+++ b/tests/auto/utils/unixdevicefileaccess/tst_unixdevicefileaccess.cpp
@@ -0,0 +1,78 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#include <QDebug>
+#include <QRandomGenerator>
+#include <QtCore/qiodevice.h>
+#include <QtTest>
+
+#include <utils/commandline.h>
+#include <utils/devicefileaccess.h>
+#include <utils/hostosinfo.h>
+#include <utils/link.h>
+
+//TESTED_COMPONENT=src/libs/utils
+using namespace Utils;
+
+namespace QTest {
+template<>
+char *toString(const FilePath &filePath)
+{
+ return qstrdup(filePath.toString().toLocal8Bit().constData());
+}
+} // namespace QTest
+
+class TestDFA : public UnixDeviceFileAccess
+{
+public:
+ using UnixDeviceFileAccess::UnixDeviceFileAccess;
+
+ virtual RunResult runInShell(const CommandLine &cmdLine,
+ const QByteArray &inputData = {}) const override
+ {
+ QProcess p;
+ p.setProgram(cmdLine.executable().toString());
+ p.setArguments(cmdLine.splitArguments());
+ p.setProcessChannelMode(QProcess::SeparateChannels);
+
+ p.start();
+ p.waitForStarted();
+ if (inputData.size() > 0) {
+ p.write(inputData);
+ p.closeWriteChannel();
+ }
+ p.waitForFinished();
+ return {p.exitCode(), p.readAllStandardOutput(), p.readAllStandardError()};
+ }
+};
+
+class tst_unixdevicefileaccess : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase()
+ {
+ if (HostOsInfo::isWindowsHost())
+ QSKIP("This test is only for Unix hosts");
+
+ m_fileSizeTestFile.writeFileContents(QByteArray(1024, 'a'));
+ }
+
+ void fileSize()
+ {
+ const auto size = m_dfaPtr->fileSize(m_fileSizeTestFile);
+ QCOMPARE(size, 1024);
+ }
+
+private:
+ TestDFA m_dfa;
+ DeviceFileAccess *m_dfaPtr = &m_dfa;
+
+ QTemporaryDir m_tempDir;
+ FilePath m_fileSizeTestFile = FilePath::fromString(m_tempDir.filePath("size-test"));
+};
+
+QTEST_GUILESS_MAIN(tst_unixdevicefileaccess)
+
+#include "tst_unixdevicefileaccess.moc"
diff --git a/tests/auto/utils/unixdevicefileaccess/unixdevicefileaccess.qbs b/tests/auto/utils/unixdevicefileaccess/unixdevicefileaccess.qbs
new file mode 100644
index 00000000000..1776ef4a6ee
--- /dev/null
+++ b/tests/auto/utils/unixdevicefileaccess/unixdevicefileaccess.qbs
@@ -0,0 +1,11 @@
+import qbs
+
+QtcAutotest {
+ name: "UnixDeviceFileAccess autotest"
+ Depends { name: "Utils" }
+ Properties {
+ condition: qbs.toolchain.contains("gcc")
+ cpp.cxxFlags: base.concat(["-Wno-trigraphs"])
+ }
+ files: "tst_unixdevicefileaccess.cpp"
+}
diff --git a/tests/auto/utils/utils.qbs b/tests/auto/utils/utils.qbs
index eea36644236..5f4d0439201 100644
--- a/tests/auto/utils/utils.qbs
+++ b/tests/auto/utils/utils.qbs
@@ -8,6 +8,7 @@ Project {
"commandline/commandline.qbs",
"deviceshell/deviceshell.qbs",
"expected/expected.qbs",
+ "filepath/filepath.qbs",
"fileutils/fileutils.qbs",
"fsengine/fsengine.qbs",
"fuzzymatcher/fuzzymatcher.qbs",
@@ -21,5 +22,6 @@ Project {
"tasktree/tasktree.qbs",
"templateengine/templateengine.qbs",
"treemodel/treemodel.qbs",
+ "unixdevicefileaccess/unixdevicefileaccess.qbs",
]
}
diff --git a/tests/manual/deviceshell/tst_deviceshell.cpp b/tests/manual/deviceshell/tst_deviceshell.cpp
index 10c14149758..2703e6b82d6 100644
--- a/tests/manual/deviceshell/tst_deviceshell.cpp
+++ b/tests/manual/deviceshell/tst_deviceshell.cpp
@@ -5,14 +5,12 @@
#include <utils/deviceshell.h>
#include <utils/environment.h>
-#include <utils/hostosinfo.h>
#include <utils/launcherinterface.h>
#include <utils/qtcprocess.h>
-#include <utils/runextensions.h>
#include <utils/temporarydirectory.h>
-#include <utils/mapreduce.h>
#include <QObject>
+#include <QtConcurrent>
#include <QtTest>
using namespace Utils;
@@ -86,45 +84,47 @@ class tst_DeviceShell : public QObject
return result;
}
- void test(int maxNumThreads, int numCalls)
+ void test(int numCalls, int maxNumThreads)
{
TestShell shell;
QCOMPARE(shell.state(), DeviceShell::State::Succeeded);
QThreadPool::globalInstance()->setMaxThreadCount(maxNumThreads);
- QList<QByteArray> testArray = testArrays(numCalls);
+ const QList<QByteArray> testArray = testArrays(numCalls);
QElapsedTimer t;
t.start();
- const QList<QByteArray> result
- = mapped<QList>(testArray, [&shell](QByteArray data) -> QByteArray {
- return shell.runInShell({"cat", {}}, data).stdOut;
- }, MapReduceOption::Ordered, QThreadPool::globalInstance());
-
- QCOMPARE(result, testArray);
+ const auto cat = [&shell](const QByteArray &data) {
+ return shell.runInShell({"cat", {}}, data).stdOut;
+ };
+ const QList<QByteArray> results = QtConcurrent::blockingMapped(testArray, cat);
+ QCOMPARE(results, testArray);
qDebug() << "maxThreads:" << maxNumThreads << ", took:" << t.elapsed() / 1000.0
<< "seconds";
}
- void testSleep(QList<int> testData, int nThreads)
+ void testSleep(QList<int> testData, int maxNumThreads)
{
TestShell shell;
QCOMPARE(shell.state(), DeviceShell::State::Succeeded);
- QThreadPool::globalInstance()->setMaxThreadCount(nThreads);
+ QThreadPool::globalInstance()->setMaxThreadCount(maxNumThreads);
QElapsedTimer t;
t.start();
- const auto result = mapped<QList>(testData, [&shell](const int &time) {
+ const auto sleep = [&shell](int time) {
shell.runInShell({"sleep", {QString("%1").arg(time)}});
return 0;
- }, MapReduceOption::Unordered, QThreadPool::globalInstance());
+ };
+ const QList<int> results = QtConcurrent::blockingMapped(testData, sleep);
+ QCOMPARE(results, QList<int>(testData.size(), 0));
- qDebug() << "maxThreads:" << nThreads << ", took:" << t.elapsed() / 1000.0 << "seconds";
+ qDebug() << "maxThreads:" << maxNumThreads << ", took:" << t.elapsed() / 1000.0
+ << "seconds";
}
private slots:
@@ -183,7 +183,7 @@ private slots:
QFETCH(int, numThreads);
QFETCH(int, numIterations);
- test(numThreads, numIterations);
+ test(numIterations, numThreads);
}
void testSleepMulti()
diff --git a/tests/manual/tasktree/main.cpp b/tests/manual/tasktree/main.cpp
index b57481bfc41..4dc6da3fee7 100644
--- a/tests/manual/tasktree/main.cpp
+++ b/tests/manual/tasktree/main.cpp
@@ -18,11 +18,11 @@
using namespace Utils;
// TODO: make tasks cancellable
-static void sleepInThread(QFutureInterface<void> &fi, int seconds, bool reportSuccess)
+static void sleepInThread(QPromise<void> &promise, int seconds, bool reportSuccess)
{
QThread::sleep(seconds);
if (!reportSuccess)
- fi.reportCanceled();
+ promise.future().cancel();
}
int main(int argc, char *argv[])
@@ -155,7 +155,7 @@ int main(int argc, char *argv[])
auto taskItem = [sync = &synchronizer, synchronizerCheckBox](TaskWidget *widget) {
const auto setupHandler = [=](AsyncTask<void> &task) {
- task.setAsyncCallData(sleepInThread, widget->busyTime(), widget->isSuccess());
+ task.setConcurrentCallData(sleepInThread, widget->busyTime(), widget->isSuccess());
if (synchronizerCheckBox->isChecked())
task.setFutureSynchronizer(sync);
widget->setState(State::Running);
diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt
index 3a36658ae4f..7baf81f84bd 100644
--- a/tests/unit/CMakeLists.txt
+++ b/tests/unit/CMakeLists.txt
@@ -30,7 +30,7 @@ if (NOT QT_CREATOR_API_DEFINED)
set(GOOGLETEST_DIR ${CMAKE_CURRENT_LIST_DIR}/unittest/3rdparty/googletest)
find_package(Clang MODULE)
- find_package(Qt5
+ find_package(Qt6
COMPONENTS
Gui Core Core5Compat Widgets Network Qml Concurrent Test Xml MODULE)
find_package(Threads)
diff --git a/tests/unit/unittest/CMakeLists.txt b/tests/unit/unittest/CMakeLists.txt
index fd73f4cb124..ad7858ac912 100644
--- a/tests/unit/unittest/CMakeLists.txt
+++ b/tests/unit/unittest/CMakeLists.txt
@@ -24,7 +24,7 @@ add_qtc_test(unittest GTEST
DEPENDS
Qt::Core Qt::Network Qt::Widgets
Qt::Xml Qt::Concurrent Qt::Qml Qt::Gui
- Qt6Core5Compat QmlJS Sqlite SqliteC
+ Qt::Core5Compat QmlJS Sqlite SqliteC
Googletest
DEFINES
QT_NO_CAST_TO_ASCII
@@ -311,15 +311,6 @@ endif()
extend_qtc_test(unittest DEPENDS Utils CPlusPlus)
extend_qtc_test(unittest
- SOURCES_PREFIX ../../../src/plugins/coreplugin
- DEFINES CORE_STATIC_LIBRARY
- SOURCES
- coreicons.cpp coreicons.h
- find/ifindfilter.cpp find/ifindfilter.h
- locator/ilocatorfilter.cpp locator/ilocatorfilter.h
-)
-
-extend_qtc_test(unittest
CONDITION TARGET qmldomlib
DEPENDS qmldomlib
SOURCES