Compare commits

...

26 Commits

Author SHA1 Message Date
Mike Griese
068159607e Leak the window when it closes on Windows 10 (#15397)
Re: #15384

Basically, when we close a `DesktopWindowXamlSource`, it calls to `Windows_UI_Xaml!DirectUI::MetadataAPI::Reset`, which resets the XAML metadata provider _for the process_. So, closing one DWXS on one thread will force an A/V next time another thread tries to do something like... display a tooltip. Not immediately, but surely soon enough.

This was fixed in Windows 11 by os.2020!5837001. That wasn't backported to Windows 10.

This will cause a ~15MB memory leak PER WINDOW. OBVIOUSLY, this is bad, but it's less bad than crashing.

We're gonna keep using #15384 for other ideas here too.

(cherry picked from commit c589784b54)
Service-Card-Id: 89283538
Service-Version: 1.18
2023-05-22 19:21:39 -05:00
Dustin L. Howett
a5c351c513 Switch the Preview text emoji to one that exists on Win10 (#15381)
(cherry picked from commit 0ee2c74cd4)
Service-Card-Id: 89252873
Service-Version: 1.18
2023-05-18 16:38:03 -05:00
Dustin L. Howett
02eb1eae59 Make the preview text 100% more accurate (#15366)
(cherry picked from commit ce60bf290a)
Service-Card-Id: 89232221
Service-Version: 1.18
2023-05-16 18:05:07 -05:00
Dustin L. Howett
745c4e4f0e Reword the AdjustIndistinguishableColors subhead and perf. note (#15361)
I've changed the wording so that it flows better.

(cherry picked from commit e269945a74)
Service-Card-Id: 89230299
Service-Version: 1.18
2023-05-16 15:05:43 -05:00
Dustin L. Howett
edb55d74f4 Add a fun new preview text in the SUI, enable the cursor (#15363)
Our existing preview text was not very helpful in learning how different
settings impacted the display of text in Terminal.

This new preview text contains:
* Bold text, which is controlled by intenseTextStyle
* Colors
* Emoji
* A cursor, which overlaps a single character to show inversion behavior

(cherry picked from commit fbe45fafb5)
Service-Card-Id: 89230302
Service-Version: 1.18
2023-05-16 15:05:42 -05:00
Dustin L. Howett
521e1318df Reword or remove a bunch of subheadings in the SUI (#15362)
Some of these were reundant, and some didn't feel right when I read
them.

Oh, and I got rid of all of these particularly unhelpful or non-additive
resources:

```
Color Scheme        [                     v ]
Is a color scheme
```

(cherry picked from commit 62766db94d)
Service-Card-Id: 89230304
Service-Version: 1.18
2023-05-16 15:05:40 -05:00
James Holderness
e4a62fb2e1 Add support for horizontal margin sequences (#15084)
This PR introduces two new escapes sequences: `DECLRMM` (Left Right
Margin Mode), which enables the horizontal margin support, and `DECSLRM`
(Set Left and Right Margins), which does exactly what the name suggests.

A whole lot of existing operations have then been updated to take those
horizontal margins into account.

## Detailed Description of the Pull Request / Additional comments

The main complication was in figuring out in what way each operation is
affected, since there's a fair amount of variation.

* When writing text to the buffer, we need to wrap within the horizontal
margins, but only when the cursor is also within the vertical margins,
otherwise we just wrap within the boundaries of the screen.

* Not all cursor movement operations are constrained by the margins, but
for those that are, we clamp within both the vertical and horizontal
margins. But if the cursor is already outside the margins, it is just
clamped at the edges of the screen.

* The `ICH` and `DCH` operations are constrained by the horizontal
margins, but only when inside the vertical margins. And if the cursor is
outside the horizontal margins, these operations have no effect at all.

* The rectangular area operations are clamped within the horizontal
margins when in the origin mode, the same way they're clamped within the
vertical margins.

* The scrolling operations only scroll the area inside both horizontal
and vertical margins. This includes the `IL` and `DL` operations, but
they also won't have any effect at all unless the cursor is already
inside the margin area.

* `CR` returns to the left margin rather than the start of the line,
unless the cursor is already left of that margin, or outside the
vertical margins.

* `LF`, `VT`, `FF`, and `IND` only trigger a scroll at the bottom margin
if the cursor is already inside both vertical and horizontal margins.
The same rules apply to `RI` when triggering a scroll at the top margin.

Another thing worth noting is the change to the `ANSISYSSC` operation.
Since that shares the same escape sequence as `DECSLRM`, they can't both
be active at the same time. However, the latter is only meant to be
active when `DECLRMM` is set, so by default `ANSISYSC` will still work,
but it'll no longer apply once the `DECLRMM` mode is enabled.

## Validation Steps Performed

Thanks to @al20878, these operations have been extensively tested on a
number of DEC terminals and I've manually confirmed our implementation
matches their behavior.

I've also extended some of our existing unit tests to cover at least the
basic margin handling, although not all of the edge cases.

Closes #14876

(cherry picked from commit b00b77a7ac)
Service-Card-Id: 89180228
Service-Version: 1.18
2023-05-15 17:37:56 -05:00
Leonard Hecker
5d1bcd6324 AtlasEngine: Clip box glyphs to their cells (#15343)
Overhangs for box glyphs can produce unsightly effects, where the
antialiased edges of horizontal and vertical lines overlap between
neighboring glyphs and produce "boldened" intersections.
This avoids the issue in most cases by simply clipping the glyph to the
size of a single cell. The downside is that it fails to work well for
custom line heights, etc.

## Validation Steps Performed

* With Cascadia Code, printing ``"`u{2593}`n`u{2593}"`` in pwsh
  doesn't produce a brightened overlap anymore 
* ``"`e#3`u{2502}`n`e#4`u{2502}"`` produces a fat vertical line 

(cherry picked from commit 4628ceb295)
Service-Card-Id: 89213363
Service-Version: 1.18
2023-05-15 17:37:54 -05:00
Mike Griese
6942992d7b Manually pre-evaluate the starting directory when calling elevate-shim (#15286)
_targets #15280_

When ctrl+clicking on a profile, pre-evaluate the starting directory of
that profile, and stash that in the commandline we pass to elevate shim.

So in the case of something like "use parent process directory", we'll
run `elevate-shim new-tab -p {guid} -d "C:\\the path\\of\\terminal\\."`

Closes #15173

---------

Co-authored-by: Leonard Hecker <lhecker@microsoft.com>
(cherry picked from commit 9a4f4abaf2)
Service-Card-Id: 89180220
Service-Version: 1.18
2023-05-15 17:37:51 -05:00
James Holderness
c7bf735167 Add support for LNM (Line Feed/New Line Mode) (#15261)
This PR adds support for the ANSI Line Feed/New Line mode (`LNM`), which
determines whether outputting a linefeed control should also trigger a
carriage return, and whether the `Return` key should generate an `LF` in
addition to `CR`.

## Detailed Description of the Pull Request / Additional comments

In ConHost, there was already a console mode which handled the output
side of things, but I've now also added a `TerminalInput` mode that
controls the behavior of the `Return` key. When `LNM` is set, both the
output and input modes are enabled, and when reset, they're disabled.

If they're not already matching, then `LNM` has no effect, and will be
reported as unknown when queried. This is the typical state for legacy
console applications, which expect a linefeed to trigger a carriage
return, but wouldn't want the `Return` key generating both `CR`+`LF`.

As part of this PR, I've also refactored the `ITerminalApi` interface to
consolidate what I'm now calling the "system" modes: bracketed paste,
auto wrap, and the new line feed mode. This closes another gap between
Terminal and ConHost, so both auto wrap, and line feed mode will now be
supported for conpty pass through.

## Validation Steps Performed

I've added an `LNM` test that checks the escape sequence is triggering
both of the expected mode changes, and added an additional `DECRQM` test
covering the currently implemented standard modes: the new `LNM`, and
the existing `IRM` (which wasn't previously tested). I've also extended
the `DECRQM` private mode test to cover `DECAWM` and Bracketed Paste
(which we also weren't previously testing).

I've manually tested `LNM` in Vttest to confirm the keyboard is working
as expected.

Closes #15167

(cherry picked from commit 3d737214a4)
Service-Card-Id: 89180225
Service-Version: 1.18
2023-05-15 17:37:49 -05:00
Leonard Hecker
6e6777a613 Fix AtlasEngine not being used in the appearance settings (#15355)
`TermControl` cannot change the text rendering engine after its
construction. Fix the issue by deferring the construction until
after we got the initial profile settings.

## Validation Steps Performed
* A line height of 0.5 shows up with overlapping rows 

(cherry picked from commit f6e9f91504)
Service-Card-Id: 89208954
Service-Version: 1.18
2023-05-15 14:34:30 -05:00
Leonard Hecker
622844ab3d AtlasEngine: Fix animated shaders (#15353)
We need to avoid calling `Present1()` with an empty dirty rect, but the
backends are what determines the resulting dirty rect, so we need to
first run the backend code and then decide if we `Present1()` or not.

## Validation Steps Performed
* `Animate_scan.hlsl` shows a smoothly animated line 

(cherry picked from commit 457bc65d7e)
Service-Card-Id: 89208947
Service-Version: 1.18
2023-05-15 14:34:29 -05:00
Leonard Hecker
74665fea72 AtlasEngine: Fix han unification issues (#15358)
This commit ensures that we pass the user's locale to `MapCharacters`.

## Validation Steps Performed
See: https://heistak.github.io/your-code-displays-japanese-wrong/
After modifying the `userLocaleName` to contain `ja-JP`, `zh-CN` and
`zh-TW`, printing "刃直海角骨入" produces the expected, localized result.

(cherry picked from commit 4c3d3d83a5)
Service-Card-Id: 89211793
Service-Version: 1.18
2023-05-15 14:34:28 -05:00
Leonard Hecker
0dbcc5bed1 AtlasEngine: Fix multiple minor issues (#15357)
This commit fixes 3 bugs that I found while working on another feature:
* `GetGlyphIndices` doesn't return an error when the codepoint couldn't
  be found, it simply returns a glyph index of 0.
* `_resetGlyphAtlas` failed to reset the `linear_flat_set` "load" to 0
  which would result in an unbounded memory growth over time.
* `linear_flat_set` was missing move constructors/operators, which
  would've led to crashes, etc., but thankfully we haven't made use
  of these operators yet. But better fix it now than never.

(cherry picked from commit af5a6ea640)
Service-Card-Id: 89210623
Service-Version: 1.18
2023-05-15 14:34:26 -05:00
Leonard Hecker
747999a7e8 Fix DesktopWindowXamlSource related crashes when closing a window (#15338)
XAML/WinUI may pump the event loop internally. One of the functions
that does this right now is `DesktopWindowXamlSource::Close()`.

This is problematic in the previous code, because we'd set `_window`
to `nullptr` before calling `Close()` and so any of the `IslandWindow`
callbacks may be invoked during shutdown, which then try to potentially
access `_window` and end up crashing. This commit fixes the issue by
simply not nulling out the `_window` and calling `Close()` directly.

Furthermore, `NonClientIslandWindow` may directly access WinUI
objects in its message handlers which also crashes.

I've had this happen roughly ~1% of my test exits in a debug build
and every single time on a (artificial) very slow CPU.

## Validation Steps Performed
* Closing a window destroys the rendering instance 

(cherry picked from commit ba39db52d7)
Service-Card-Id: 89191262
Service-Version: 1.18
2023-05-15 14:34:25 -05:00
Mike Griese
11233c5108 Don't even try to MoveContent to the same window you started in (#15325)
Don't go.
Tracked in #14957

(cherry picked from commit 1324a0148a)
Service-Card-Id: 89180217
Service-Version: 1.18
2023-05-12 17:51:14 -05:00
Mike Griese
704be8021c Don't dismiss the command palette when the new tab menu closes (#15340)
Transient UIs are hard.

Regressed in #15077.

Closes #15305

(cherry picked from commit 1bf2fcb6e0)
Service-Card-Id: 89193705
Service-Version: 1.18
2023-05-12 17:51:13 -05:00
Leonard Hecker
9909b890eb Make path-string conversions cheaper (#15332)
`native()` returns a `const std::wstring&`, whereas `wstring()`
returns a copy. Use the former to make path conversions cheaper.

(cherry picked from commit 95944e5939)
Service-Card-Id: 89194127
Service-Version: 1.18
2023-05-12 17:51:12 -05:00
Leonard Hecker
7e5922f3d3 Fix WindowEmperor exiting too early (#15337)
`WindowEmperor` would exit as soon as the last window would enter
`RundownForExit()`, which is too early and triggers leak checks.
This commit splits up the shutdown up into deregistering the window from
the list of windows and into actually decrementing the window count.

Closes #15306

## Validation Steps Performed
* D2D leak warnings seem to disappear 

(cherry picked from commit 488de2d42c)
Service-Card-Id: 89180861
Service-Version: 1.18
2023-05-12 17:51:11 -05:00
Leonard Hecker
8bc192db65 Avoid recreating the bell MediaPlayer every time (#15333)
We don't need to recreate the `MediaPlayer` to avoid the influence of
media keys if we simply opt out of media key controls.

## Validation Steps Performed
* Set a random .wav as the bell sound
* Bell is audible 
* Media keys have no effect while the sound plays 

(cherry picked from commit bf8ef638b7)
Service-Card-Id: 89180446
Service-Version: 1.18
2023-05-12 17:51:10 -05:00
Leonard Hecker
6504066441 Fix race conditions in ControlCore (#15334)
`ControlCore` contained two bugs:
* Race condition on access of the 3 throttled funcs which may now
  be `reset()` during tear out
* The `ScrollPositionChanged` event emitter was written incorrectly
  and would emit the event from the background thread without
  throttling during tear out

(cherry picked from commit 6a26fd68c4)
Service-Card-Id: 89180582
Service-Version: 1.18
2023-05-12 17:51:09 -05:00
Dustin L. Howett
7c69fe3d0b 1.18: build PGO using Preview branding 2023-05-12 17:50:32 -05:00
Dustin L. Howett
08d395514f PGO train 1.18 separately 2023-05-12 14:27:32 -05:00
Mike Griese
5c6e626bee Restore the tab padding (#15339)
Honestly, I don't really know where it regressed. There isn't time for
me to go digging.

See also
* #15313
* #15164

Closes #15326

(cherry picked from commit d0f66b9668)
Service-Card-Id: 89193703
Service-Version: 1.18
2023-05-12 14:04:41 -05:00
Mike Griese
8d2447bce4 Use a "virtual CWD" for each terminal window (#15280)
Before process model v3, each Terminal window was running in its own process, each with its own CWD. This allowed `startingDirectory: .` to work relative to the terminal's own CWD. However, now all windows share the same process, so there can only be one CWD. That's not great - folks who right-click "open in terminal", then "Use parent process directory" are only ever going to be able to use the CWD of the _first_ terminal opened.

This PR remedies this issue, with a theory we had for another issue. Essentially, we'll give each Terminal window a "virtual" CWD. The Terminal isn't actually in that CWD, the terminal is in `system32`. This also will prevent the Terminal from locking the directory it was originally opened in.

* Closes #5506
* There wasn't a 1.18 issue for "Use parent process directory is broken" yet, presumably selfhosters aren't using that feature
* Related to #14957

Many more notes on this topic in https://github.com/microsoft/terminal/issues/4637#issuecomment-1531979200

> **Warning**
> ## Breaking change‼️

This will break a profile like

```json
{
    "commandline": "media-test.exe",
    "name": "Use CWD for media-test",
    "startingDirectory": "."
},
```

if the user right-clicks "open in terminal", then attempts to open that profile. There's some theoretical work we could do in a follow up to fix this, but I'm inclined to say that relative paths for `commandline`s were already dangerous and should have been avoided.

(cherry picked from commit 5c08a86c49)
Service-Card-Id: 89180224
Service-Version: 1.18
2023-05-12 14:04:40 -05:00
Leonard Hecker
e80661f8b1 Fix theoretical new-tab file drop crash (#15336)
After retrieving the items via `GetStorageItemsAsync()` inside a try
clause it fails to check if the pointer is actually non-null.
Apart from this this commit fixes the unsafe use of `this` by properly
using `get_weak()`. Finally it allows >1 paths to be dropped.

## Validation Steps Performed
* Dropping >1 file works 
* Dropping >1 directory works 

(cherry picked from commit 99abb2a6b5)
Service-Card-Id: 89193984
Service-Version: 1.18
2023-05-12 14:04:39 -05:00
74 changed files with 1835 additions and 809 deletions

View File

@@ -9,6 +9,7 @@ ABCDEFGHIJ
abcdefghijk
ABCDEFGHIJKLMNO
abcdefghijklmnop
ABCDEFGHIJKLMNOPQRS
ABCDEFGHIJKLMNOPQRST
ABCG
ABE
@@ -21,9 +22,12 @@ BBGGRR
efg
EFG
EFGh
KLMNOQQQQQQQQQQ
QQQQQQQQQQABCDEFGHIJ
QQQQQQQQQQABCDEFGHIJKLMNOPQRS
QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQ
QQQQQQQQQQABCDEFGHIJKLMNOPQRSTQQQQQQQQQQ
QQQQQQQQQQABCDEFGHIJPQRST
QQQQQQQQQQABCDEFGHIJPQRSTQQQQQQQQQQ
qrstuvwxyz
qwerty

View File

@@ -58,4 +58,7 @@ Copy-Item "build\Helix\EnsureMachineState.ps1" "$payloadDir"
# Extract the unpackaged distribution of Windows Terminal to the payload directory,
# where it will create a subdirectory named terminal-0.0.1.0
# This is referenced in TerminalApp.cs later as part of the test harness.
& tar -x -v -f "$repoDirectory\Artifacts\$ArtifactName\unpackaged\WindowsTerminalDev_0.0.1.0_x64.zip" -C "$payloadDir"
## 1.18 HAX: Preview Build
#& tar -x -v -f "$repoDirectory\Artifacts\$ArtifactName\unpackaged\Microsoft.WindowsTerminal_1.0.0.0_x64.zip" -C "$payloadDir"
& tar -x -v -f "$repoDirectory\Artifacts\$ArtifactName\unpackaged\Microsoft.WindowsTerminalPreview_0.5.0.0_x64.zip" -C "$payloadDir"
Move-Item "$payloadDir\terminal-*" "$payloadDir\terminal-0.0.1.0"

View File

@@ -9,7 +9,7 @@
<PropertyGroup>
<!-- Optional, defaults to main. Name of the branch which will be used for calculating branch point. -->
<PGOBranch>main</PGOBranch>
<PGOBranch>release-1.18</PGOBranch>
<!-- Mandatory. Name of the NuGet package which will contain PGO databases for consumption by build system. -->
<PGOPackageName>Microsoft.Internal.Windows.Terminal.PGODatabase</PGOPackageName>

View File

@@ -18,6 +18,7 @@ stages:
- template: ./templates/build-console-pgo.yml
parameters:
platform: x64
additionalBuildArguments: "/p:WindowsTerminalOfficialBuild=true /p:WindowsTerminalBranding=Preview"
- stage: Publish_PGO_Databases
displayName: Publish PGO databases
dependsOn: ['Build_x64']

View File

@@ -10,7 +10,7 @@ namespace Microsoft.Terminal.Remoting
CommandlineArgs(String[] args, String cwd, UInt32 showWindowCommand);
String[] Commandline { get; set; };
String CurrentDirectory();
String CurrentDirectory { get; };
UInt32 ShowWindowCommand { get; };
};

View File

@@ -41,9 +41,8 @@ namespace
// Make a temporary monarch CLSID based on the unpackaged install root
std::filesystem::path modulePath{ wil::GetModuleFileNameW<std::wstring>(wil::GetModuleInstanceHandle()) };
modulePath.remove_filename();
std::wstring pathRootAsString{ modulePath.wstring() };
return Utils::CreateV5Uuid(processRootHashedGuidBase, std::as_bytes(std::span{ pathRootAsString }));
return Utils::CreateV5Uuid(processRootHashedGuidBase, std::as_bytes(std::span{ modulePath.native() }));
}();
return processRootHashedGuid;
}

View File

@@ -45,7 +45,7 @@
Color="{ThemeResource SystemErrorTextColor}" />
<!-- Suppress top padding -->
<Thickness x:Key="TabViewHeaderPadding">9,0,5,0</Thickness>
<Thickness x:Key="TabViewHeaderPadding">0,0,0,0</Thickness>
<Thickness x:Key="TabViewItemBorderThickness">1,1,1,0</Thickness>

View File

@@ -146,7 +146,7 @@
-->
<x:Double x:Key="CaptionButtonHeightWindowed">40.0</x:Double>
<!-- 32 + (1 to compensate for GH#10746) + (-1 for GH#15164) -->
<x:Double x:Key="CaptionButtonHeightMaximized">33.0</x:Double>
<x:Double x:Key="CaptionButtonHeightMaximized">32.0</x:Double>
<Style x:Key="CaptionButton"
TargetType="Button">

View File

@@ -1121,19 +1121,15 @@ winrt::fire_and_forget Pane::_playBellSound(winrt::Windows::Foundation::Uri uri)
co_await wil::resume_foreground(_root.Dispatcher());
if (auto pane{ weakThis.lock() })
{
// BODGY
// GH#12258: We learned that if you leave the MediaPlayer open, and
// press the media keys (like play/pause), then the OS will _replay the
// bell_. So we have to re-create the MediaPlayer each time we want to
// play the bell, to make sure a subsequent play doesn't come through
// and reactivate the old one.
if (!_bellPlayer)
if (!_bellPlayerCreated)
{
// The MediaPlayer might not exist on Windows N SKU.
try
{
_bellPlayerCreated = true;
_bellPlayer = winrt::Windows::Media::Playback::MediaPlayer();
// GH#12258: The media keys (like play/pause) should have no effect on our bell sound.
_bellPlayer.CommandManager().IsEnabled(false);
}
CATCH_LOG();
}
@@ -1143,27 +1139,6 @@ winrt::fire_and_forget Pane::_playBellSound(winrt::Windows::Foundation::Uri uri)
const auto item{ winrt::Windows::Media::Playback::MediaPlaybackItem(source) };
_bellPlayer.Source(item);
_bellPlayer.Play();
// This lambda will clean up the bell player when we're done with it.
auto weakThis2{ weak_from_this() };
_mediaEndedRevoker = _bellPlayer.MediaEnded(winrt::auto_revoke, [weakThis2](auto&&, auto&&) {
if (auto self{ weakThis2.lock() })
{
if (self->_bellPlayer)
{
// We need to make sure clear out the current track
// that's being played, again, so that the system can't
// come through and replay it. In testing, we needed to
// do this, closing the MediaPlayer alone wasn't good
// enough.
self->_bellPlayer.Pause();
self->_bellPlayer.Source(nullptr);
self->_bellPlayer.Close();
}
self->_mediaEndedRevoker.revoke();
self->_bellPlayer = nullptr;
}
});
}
}
}
@@ -1269,14 +1244,14 @@ void Pane::Shutdown()
// Clear out our media player callbacks, and stop any playing media. This
// will prevent the callback from being triggered after we've closed, and
// also make sure that our sound stops when we're closed.
_mediaEndedRevoker.revoke();
if (_bellPlayer)
{
_bellPlayer.Pause();
_bellPlayer.Source(nullptr);
_bellPlayer.Close();
_bellPlayer = nullptr;
_bellPlayerCreated = false;
}
_bellPlayer = nullptr;
if (_IsLeaf())
{

View File

@@ -265,7 +265,7 @@ private:
bool _zoomed{ false };
winrt::Windows::Media::Playback::MediaPlayer _bellPlayer{ nullptr };
winrt::Windows::Media::Playback::MediaPlayer::MediaEnded_revoker _mediaEndedRevoker;
bool _bellPlayerCreated{ false };
bool _IsLeaf() const noexcept;
bool _HasFocusedChild() const noexcept;

View File

@@ -238,12 +238,7 @@ namespace winrt::TerminalApp::implementation
page->_OpenNewTerminalViaDropdown(NewTerminalArgs());
}
});
_newTabButton.Drop([weakThis{ get_weak() }](const Windows::Foundation::IInspectable&, winrt::Windows::UI::Xaml::DragEventArgs e) {
if (auto page{ weakThis.get() })
{
page->NewTerminalByDrop(e);
}
});
_newTabButton.Drop({ get_weak(), &TerminalPage::_NewTerminalByDrop });
_tabView.SelectionChanged({ this, &TerminalPage::_OnTabSelectionChanged });
_tabView.TabCloseRequested({ this, &TerminalPage::_OnTabCloseRequested });
_tabView.TabItemsChanged({ this, &TerminalPage::_OnTabItemsChanged });
@@ -402,35 +397,46 @@ namespace winrt::TerminalApp::implementation
}
}
winrt::fire_and_forget TerminalPage::NewTerminalByDrop(winrt::Windows::UI::Xaml::DragEventArgs& e)
winrt::fire_and_forget TerminalPage::_NewTerminalByDrop(const Windows::Foundation::IInspectable&, winrt::Windows::UI::Xaml::DragEventArgs e)
try
{
Windows::Foundation::Collections::IVectorView<Windows::Storage::IStorageItem> items;
try
const auto data = e.DataView();
if (!data.Contains(StandardDataFormats::StorageItems()))
{
items = co_await e.DataView().GetStorageItemsAsync();
co_return;
}
CATCH_LOG();
if (items.Size() == 1)
const auto weakThis = get_weak();
const auto items = co_await data.GetStorageItemsAsync();
const auto strongThis = weakThis.get();
if (!strongThis)
{
std::filesystem::path path(items.GetAt(0).Path().c_str());
co_return;
}
TraceLoggingWrite(
g_hTerminalAppProvider,
"NewTabByDragDrop",
TraceLoggingDescription("Event emitted when the user drag&drops onto the new tab button"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
for (const auto& item : items)
{
auto directory = item.Path();
std::filesystem::path path(std::wstring_view{ directory });
if (!std::filesystem::is_directory(path))
{
path = path.parent_path();
directory = winrt::hstring{ path.parent_path().native() };
}
NewTerminalArgs args;
args.StartingDirectory(winrt::hstring{ path.wstring() });
this->_OpenNewTerminalViaDropdown(args);
TraceLoggingWrite(
g_hTerminalAppProvider,
"NewTabByDragDrop",
TraceLoggingDescription("Event emitted when the user drag&drops onto the new tab button"),
TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES),
TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage));
args.StartingDirectory(directory);
_OpenNewTerminalViaDropdown(args);
}
}
CATCH_LOG()
// Method Description:
// - This method is called once command palette action was chosen for dispatching
@@ -551,27 +557,28 @@ namespace winrt::TerminalApp::implementation
// Handle it on a subsequent pass of the UI thread.
co_await wil::resume_foreground(Dispatcher(), CoreDispatcherPriority::Normal);
// If the caller provided a CWD, switch to that directory, then switch
// If the caller provided a CWD, "switch" to that directory, then switch
// back once we're done. This looks weird though, because we have to set
// up the scope_exit _first_. We'll release the scope_exit if we don't
// actually need it.
auto originalCwd{ wil::GetCurrentDirectoryW<std::wstring>() };
auto restoreCwd = wil::scope_exit([&originalCwd]() {
auto originalVirtualCwd{ _WindowProperties.VirtualWorkingDirectory() };
auto restoreCwd = wil::scope_exit([&originalVirtualCwd, this]() {
// ignore errors, we'll just power on through. We'd rather do
// something rather than fail silently if the directory doesn't
// actually exist.
LOG_IF_WIN32_BOOL_FALSE(SetCurrentDirectory(originalCwd.c_str()));
_WindowProperties.VirtualWorkingDirectory(originalVirtualCwd);
});
if (cwd.empty())
{
// We didn't actually need to change the virtual CWD, so we don't
// need to restore it
restoreCwd.release();
}
else
{
// ignore errors, we'll just power on through. We'd rather do
// something rather than fail silently if the directory doesn't
// actually exist.
LOG_IF_WIN32_BOOL_FALSE(SetCurrentDirectory(cwd.c_str()));
_WindowProperties.VirtualWorkingDirectory(cwd);
}
if (auto page{ weakThis.get() })
@@ -851,7 +858,10 @@ namespace winrt::TerminalApp::implementation
});
// Necessary for fly-out sub items to get focus on a tab before collapsing. Related to #15049
newTabFlyout.Closing([this](auto&&, auto&&) {
_FocusCurrentTab(true);
if (!_commandPaletteIs(Visibility::Visible))
{
_FocusCurrentTab(true);
}
});
_newTabButton.Flyout(newTabFlyout);
}
@@ -1113,6 +1123,7 @@ namespace winrt::TerminalApp::implementation
if (profile)
{
newTerminalArgs.Profile(::Microsoft::Console::Utils::GuidToString(profile.Guid()));
newTerminalArgs.StartingDirectory(_evaluatePathForCwd(profile.EvaluatedStartingDirectory()));
}
}
@@ -1158,6 +1169,11 @@ namespace winrt::TerminalApp::implementation
}
}
std::wstring TerminalPage::_evaluatePathForCwd(const std::wstring_view path)
{
return Utils::EvaluateStartingDirectory(_WindowProperties.VirtualWorkingDirectory(), path);
}
// Method Description:
// - Creates a new connection based on the profile settings
// Arguments:
@@ -1188,7 +1204,7 @@ namespace winrt::TerminalApp::implementation
connection = TerminalConnection::ConptyConnection{};
}
auto valueSet = TerminalConnection::ConptyConnection::CreateSettings(azBridgePath.wstring(),
auto valueSet = TerminalConnection::ConptyConnection::CreateSettings(azBridgePath.native(),
L".",
L"Azure",
nullptr,
@@ -1226,16 +1242,7 @@ namespace winrt::TerminalApp::implementation
// construction, because the connection might not spawn the child
// process until later, on another thread, after we've already
// restored the CWD to its original value.
auto newWorkingDirectory{ settings.StartingDirectory() };
if (newWorkingDirectory.size() == 0 || newWorkingDirectory.size() == 1 &&
!(newWorkingDirectory[0] == L'~' || newWorkingDirectory[0] == L'/'))
{ // We only want to resolve the new WD against the CWD if it doesn't look like a Linux path (see GH#592)
auto cwdString{ wil::GetCurrentDirectoryW<std::wstring>() };
std::filesystem::path cwd{ cwdString };
cwd /= settings.StartingDirectory().c_str();
newWorkingDirectory = winrt::hstring{ cwd.wstring() };
}
auto newWorkingDirectory{ _evaluatePathForCwd(settings.StartingDirectory()) };
auto conhostConn = TerminalConnection::ConptyConnection();
auto valueSet = TerminalConnection::ConptyConnection::CreateSettings(settings.Commandline(),
newWorkingDirectory,
@@ -1444,9 +1451,10 @@ namespace winrt::TerminalApp::implementation
return;
}
if (const auto p = CommandPaletteElement(); p && p.Visibility() == Visibility::Visible && cmd.ActionAndArgs().Action() != ShortcutAction::ToggleCommandPalette)
if (_commandPaletteIs(Visibility::Visible) &&
cmd.ActionAndArgs().Action() != ShortcutAction::ToggleCommandPalette)
{
p.Visibility(Visibility::Collapsed);
CommandPaletteElement().Visibility(Visibility::Collapsed);
}
// Let's assume the user has bound the dead key "^" to a sendInput command that sends "b".
@@ -1778,6 +1786,11 @@ namespace winrt::TerminalApp::implementation
return _loadCommandPaletteSlowPath();
}
bool TerminalPage::_commandPaletteIs(WUX::Visibility visibility)
{
const auto p = CommandPaletteElement();
return p && p.Visibility() == visibility;
}
CommandPalette TerminalPage::_loadCommandPaletteSlowPath()
{
@@ -1789,7 +1802,7 @@ namespace winrt::TerminalApp::implementation
// When the visibility of the command palette changes to "collapsed",
// the palette has been closed. Toss focus back to the currently active control.
p.RegisterPropertyChangedCallback(UIElement::VisibilityProperty(), [this](auto&&, auto&&) {
if (CommandPaletteElement().Visibility() == Visibility::Collapsed)
if (_commandPaletteIs(Visibility::Collapsed))
{
_FocusActiveControl(nullptr, nullptr);
}
@@ -2077,6 +2090,13 @@ namespace winrt::TerminalApp::implementation
const auto windowId{ args.Window() };
if (!windowId.empty())
{
// if the windowId is the same as our name, do nothing
if (windowId == WindowProperties().WindowName() ||
windowId == winrt::to_hstring(WindowProperties().WindowId()))
{
return true;
}
if (const auto terminalTab{ _GetFocusedTabImpl() })
{
auto startupActions = terminalTab->BuildStartupActions(true);
@@ -2756,9 +2776,8 @@ namespace winrt::TerminalApp::implementation
// Arguments:
// - sender (not used)
// - args: the arguments specifying how to set the display status to ShowWindow for our window handle
winrt::fire_and_forget TerminalPage::_ShowWindowChangedHandler(const IInspectable /*sender*/, const Microsoft::Terminal::Control::ShowWindowArgs args)
void TerminalPage::_ShowWindowChangedHandler(const IInspectable& /*sender*/, const Microsoft::Terminal::Control::ShowWindowArgs args)
{
co_await resume_foreground(Dispatcher());
_ShowWindowChangedHandlers(*this, args);
}
@@ -4236,6 +4255,9 @@ namespace winrt::TerminalApp::implementation
// whatever the default profile's GUID is.
newTerminalArgs.Profile(::Microsoft::Console::Utils::GuidToString(profile.Guid()));
newTerminalArgs.StartingDirectory(_evaluatePathForCwd(controlSettings.DefaultSettings().StartingDirectory()));
_OpenElevatedWT(newTerminalArgs);
return true;
}

View File

@@ -106,8 +106,6 @@ namespace winrt::TerminalApp::implementation
void HandoffToElevated(const Microsoft::Terminal::Settings::Model::CascadiaSettings& settings);
Microsoft::Terminal::Settings::Model::WindowLayout GetWindowLayout();
winrt::fire_and_forget NewTerminalByDrop(winrt::Windows::UI::Xaml::DragEventArgs& e);
hstring Title();
void TitlebarClicked();
@@ -275,7 +273,11 @@ namespace winrt::TerminalApp::implementation
winrt::Microsoft::Terminal::TerminalConnection::ConptyConnection::NewConnection_revoker _newConnectionRevoker;
winrt::fire_and_forget _NewTerminalByDrop(const Windows::Foundation::IInspectable&, winrt::Windows::UI::Xaml::DragEventArgs e);
__declspec(noinline) CommandPalette _loadCommandPaletteSlowPath();
bool _commandPaletteIs(winrt::Windows::UI::Xaml::Visibility visibility);
winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::UI::Xaml::Controls::ContentDialogResult> _ShowDialogHelper(const std::wstring_view& name);
void _ShowAboutDialog();
@@ -293,6 +295,9 @@ namespace winrt::TerminalApp::implementation
void _OpenNewTabDropdown();
HRESULT _OpenNewTab(const Microsoft::Terminal::Settings::Model::NewTerminalArgs& newTerminalArgs, winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection existingConnection = nullptr);
void _CreateNewTabFromPane(std::shared_ptr<Pane> pane, uint32_t insertPosition = -1);
std::wstring _evaluatePathForCwd(std::wstring_view path);
winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _CreateConnectionFromSettings(Microsoft::Terminal::Settings::Model::Profile profile, Microsoft::Terminal::Settings::Model::TerminalSettings settings, const bool inheritCursor);
winrt::Microsoft::Terminal::TerminalConnection::ITerminalConnection _duplicateConnectionForRestart(std::shared_ptr<Pane> pane);
void _restartPaneConnection(const std::shared_ptr<Pane>& pane);
@@ -505,7 +510,7 @@ namespace winrt::TerminalApp::implementation
void _updateAllTabCloseButtons(const winrt::TerminalApp::TabBase& focusedTab);
void _updatePaneResources(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme);
winrt::fire_and_forget _ShowWindowChangedHandler(const IInspectable sender, const winrt::Microsoft::Terminal::Control::ShowWindowArgs args);
void _ShowWindowChangedHandler(const IInspectable& sender, const winrt::Microsoft::Terminal::Control::ShowWindowArgs args);
winrt::fire_and_forget _windowPropertyChanged(const IInspectable& sender, const winrt::Windows::UI::Xaml::Data::PropertyChangedEventArgs& args);
void _onTabDragStarting(const winrt::Microsoft::UI::Xaml::Controls::TabView& sender, const winrt::Microsoft::UI::Xaml::Controls::TabViewTabDragStartingEventArgs& e);

View File

@@ -51,6 +51,8 @@ namespace TerminalApp
String WindowNameForDisplay { get; };
String WindowIdForDisplay { get; };
String VirtualWorkingDirectory { get; set; };
Boolean IsQuakeWindow();
};

View File

@@ -1019,11 +1019,14 @@ namespace winrt::TerminalApp::implementation
// returned.
// Arguments:
// - args: an array of strings to process as a commandline. These args can contain spaces
// - cwd: The CWD that this window should treat as its own "virtual" CWD
// Return Value:
// - the result of the first command who's parsing returned a non-zero code,
// or 0. (see TerminalWindow::_ParseArgs)
int32_t TerminalWindow::SetStartupCommandline(array_view<const winrt::hstring> args)
int32_t TerminalWindow::SetStartupCommandline(array_view<const winrt::hstring> args, winrt::hstring cwd)
{
_WindowProperties->SetInitialCwd(std::move(cwd));
// This is called in AppHost::ctor(), before we've created the window
// (or called TerminalWindow::Initialize)
const auto result = _appArgs.ParseArgs(args);
@@ -1345,6 +1348,7 @@ namespace winrt::TerminalApp::implementation
CATCH_LOG();
}
}
uint64_t WindowProperties::WindowId() const noexcept
{
return _WindowId;

View File

@@ -48,8 +48,14 @@ namespace winrt::TerminalApp::implementation
winrt::hstring WindowNameForDisplay() const noexcept;
bool IsQuakeWindow() const noexcept;
WINRT_OBSERVABLE_PROPERTY(winrt::hstring, VirtualWorkingDirectory, _PropertyChangedHandlers, L"");
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
public:
// Used for setting the initial CWD, before we have XAML set up for property change notifications.
void SetInitialCwd(winrt::hstring cwd) { _VirtualWorkingDirectory = std::move(cwd); };
private:
winrt::hstring _WindowName{};
uint64_t _WindowId{ 0 };
@@ -71,7 +77,7 @@ namespace winrt::TerminalApp::implementation
bool HasCommandlineArguments() const noexcept;
int32_t SetStartupCommandline(array_view<const winrt::hstring> actions);
int32_t SetStartupCommandline(array_view<const winrt::hstring> actions, winrt::hstring cwd);
void SetStartupContent(const winrt::hstring& content, const Windows::Foundation::IReference<Windows::Foundation::Rect>& contentBounds);
int32_t ExecuteCommandline(array_view<const winrt::hstring> actions, const winrt::hstring& cwd);
void SetSettingsStartupArgs(const std::vector<winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs>& actions);

View File

@@ -53,7 +53,7 @@ namespace TerminalApp
Boolean HasCommandlineArguments();
Int32 SetStartupCommandline(String[] commands);
Int32 SetStartupCommandline(String[] commands, String cwd);
void SetStartupContent(String json, Windows.Foundation.IReference<Windows.Foundation.Rect> bounds);
Int32 ExecuteCommandline(String[] commands, String cwd);
String ParseCommandlineMessage { get; };

View File

@@ -82,9 +82,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_actualFont{ DEFAULT_FONT_FACE, 0, DEFAULT_FONT_WEIGHT, { 0, DEFAULT_FONT_SIZE }, CP_UTF8, false }
{
_settings = winrt::make_self<implementation::ControlSettings>(settings, unfocusedAppearance);
_terminal = std::make_shared<::Microsoft::Terminal::Core::Terminal>();
_setupDispatcherAndCallbacks();
Connection(connection);
_terminal->SetWriteInputCallback([this](std::wstring_view wstr) {
@@ -137,17 +138,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_renderer->SetBackgroundColorChangedCallback([this]() { _rendererBackgroundColorChanged(); });
_renderer->SetFrameColorChangedCallback([this]() { _rendererTabColorChanged(); });
_renderer->SetRendererEnteredErrorStateCallback([weakThis = get_weak()]() {
if (auto strongThis{ weakThis.get() })
{
strongThis->_RendererEnteredErrorStateHandlers(*strongThis, nullptr);
}
});
_renderer->SetRendererEnteredErrorStateCallback([this]() { _RendererEnteredErrorStateHandlers(nullptr, nullptr); });
THROW_IF_FAILED(localPointerToThread->Initialize(_renderer.get()));
}
_setupDispatcherAndCallbacks();
UpdateSettings(settings, unfocusedAppearance);
}
@@ -179,7 +173,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// need to hop across the process boundary every time text is output.
// We can throttle this to once every 8ms, which will get us out of
// the way of the main output & rendering threads.
_tsfTryRedrawCanvas = std::make_shared<ThrottledFuncTrailing<>>(
const auto shared = _shared.lock();
shared->tsfTryRedrawCanvas = std::make_shared<ThrottledFuncTrailing<>>(
_dispatcher,
TsfRedrawInterval,
[weakThis = get_weak()]() {
@@ -191,7 +186,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// NOTE: Calling UpdatePatternLocations from a background
// thread is a workaround for us to hit GH#12607 less often.
_updatePatternLocations = std::make_unique<til::throttled_func_trailing<>>(
shared->updatePatternLocations = std::make_unique<til::throttled_func_trailing<>>(
UpdatePatternLocationsInterval,
[weakTerminal = std::weak_ptr{ _terminal }]() {
if (const auto t = weakTerminal.lock())
@@ -201,7 +196,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
});
_updateScrollBar = std::make_shared<ThrottledFuncTrailing<Control::ScrollPositionChangedArgs>>(
shared->updateScrollBar = std::make_shared<ThrottledFuncTrailing<Control::ScrollPositionChangedArgs>>(
_dispatcher,
ScrollBarUpdateInterval,
[weakThis = get_weak()](const auto& update) {
@@ -231,9 +226,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Clear out any throttled funcs that we had wired up to run on this UI
// thread. These will be recreated in _setupDispatcherAndCallbacks, when
// we're re-attached to a new control (on a possibly new UI thread).
_tsfTryRedrawCanvas.reset();
_updatePatternLocations.reset();
_updateScrollBar.reset();
const auto shared = _shared.lock();
shared->tsfTryRedrawCanvas.reset();
shared->updatePatternLocations.reset();
shared->updateScrollBar.reset();
}
void ControlCore::AttachToNewControl(const Microsoft::Terminal::Control::IKeyBindings& keyBindings)
@@ -318,7 +314,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{ // scope for terminalLock
auto terminalLock = _terminal->LockForWriting();
if (_initializedTerminal)
if (_initializedTerminal.load(std::memory_order_relaxed))
{
return false;
}
@@ -403,7 +399,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
THROW_IF_FAILED(_renderEngine->Enable());
_initializedTerminal = true;
_initializedTerminal.store(true, std::memory_order_relaxed);
} // scope for TerminalLock
// Start the connection outside of lock, because it could
@@ -423,7 +419,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// - <none>
void ControlCore::EnablePainting()
{
if (_initializedTerminal)
if (_initializedTerminal.load(std::memory_order_relaxed))
{
_renderer->EnablePainting();
}
@@ -656,9 +652,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// itself - it was initiated by the mouse wheel, or the scrollbar.
_terminal->UserScrollViewport(viewTop);
if (_updatePatternLocations)
const auto shared = _shared.lock_shared();
if (shared->updatePatternLocations)
{
(*_updatePatternLocations)();
(*shared->updatePatternLocations)();
}
}
@@ -825,7 +822,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Update the terminal core with its new Core settings
_terminal->UpdateSettings(*_settings);
if (!_initializedTerminal)
if (!_initializedTerminal.load(std::memory_order_relaxed))
{
// If we haven't initialized, there's no point in continuing.
// Initialization will handle the renderer settings.
@@ -1434,10 +1431,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
const int viewHeight,
const int bufferSize)
{
if (!_initializedTerminal)
if (!_initializedTerminal.load(std::memory_order_relaxed))
{
return;
}
// Clear the regex pattern tree so the renderer does not try to render them while scrolling
// We're **NOT** taking the lock here unlike _scrollbarChangeHandler because
// we are already under lock (since this usually happens as a result of writing).
@@ -1448,20 +1446,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation
auto update{ winrt::make<ScrollPositionChangedArgs>(viewTop,
viewHeight,
bufferSize) };
if (!_inUnitTests && _updateScrollBar)
{
_updateScrollBar->Run(update);
}
else
if (_inUnitTests) [[unlikely]]
{
_ScrollPositionChangedHandlers(*this, update);
}
// Additionally, start the throttled update of where our links are.
if (_updatePatternLocations)
else
{
(*_updatePatternLocations)();
const auto shared = _shared.lock_shared();
if (shared->updateScrollBar)
{
shared->updateScrollBar->Run(update);
}
}
}
@@ -1469,9 +1465,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
// When the buffer's cursor moves, start the throttled func to
// eventually dispatch a CursorPositionChanged event.
if (_tsfTryRedrawCanvas)
const auto shared = _shared.lock_shared();
if (shared->tsfTryRedrawCanvas)
{
_tsfTryRedrawCanvas->Run();
shared->tsfTryRedrawCanvas->Run();
}
}
@@ -1482,11 +1479,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void ControlCore::_terminalShowWindowChanged(bool showOrHide)
{
if (_initializedTerminal)
{
auto showWindow = winrt::make_self<implementation::ShowWindowArgs>(showOrHide);
_ShowWindowChangedHandlers(*this, *showWindow);
}
auto showWindow = winrt::make_self<implementation::ShowWindowArgs>(showOrHide);
_ShowWindowChangedHandlers(*this, *showWindow);
}
// Method Description:
@@ -1617,6 +1611,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
const auto weakThis{ get_weak() };
// Concurrent read of _dispatcher is safe, because Detach() calls WaitForPaintCompletionAndDisable()
// which blocks until this call returns. _dispatcher will only be changed afterwards.
co_await wil::resume_foreground(_dispatcher);
if (auto core{ weakThis.get() })
@@ -1686,7 +1682,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
Core::Point ControlCore::CursorPosition() const
{
// If we haven't been initialized yet, then fake it.
if (!_initializedTerminal)
if (!_initializedTerminal.load(std::memory_order_relaxed))
{
return { 0, 0 };
}
@@ -1805,9 +1801,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_terminal->Write(hstr);
// Start the throttled update of where our hyperlinks are.
if (_updatePatternLocations)
const auto shared = _shared.lock_shared();
if (shared->updatePatternLocations)
{
(*_updatePatternLocations)();
(*shared->updatePatternLocations)();
}
}
catch (...)
@@ -2016,7 +2013,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// - <none>
void ControlCore::WindowVisibilityChanged(const bool showOrHide)
{
if (_initializedTerminal)
if (_initializedTerminal.load(std::memory_order_relaxed))
{
// show is true, hide is false
if (auto conpty{ _connection.try_as<TerminalConnection::ConptyConnection>() })

View File

@@ -266,7 +266,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// clang-format on
private:
bool _initializedTerminal{ false };
struct SharedState
{
std::shared_ptr<ThrottledFuncTrailing<>> tsfTryRedrawCanvas;
std::unique_ptr<til::throttled_func_trailing<>> updatePatternLocations;
std::shared_ptr<ThrottledFuncTrailing<Control::ScrollPositionChangedArgs>> updateScrollBar;
};
std::atomic<bool> _initializedTerminal{ false };
bool _closing{ false };
TerminalConnection::ITerminalConnection _connection{ nullptr };
@@ -313,9 +320,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
uint64_t _owningHwnd{ 0 };
winrt::Windows::System::DispatcherQueue _dispatcher{ nullptr };
std::shared_ptr<ThrottledFuncTrailing<>> _tsfTryRedrawCanvas;
std::unique_ptr<til::throttled_func_trailing<>> _updatePatternLocations;
std::shared_ptr<ThrottledFuncTrailing<Control::ScrollPositionChangedArgs>> _updateScrollBar;
til::shared_mutex<SharedState> _shared;
til::point _contextMenuBufferPosition{ 0, 0 };

View File

@@ -149,26 +149,28 @@ namespace Microsoft.Terminal.Control
Boolean ShouldShowSelectCommand();
Boolean ShouldShowSelectOutput();
event FontSizeChangedEventArgs FontSizeChanged;
// These events are called from some background thread
event Windows.Foundation.TypedEventHandler<Object, CopyToClipboardEventArgs> CopyToClipboard;
event Windows.Foundation.TypedEventHandler<Object, TitleChangedEventArgs> TitleChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> WarningBell;
event Windows.Foundation.TypedEventHandler<Object, Object> TabColorChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> BackgroundColorChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> TaskbarProgressChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> RendererEnteredErrorState;
event Windows.Foundation.TypedEventHandler<Object, ShowWindowArgs> ShowWindowChanged;
// These events are always called from the UI thread (bugs aside)
event FontSizeChangedEventArgs FontSizeChanged;
event Windows.Foundation.TypedEventHandler<Object, ScrollPositionChangedArgs> ScrollPositionChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> CursorPositionChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> TaskbarProgressChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> ConnectionStateChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> HoveredHyperlinkChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> RendererEnteredErrorState;
event Windows.Foundation.TypedEventHandler<Object, Object> SwapChainChanged;
event Windows.Foundation.TypedEventHandler<Object, RendererWarningArgs> RendererWarning;
event Windows.Foundation.TypedEventHandler<Object, NoticeEventArgs> RaiseNotice;
event Windows.Foundation.TypedEventHandler<Object, TransparencyChangedEventArgs> TransparencyChanged;
event Windows.Foundation.TypedEventHandler<Object, Object> ReceivedOutput;
event Windows.Foundation.TypedEventHandler<Object, FoundResultsArgs> FoundMatch;
event Windows.Foundation.TypedEventHandler<Object, ShowWindowArgs> ShowWindowChanged;
event Windows.Foundation.TypedEventHandler<Object, UpdateSelectionMarkersEventArgs> UpdateSelectionMarkers;
event Windows.Foundation.TypedEventHandler<Object, OpenHyperlinkEventArgs> OpenHyperlink;
event Windows.Foundation.TypedEventHandler<Object, Object> CloseTerminalRequested;

View File

@@ -997,7 +997,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// _cursorTimer doesn't exist, and it would never turn on the
// cursor. To mitigate, we'll initialize the cursor's 'on' state
// with `_focused` here.
_core.CursorOn(_focused);
_core.CursorOn(_focused || DisplayCursorWhileBlurred);
if (DisplayCursorWhileBlurred)
{
_cursorTimer->Start();
}
}
else
{
@@ -1873,7 +1877,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
TSFInputControl().NotifyFocusLeave();
}
if (_cursorTimer)
if (_cursorTimer && !DisplayCursorWhileBlurred)
{
_cursorTimer->Stop();
_core.CursorOn(false);

View File

@@ -182,6 +182,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
WINRT_OBSERVABLE_PROPERTY(winrt::Windows::UI::Xaml::Media::Brush, BackgroundBrush, _PropertyChangedHandlers, nullptr);
public:
til::property<bool> DisplayCursorWhileBlurred{ false };
private:
friend struct TermControlT<TermControl>; // friend our parent so it can bind private event handlers

View File

@@ -111,6 +111,8 @@ namespace Microsoft.Terminal.Control
// opacity set by the settings should call this instead.
Double BackgroundOpacity { get; };
Boolean DisplayCursorWhileBlurred;
Windows.UI.Xaml.Media.Brush BackgroundBrush { get; };
void ColorSelection(SelectionColor fg, SelectionColor bg, Microsoft.Terminal.Core.MatchMode matchMode);

View File

@@ -235,7 +235,7 @@ void Terminal::EraseScrollback()
bool Terminal::IsXtermBracketedPasteModeEnabled() const noexcept
{
return _bracketedPasteMode;
return _systemMode.test(Mode::BracketedPaste);
}
std::wstring_view Terminal::GetWorkingDirectory() noexcept

View File

@@ -111,17 +111,14 @@ public:
til::rect GetViewport() const noexcept override;
void SetViewportPosition(const til::point position) noexcept override;
void SetTextAttributes(const TextAttribute& attrs) noexcept override;
void SetAutoWrapMode(const bool wrapAtEOL) noexcept override;
bool GetAutoWrapMode() const noexcept override;
void SetSystemMode(const Mode mode, const bool enabled) noexcept override;
bool GetSystemMode(const Mode mode) const noexcept override;
void WarningBell() override;
bool GetLineFeedMode() const noexcept override;
void SetWindowTitle(const std::wstring_view title) override;
CursorType GetUserDefaultCursorStyle() const noexcept override;
bool ResizeWindow(const til::CoordType width, const til::CoordType height) noexcept override;
void SetConsoleOutputCP(const unsigned int codepage) noexcept override;
unsigned int GetConsoleOutputCP() const noexcept override;
void SetBracketedPasteMode(const bool enabled) noexcept override;
bool GetBracketedPasteMode() const noexcept override;
void CopyToClipboard(std::wstring_view content) override;
void SetTaskbarProgress(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::TaskbarState state, const size_t progress) override;
void SetWorkingDirectory(std::wstring_view uri) override;
@@ -320,10 +317,11 @@ private:
CursorType _defaultCursorShape = CursorType::Legacy;
til::enumset<Mode> _systemMode{ Mode::AutoWrap };
bool _snapOnInput = true;
bool _altGrAliasing = true;
bool _suppressApplicationTitle = false;
bool _bracketedPasteMode = false;
bool _trimBlockSelection = false;
bool _autoMarkPrompts = false;

View File

@@ -61,15 +61,14 @@ void Terminal::SetTextAttributes(const TextAttribute& attrs) noexcept
_activeBuffer().SetCurrentAttributes(attrs);
}
void Terminal::SetAutoWrapMode(const bool /*wrapAtEOL*/) noexcept
void Terminal::SetSystemMode(const Mode mode, const bool enabled) noexcept
{
// TODO: This will be needed to support DECAWM.
_systemMode.set(mode, enabled);
}
bool Terminal::GetAutoWrapMode() const noexcept
bool Terminal::GetSystemMode(const Mode mode) const noexcept
{
// TODO: This will be needed to support DECAWM.
return true;
return _systemMode.test(mode);
}
void Terminal::WarningBell()
@@ -77,12 +76,6 @@ void Terminal::WarningBell()
_pfnWarningBell();
}
bool Terminal::GetLineFeedMode() const noexcept
{
// TODO: This will be needed to support LNM.
return false;
}
void Terminal::SetWindowTitle(const std::wstring_view title)
{
if (!_suppressApplicationTitle)
@@ -114,16 +107,6 @@ unsigned int Terminal::GetConsoleOutputCP() const noexcept
return CP_UTF8;
}
void Terminal::SetBracketedPasteMode(const bool enabled) noexcept
{
_bracketedPasteMode = enabled;
}
bool Terminal::GetBracketedPasteMode() const noexcept
{
return _bracketedPasteMode;
}
void Terminal::CopyToClipboard(std::wstring_view content)
{
_pfnCopyToClipboard(content);

View File

@@ -8,7 +8,17 @@
using namespace ::winrt::Microsoft::Terminal::TerminalConnection;
using namespace ::winrt::Windows::Foundation;
static constexpr std::wstring_view PreviewText{ L"Windows Terminal\r\nCopyright (c) Microsoft Corporation\r\n\nC:\\Windows\\Terminal> " };
// clang-format off
static constexpr std::wstring_view PreviewText{
L"Windows Terminal\r\n"
L"C:\\> \x1b[93m" L"git\x1b[m diff \x1b[90m-w\x1b[m\r\n"
L"\x1b[1m" L"diff --git a/win b/win\x1b[m\r\n"
L"\x1b[36m@@ -1 +1 @@\x1b[m\r\n"
L"\x1b[31m- Windows Console\x1b[m\r\n"
L"\x1b[32m+ Windows Terminal!\x1b[m\r\n"
L"C:\\> \x1b[93mWrite-Host \x1b[36m\"\xd83c\xdf2f!\"\x1b[1D\x1b[m"
};
// clang-format on
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
@@ -16,8 +26,6 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
void PreviewConnection::Start() noexcept
{
// First send a sequence to disable cursor blinking
_TerminalOutputHandlers(L"\x1b[?12l");
// Send the preview text
_TerminalOutputHandlers(PreviewText);
}

View File

@@ -16,14 +16,9 @@ using namespace winrt::Windows::UI::Xaml::Navigation;
namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
Profiles_Appearance::Profiles_Appearance() :
_previewControl{ Control::TermControl(Model::TerminalSettings{}, nullptr, make<PreviewConnection>()) }
Profiles_Appearance::Profiles_Appearance()
{
InitializeComponent();
_previewControl.IsEnabled(false);
_previewControl.AllowFocusWhenDisabled(false);
ControlPreview().Child(_previewControl);
}
void Profiles_Appearance::OnNavigatedTo(const NavigationEventArgs& e)
@@ -38,25 +33,23 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
ProfileViewModel::UpdateFontList();
}
if (!_previewControl)
{
const auto settings = _Profile.TermSettings();
_previewControl = Control::TermControl(settings, settings, make<PreviewConnection>());
_previewControl.IsEnabled(false);
_previewControl.AllowFocusWhenDisabled(false);
_previewControl.DisplayCursorWhileBlurred(true);
ControlPreview().Child(_previewControl);
}
// Subscribe to some changes in the view model
// These changes should force us to update our own set of "Current<Setting>" members,
// and propagate those changes to the UI
_ViewModelChangedRevoker = _Profile.PropertyChanged(winrt::auto_revoke, [=](auto&&, const PropertyChangedEventArgs& /*args*/) {
_previewControl.UpdateControlSettings(_Profile.TermSettings(), _Profile.TermSettings());
});
_ViewModelChangedRevoker = _Profile.PropertyChanged(winrt::auto_revoke, { this, &Profiles_Appearance::_onProfilePropertyChanged });
// The Appearances object handles updating the values in the settings UI, but
// we still need to listen to the changes here just to update the preview control
_AppearanceViewModelChangedRevoker = _Profile.DefaultAppearance().PropertyChanged(winrt::auto_revoke, [=](auto&&, const PropertyChangedEventArgs& /*args*/) {
_previewControl.UpdateControlSettings(_Profile.TermSettings(), _Profile.TermSettings());
});
// There is a possibility that the control has not fully initialized yet,
// so wait for it to initialize before updating the settings (so we know
// that the renderer is set up)
_previewControl.Initialized([&](auto&& /*s*/, auto&& /*e*/) {
_previewControl.UpdateControlSettings(_Profile.TermSettings(), _Profile.TermSettings());
});
_AppearanceViewModelChangedRevoker = _Profile.DefaultAppearance().PropertyChanged(winrt::auto_revoke, { this, &Profiles_Appearance::_onProfilePropertyChanged });
}
void Profiles_Appearance::OnNavigatedFrom(const NavigationEventArgs& /*e*/)
@@ -74,4 +67,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
{
_Profile.DeleteUnfocusedAppearance();
}
void Profiles_Appearance::_onProfilePropertyChanged(const IInspectable&, const PropertyChangedEventArgs&) const
{
const auto settings = _Profile.TermSettings();
_previewControl.UpdateControlSettings(settings, settings);
}
}

View File

@@ -25,7 +25,9 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
WINRT_PROPERTY(Editor::ProfileViewModel, Profile, nullptr);
private:
Microsoft::Terminal::Control::TermControl _previewControl;
void _onProfilePropertyChanged(const IInspectable&, const PropertyChangedEventArgs&) const;
Microsoft::Terminal::Control::TermControl _previewControl{ nullptr };
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _ViewModelChangedRevoker;
Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker _AppearanceViewModelChangedRevoker;
Editor::IHostedInWindow _windowRoot;

View File

@@ -47,8 +47,8 @@
<!-- Control Preview -->
<Border MaxWidth="{StaticResource StandardControlMaxWidth}">
<Border x:Name="ControlPreview"
Width="350"
Height="160"
Width="400"
Height="180"
Margin="0,0,0,12"
HorizontalAlignment="Left"
BorderBrush="{ThemeResource SystemControlForegroundBaseMediumLowBrush}"

View File

@@ -140,7 +140,7 @@
<comment>This is the header for a control that allows the user to set the currently selected color scheme as their default.</comment>
</data>
<data name="ColorScheme_SetAsDefault.HelpText" xml:space="preserve">
<value>Apply this color scheme to all your profiles. This sets this color scheme in your 'Defaults' profile, it can still be overridden in each individual profile.</value>
<value>Apply this color scheme to all your profiles, by default. Individual profiles can still select their own color schemes.</value>
<comment>A description explaining how this control changes the user's default color scheme.</comment>
</data>
<data name="ColorScheme_Rename.Header" xml:space="preserve">
@@ -232,8 +232,8 @@
<comment>The header for a control allowing users to choose the app's language.</comment>
</data>
<data name="Globals_Language.HelpText" xml:space="preserve">
<value>Sets an override for the app's preferred language.</value>
<comment>A description explaining how this control changes the app's language.</comment>
<value>Selects a display language for the application. This will override your default Windows interface language.</value>
<comment>A description explaining how this control changes the app's language. {Locked="Windows"}</comment>
</data>
<data name="Globals_LanguageDefault" xml:space="preserve">
<value>Use system default</value>
@@ -252,7 +252,7 @@
<comment>Header for a control to select position of newly created tabs.</comment>
</data>
<data name="Globals_NewTabPosition.HelpText" xml:space="preserve">
<value>Specifies where new tabs appear in the tab row</value>
<value>Specifies where new tabs appear in the tab row.</value>
<comment>A description for what the "Position of newly created tabs" setting does.</comment>
</data>
<data name="Globals_NewTabPositionAfterLastTab.Content" xml:space="preserve">
@@ -450,10 +450,6 @@
<value>Use acrylic material in the tab row</value>
<comment>Header for a control to toggle whether "acrylic material" is used. "Acrylic material" is a Microsoft-specific term: https://docs.microsoft.com/en-us/windows/apps/design/style/acrylic</comment>
</data>
<data name="Globals_AcrylicTabRow.HelpText" xml:space="preserve">
<value>When checked, the tab row will have the acrylic material.</value>
<comment>A description for the "Globals_AcrylicTabRow.Header" setting does. "Acrylic material" is a Microsoft-specific term: https://docs.microsoft.com/en-us/windows/apps/design/style/acrylic</comment>
</data>
<data name="Globals_ShowTitleInTitlebar.Header" xml:space="preserve">
<value>Use active terminal title as application title</value>
<comment>Header for a control to toggle whether the terminal's title is shown as the application title, or not.</comment>
@@ -483,8 +479,8 @@
<comment>Header for a control to toggle whether the app should launch when the user's machine starts up, or not.</comment>
</data>
<data name="Globals_StartOnUserLogin.HelpText" xml:space="preserve">
<value>When enabled, this enables the launch of Terminal at machine startup.</value>
<comment>A description for what the "start on user login" setting does. Presented near "Globals_StartOnUserLogin.Header".</comment>
<value>Automatically launch Terminal when you log in to Windows.</value>
<comment>A description for what the "start on user login" setting does. Presented near "Globals_StartOnUserLogin.Header". {Locked="Windows"}</comment>
</data>
<data name="Globals_CenterOnLaunchCentered" xml:space="preserve">
<value>Centered</value>
@@ -511,7 +507,7 @@
<comment>Header for a control to choose how wide the tabs are.</comment>
</data>
<data name="Globals_TabWidthMode.HelpText" xml:space="preserve">
<value>Compact will shrink unfocused tabs to the size of the icon.</value>
<value>Compact will shrink inactive tabs to the size of the icon.</value>
<comment>A description for what the "tab width mode" setting does. Presented near "Globals_TabWidthMode.Header". 'Compact' must match the value for &lt;Globals_TabWidthModeCompact.Content&gt;.</comment>
</data>
<data name="Globals_TabWidthModeCompact.Content" xml:space="preserve">
@@ -527,13 +523,9 @@
<comment>An option to choose from for the "tab width mode" setting. When selected, each tab adjusts its width to the content within the tab.</comment>
</data>
<data name="Globals_Theme.Header" xml:space="preserve">
<value>Theme</value>
<value>Application Theme</value>
<comment>Header for a control to choose the theme colors used in the app.</comment>
</data>
<data name="Globals_Theme.HelpText" xml:space="preserve">
<value>Sets the theme of the application.</value>
<comment>A description for what the "theme" setting does. Presented near "Globals_Theme.Header".</comment>
</data>
<data name="Globals_ThemeDark.Content" xml:space="preserve">
<value>Dark</value>
<comment>An option to choose from for the "theme" setting. When selected, the app is in dark theme and darker colors are used throughout the app.</comment>
@@ -563,8 +555,8 @@
<comment>Header for a control to determine what delimiters to use to identify separate words during word selection.</comment>
</data>
<data name="Globals_WordDelimiters.HelpText" xml:space="preserve">
<value>Symbols used to define boundaries between words.</value>
<comment>A description for what the "word delimiters" setting does. Presented near "Globals_WordDelimiters.Header".</comment>
<value>These symbols will be used when you double-click text in the terminal or activate "Mark mode".</value>
<comment>A description for what the "word delimiters" setting does. Presented near "Globals_WordDelimiters.Header". "Mark" is used in the sense of "choosing something to interact with."</comment>
</data>
<data name="Nav_Appearance.Content" xml:space="preserve">
<value>Appearance</value>
@@ -619,8 +611,8 @@
<comment>Header for a control to toggle whether the app treats ctrl+alt as the AltGr (also known as the Alt Graph) modifier key found on keyboards. {Locked="AltGr"}</comment>
</data>
<data name="Profile_AltGrAliasing.HelpText" xml:space="preserve">
<value>By default Windows treats Ctrl+Alt as an alias for AltGr. When disabled, this behavior will be disabled.</value>
<comment>A description for what the "AltGr aliasing" setting does. Presented near "Profile_AltGrAliasing.Header".</comment>
<value>By default, Windows treats Ctrl+Alt as an alias for AltGr. This setting will override Windows' default behavior.</value>
<comment>A description for what the "AltGr aliasing" setting does. Presented near "Profile_AltGrAliasing.Header". {Locked="Windows"}</comment>
</data>
<data name="Profile_AntialiasingMode.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Text antialiasing</value>
@@ -786,10 +778,6 @@
<value>Color scheme</value>
<comment>Header for a control to select the scheme (or set) of colors used in the session. This is selected from a list of options managed by the user.</comment>
</data>
<data name="Profile_ColorScheme.HelpText" xml:space="preserve">
<value>Name of the color scheme to use.</value>
<comment>A description for what the "color scheme" setting does. Presented near "Profile_ColorScheme".</comment>
</data>
<data name="Profile_Commandline.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Command line</value>
<comment>Name for a control to determine commandline executable (i.e. a .exe file) to run when a terminal session of this profile is launched.</comment>
@@ -835,7 +823,7 @@
<comment>An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility only when the colors are part of this profile's color scheme's color table.</comment>
</data>
<data name="Profile_AdjustIndistinguishableColorsAlways.Content" xml:space="preserve">
<value>Always (More performance intensive)</value>
<value>Always</value>
<comment>An option to choose from for the "adjust indistinguishable colors" setting. When selected, we will adjust the text colors for visibility.</comment>
</data>
<data name="Profile_CursorShapeBar.Content" xml:space="preserve">
@@ -870,10 +858,6 @@
<value>Font face</value>
<comment>Name for a control to select the font for text in the app.</comment>
</data>
<data name="Profile_FontFace.HelpText" xml:space="preserve">
<value>Name of the font face used in the profile.</value>
<comment>A description for what the "font face" setting does. Presented near "Profile_FontFace".</comment>
</data>
<data name="Profile_FontSize.Header" xml:space="preserve">
<value>Font size</value>
<comment>Header for a control to determine the size of the text in the app.</comment>
@@ -895,7 +879,7 @@
<comment>Header for a control that sets the text line height.</comment>
</data>
<data name="Profile_LineHeight.HelpText" xml:space="preserve">
<value>Sets the height of each line in the terminal as a multiple of the font size. The default depends on your font and is usually around 1.2.</value>
<value>Override the line height of the terminal. Measured as a multiple of the font size. The default value depends on your font and is usually around 1.2.</value>
<comment>A description for what the "line height" setting does. Presented near "Profile_LineHeight".</comment>
</data>
<data name="Profile_LineHeightBox.PlaceholderText" xml:space="preserve">
@@ -987,15 +971,15 @@
<comment>Header for a control to toggle classic CRT display effects, which gives the terminal a retro look.</comment>
</data>
<data name="Profile_RetroTerminalEffect.HelpText" xml:space="preserve">
<value>When enabled, enables retro terminal effects such as glowing text and scan lines.</value>
<comment>A description for what the "retro terminal effects" setting does. Presented near "Profile_RetroTerminalEffect".</comment>
<value>Show retro-style terminal effects such as glowing text and scan lines.</value>
<comment>A description for what the "retro terminal effects" setting does. Presented near "Profile_RetroTerminalEffect". "Retro" is a common English prefix that suggests a nostalgic, dated appearance.</comment>
</data>
<data name="Profile_AdjustIndistinguishableColors.Header" xml:space="preserve">
<value>Automatically adjust lightness of indistinguishable text</value>
<comment>Header for a control to toggle if we should adjust the foreground color's lightness to make it more visible when necessary, based on the background color.</comment>
</data>
<data name="Profile_AdjustIndistinguishableColors.HelpText" xml:space="preserve">
<value>When enabled, enables automatic adjustment of indistinguishable colors, which will, only when necessary, adjust the foreground color's lightness to make it more visible (based on the background color).</value>
<value>Automatically brightens or darkens text to make it more visible. Even when enabled, this adjustment will only occur when a combination of foreground and background colors would result in poor contrast.</value>
<comment>A description for what the "adjust indistinguishable colors" setting does. Presented near "Profile_AdjustIndistinguishableColors".</comment>
</data>
<data name="Profile_ScrollbarVisibility.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
@@ -1055,8 +1039,8 @@
<comment>Header for a control to toggle changes in the app title.</comment>
</data>
<data name="Profile_SuppressApplicationTitle.HelpText" xml:space="preserve">
<value>Use the tab title to override the default title of the tab and suppress any title change messages from the application.</value>
<comment>A description for what the "suppress application title" setting does. Presented near "Profile_SuppressApplicationTitle".</comment>
<value>Ignore application requests to change the title (OSC 2).</value>
<comment>A description for what the "suppress application title" setting does. Presented near "Profile_SuppressApplicationTitle". "OSC 2" is a technical term that is understood in the industry. {Locked="OSC 2"}</comment>
</data>
<data name="Profile_TabTitle.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Tab title</value>
@@ -1095,7 +1079,7 @@
<comment>A supplementary setting to the "background image" setting. When enabled, the OS desktop wallpaper is used as the background image. Presented near "Profile_BackgroundImage".</comment>
</data>
<data name="Profile_UseDesktopImage.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>When enabled, use the desktop wallpaper image as the background image for the terminal.</value>
<value>Use the desktop wallpaper image as the background image for the terminal.</value>
<comment>A description for what the supplementary "use desktop image" setting does. Presented near "Profile_UseDesktopImage".</comment>
</data>
<data name="Settings_ResetSettingsButton.Content" xml:space="preserve">
@@ -1370,7 +1354,7 @@
<comment>Header for a control to choose how the tab switcher operates.</comment>
</data>
<data name="Globals_TabSwitcherMode.HelpText" xml:space="preserve">
<value>Defines the terminal behavior when switching tabs with the keyboard.</value>
<value>Selects which interface will be used when you switch tabs using the keyboard.</value>
<comment>A description for what the "tab switcher mode" setting does. Presented near "Globals_TabSwitcherMode.Header".</comment>
</data>
<data name="Globals_TabSwitcherModeMru.Content" xml:space="preserve">
@@ -1386,13 +1370,9 @@
<comment>An option to choose from for the "tab switcher mode" setting. The tab switcher overlay is hidden and does not appear in a separate window.</comment>
</data>
<data name="Globals_CopyFormat.Header" xml:space="preserve">
<value>Text format when copying</value>
<value>Text formats to copy to the clipboard</value>
<comment>Header for a control to select the format of copied text.</comment>
</data>
<data name="Globals_CopyFormat.HelpText" xml:space="preserve">
<value>Defines the type of formatting in which selected text is copied to your clipboard.</value>
<comment>A description for what the "copy formatting" setting does. Presented near "Globals_CopyFormat.Header".</comment>
</data>
<data name="Globals_CopyFormatNone.Content" xml:space="preserve">
<value>Plain text only</value>
<comment>An option to choose from for the "copy formatting" setting. Store only plain text data.</comment>
@@ -1571,10 +1551,10 @@
</data>
<data name="Globals_AutoHideWindow.Header" xml:space="preserve">
<value>Automatically hide window</value>
<comment>Header for a control to toggle the "Automatically hide window" setting. If enabled, the window will be hidden as soon as it loses focus.</comment>
<comment>Header for a control to toggle the "Automatically hide window" setting. If enabled, the terminal will be hidden as soon as you switch to another window.</comment>
</data>
<data name="Globals_AutoHideWindow.HelpText" xml:space="preserve">
<value>If enabled, the window will be hidden as soon as it loses focus.</value>
<value>If enabled, the terminal will be hidden as soon as you switch to another window.</value>
<comment>A description for what the "Automatically hide window" setting does.</comment>
</data>
<data name="ColorScheme_DeleteDisclaimerInBox" xml:space="preserve">

View File

@@ -157,7 +157,7 @@ static void _accumulateTraditionalLayoutPowerShellInstancesInDirectory(std::wstr
const auto executable = versionedPath / PWSH_EXE;
if (std::filesystem::exists(executable))
{
const auto preview = versionedPath.filename().wstring().find(L"-preview") != std::wstring::npos;
const auto preview = versionedPath.filename().native().find(L"-preview") != std::wstring::npos;
const auto previewFlag = preview ? PowerShellFlags::Preview : PowerShellFlags::None;
out.emplace_back(PowerShellInstance{ std::stoi(versionedPath.filename()),
PowerShellFlags::Traditional | flags | previewFlag,

View File

@@ -46,6 +46,7 @@ Licensed under the MIT license.
// Manually include til after we include Windows.Foundation to give it winrt superpowers
#include "til.h"
#include <til/mutex.h>
#include <til/winrt.h>
#include "ThrottledFunc.h"

View File

@@ -65,20 +65,6 @@ AppHost::AppHost(const winrt::TerminalApp::AppLogic& logic,
std::placeholders::_2);
_window->SetCreateCallback(pfn);
// Event handlers:
// MAKE SURE THEY ARE ALL:
// * winrt::auto_revoke
// * revoked manually in the dtor before the window is nulled out.
//
// If you don't, then it's possible for them to get triggered as the app is
// tearing down, after we've nulled out the window, during the dtor. That
// can cause unexpected AV's everywhere.
//
// _window callbacks don't need to be treated this way, because:
// * IslandWindow isn't a WinRT type (so it doesn't have neat revokers like this)
// * This particular bug scenario applies when we've already freed the window.
//
// (Most of these events are actually set up in AppHost::Initialize)
_window->MouseScrolled({ this, &AppHost::_WindowMouseWheeled });
_window->WindowActivated({ this, &AppHost::_WindowActivated });
_window->WindowMoved({ this, &AppHost::_WindowMoved });
@@ -91,21 +77,6 @@ AppHost::AppHost(const winrt::TerminalApp::AppLogic& logic,
_window->MakeWindow();
}
AppHost::~AppHost()
{
// destruction order is important for proper teardown here
// revoke ALL our event handlers. There's a large class of bugs where we
// might get a callback to one of these when we call app.Close() below. Make
// sure to revoke these first, so we won't get any more callbacks, then null
// out the window, then close the app.
_revokers = {};
_showHideWindowThrottler.reset();
_window = nullptr;
}
bool AppHost::OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down)
{
if (_windowLogic)
@@ -177,7 +148,7 @@ void AppHost::_HandleCommandlineArgs(const Remoting::WindowRequestedArgs& window
}
else if (args)
{
const auto result = _windowLogic.SetStartupCommandline(args.Commandline());
const auto result = _windowLogic.SetStartupCommandline(args.Commandline(), args.CurrentDirectory());
const auto message = _windowLogic.ParseCommandlineMessage();
if (!message.empty())
{
@@ -443,6 +414,15 @@ void AppHost::Initialize()
_window->OnAppInitialized();
}
void AppHost::Close()
{
// After calling _window->Close() we should avoid creating more WinUI related actions.
// I suspect WinUI wouldn't like that very much. As such unregister all event handlers first.
_revokers = {};
_showHideWindowThrottler.reset();
_window->Close();
}
// Method Description:
// - Called every time when the active tab's title changes. We'll also fire off
// a window message so we can update the window's title on the main thread,
@@ -498,7 +478,7 @@ void AppHost::LastTabClosed(const winrt::Windows::Foundation::IInspectable& /*se
// event handler finishes.
_windowManager.SignalClose(_peasant);
_window->Close();
PostQuitMessage(0);
}
LaunchPosition AppHost::_GetWindowLaunchPosition()
@@ -1110,10 +1090,7 @@ void AppHost::_ShowWindowChanged(const winrt::Windows::Foundation::IInspectable&
// should prevent scenarios where the Terminal window state and PTY window
// state get de-sync'd, and cause the window to minimize/restore constantly
// in a loop.
if (_showHideWindowThrottler)
{
_showHideWindowThrottler->Run(args.ShowOrHide());
}
_showHideWindowThrottler->Run(args.ShowOrHide());
}
void AppHost::_SummonWindowRequested(const winrt::Windows::Foundation::IInspectable& sender,

View File

@@ -13,11 +13,11 @@ public:
winrt::Microsoft::Terminal::Remoting::WindowRequestedArgs args,
const winrt::Microsoft::Terminal::Remoting::WindowManager& manager,
const winrt::Microsoft::Terminal::Remoting::Peasant& peasant) noexcept;
~AppHost();
void AppTitleChanged(const winrt::Windows::Foundation::IInspectable& sender, winrt::hstring newTitle);
void LastTabClosed(const winrt::Windows::Foundation::IInspectable& sender, const winrt::TerminalApp::LastTabClosedEventArgs& args);
void Initialize();
void Close();
bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down);
void SetTaskbarProgress(const winrt::Windows::Foundation::IInspectable& sender, const winrt::Windows::Foundation::IInspectable& args);

View File

@@ -37,7 +37,56 @@ IslandWindow::IslandWindow() noexcept :
IslandWindow::~IslandWindow()
{
_source.Close();
Close();
}
void IslandWindow::Close()
{
static const bool isWindows11 = []() {
OSVERSIONINFOEXW osver{};
osver.dwOSVersionInfoSize = sizeof(osver);
osver.dwBuildNumber = 22000;
DWORDLONG dwlConditionMask = 0;
VER_SET_CONDITION(dwlConditionMask, VER_BUILDNUMBER, VER_GREATER_EQUAL);
if (VerifyVersionInfoW(&osver, VER_BUILDNUMBER, dwlConditionMask) != FALSE)
{
return true;
}
return false;
}();
if (!isWindows11)
{
// BODGY
// ____ ____ _____ _______ __
// | _ \ / __ \| __ \ / ____\ \ / /
// | |_) | | | | | | | | __ \ \_/ /
// | _ <| | | | | | | | |_ | \ /
// | |_) | |__| | |__| | |__| | | |
// |____/ \____/|_____/ \_____| |_|
//
// There's a bug in Windows 10 where closing a DesktopWindowXamlSource
// on any thread will free an internal static resource that's used by
// XAML for the entire process. This would result in closing window
// essentially causing the entire app to crash.
//
// To avoid this, leak the XAML island. We only need to leak this on
// Windows 10, since the bug is fixed in Windows 11.
//
// See GH #15384, MSFT:32109540
auto a{ _source };
winrt::detach_abi(_source);
// </BODGY>
}
if (_source)
{
_source.Close();
_source = nullptr;
}
}
HWND IslandWindow::GetInteropHandle() const
@@ -89,17 +138,6 @@ void IslandWindow::MakeWindow() noexcept
WINRT_ASSERT(_window);
}
// Method Description:
// - Called when no tab is remaining to close the window.
// Arguments:
// - <none>
// Return Value:
// - <none>
void IslandWindow::Close()
{
PostQuitMessage(0);
}
// Method Description:
// - Set a callback to be called when we process a WM_CREATE message. This gives
// the AppHost a chance to resize the window to the proper size.

View File

@@ -20,7 +20,7 @@ public:
virtual ~IslandWindow() override;
virtual void MakeWindow() noexcept;
void Close();
virtual void Close();
virtual void OnSize(const UINT width, const UINT height);
HWND GetInteropHandle() const;

View File

@@ -26,7 +26,13 @@ NonClientIslandWindow::NonClientIslandWindow(const ElementTheme& requestedTheme)
{
}
NonClientIslandWindow::~NonClientIslandWindow() = default;
void NonClientIslandWindow::Close()
{
// Avoid further callbacks into XAML/WinUI-land after we've Close()d the DesktopWindowXamlSource
// inside `IslandWindow::Close()`. XAML thanks us for doing that by not crashing. Thank you XAML.
SetWindowLongPtr(_dragBarWindow.get(), GWLP_USERDATA, 0);
IslandWindow::Close();
}
static constexpr const wchar_t* dragBarClassName{ L"DRAG_BAR_WINDOW_CLASS" };

View File

@@ -30,8 +30,8 @@ public:
static constexpr const int topBorderVisibleHeight = 1;
NonClientIslandWindow(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme) noexcept;
virtual ~NonClientIslandWindow() override;
virtual void Close() override;
void MakeWindow() noexcept override;
virtual void OnSize(const UINT width, const UINT height) override;

View File

@@ -71,7 +71,17 @@ bool WindowEmperor::HandleCommandlineArgs()
{
std::vector<winrt::hstring> args;
_buildArgsFromCommandline(args);
auto cwd{ wil::GetCurrentDirectoryW<std::wstring>() };
const auto cwd{ wil::GetCurrentDirectoryW<std::wstring>() };
{
// ALWAYS change the _real_ CWD of the Terminal to system32, so that we
// don't lock the directory we were spawned in.
std::wstring system32{};
if (SUCCEEDED_LOG(wil::GetSystemDirectoryW<std::wstring>(system32)))
{
LOG_IF_WIN32_BOOL_FALSE(SetCurrentDirectoryW(system32.c_str()));
}
}
// Get the requested initial state of the window from our startup info. For
// something like `start /min`, this will set the wShowWindow member to
@@ -140,10 +150,16 @@ void WindowEmperor::_createNewWindowThread(const Remoting::WindowRequestedArgs&
std::thread t([weakThis, window]() {
try
{
auto cleanup = wil::scope_exit([&]() {
const auto decrementWindowCount = wil::scope_exit([&]() {
if (auto self{ weakThis.lock() })
{
self->_windowExitedHandler(window->Peasant().GetID());
self->_decrementWindowCount();
}
});
auto removeWindow = wil::scope_exit([&]() {
if (auto self{ weakThis.lock() })
{
self->_removeWindow(window->PeasantID());
}
});
@@ -160,7 +176,7 @@ void WindowEmperor::_createNewWindowThread(const Remoting::WindowRequestedArgs&
// remove the window from our list of windows, before we release the
// AppHost (and subsequently, the host's Logic() member that we use
// elsewhere).
cleanup.reset();
removeWindow.reset();
// Now that we no longer care about this thread's window, let it
// release it's app host and flush the rest of the XAML queue.
@@ -204,17 +220,20 @@ void WindowEmperor::_windowStartedHandlerPostXAML(const std::shared_ptr<WindowTh
lockedWindows->push_back(sender);
}
}
void WindowEmperor::_windowExitedHandler(uint64_t senderID)
void WindowEmperor::_removeWindow(uint64_t senderID)
{
auto lockedWindows{ _windows.lock() };
// find the window in _windows who's peasant's Id matches the peasant's Id
// and remove it
std::erase_if(*lockedWindows,
[&](const auto& w) {
return w->Peasant().GetID() == senderID;
});
std::erase_if(*lockedWindows, [&](const auto& w) {
return w->PeasantID() == senderID;
});
}
void WindowEmperor::_decrementWindowCount()
{
// When we run out of windows, exit our process if and only if:
// * We're not allowed to run headless OR
// * we've explicitly been told to "quit", which should fully exit the Terminal.
@@ -226,6 +245,7 @@ void WindowEmperor::_windowExitedHandler(uint64_t senderID)
_close();
}
}
// Method Description:
// - Set up all sorts of handlers now that we've determined that we're a process
// that will end up hosting the windows. These include:

View File

@@ -55,7 +55,8 @@ private:
bool _quitting{ false };
void _windowStartedHandlerPostXAML(const std::shared_ptr<WindowThread>& sender);
void _windowExitedHandler(uint64_t senderID);
void _removeWindow(uint64_t senderID);
void _decrementWindowCount();
void _becomeMonarch();
void _numberOfWindowsChanged(const winrt::Windows::Foundation::IInspectable&, const winrt::Windows::Foundation::IInspectable&);

View File

@@ -42,7 +42,8 @@ int WindowThread::RunMessagePump()
void WindowThread::RundownForExit()
{
_host = nullptr;
_host->Close();
// !! LOAD BEARING !!
//
// Make sure to finish pumping all the messages for our thread here. We
@@ -128,7 +129,8 @@ int WindowThread::_messagePump()
}
return 0;
}
winrt::Microsoft::Terminal::Remoting::Peasant WindowThread::Peasant()
uint64_t WindowThread::PeasantID()
{
return _peasant;
return _peasant.GetID();
}

View File

@@ -17,7 +17,7 @@ public:
int RunMessagePump();
void RundownForExit();
winrt::Microsoft::Terminal::Remoting::Peasant Peasant();
uint64_t PeasantID();
WINRT_CALLBACK(UpdateSettingsRequested, winrt::delegate<void()>);

View File

@@ -114,40 +114,49 @@ void ConhostInternalGetSet::SetTextAttributes(const TextAttribute& attrs)
}
// Routine Description:
// - Sets the ENABLE_WRAP_AT_EOL_OUTPUT mode. This controls whether the cursor moves
// to the beginning of the next row when it reaches the end of the current row.
// - Sets the state of one of the system modes.
// Arguments:
// - wrapAtEOL - set to true to wrap, false to overwrite the last character.
// - mode - The mode being updated.
// - enabled - True to enable the mode, false to disable it.
// Return Value:
// - <none>
void ConhostInternalGetSet::SetAutoWrapMode(const bool wrapAtEOL)
void ConhostInternalGetSet::SetSystemMode(const Mode mode, const bool enabled)
{
auto& outputMode = _io.GetActiveOutputBuffer().OutputMode;
WI_UpdateFlag(outputMode, ENABLE_WRAP_AT_EOL_OUTPUT, wrapAtEOL);
switch (mode)
{
case Mode::AutoWrap:
WI_UpdateFlag(_io.GetActiveOutputBuffer().OutputMode, ENABLE_WRAP_AT_EOL_OUTPUT, enabled);
break;
case Mode::LineFeed:
WI_UpdateFlag(_io.GetActiveOutputBuffer().OutputMode, DISABLE_NEWLINE_AUTO_RETURN, !enabled);
break;
case Mode::BracketedPaste:
ServiceLocator::LocateGlobals().getConsoleInformation().SetBracketedPasteMode(enabled);
break;
default:
THROW_HR(E_INVALIDARG);
}
}
// Routine Description:
// - Retrieves the current state of ENABLE_WRAP_AT_EOL_OUTPUT mode.
// - Retrieves the current state of one of the system modes.
// Arguments:
// - <none>
// - mode - The mode being queried.
// Return Value:
// - true if the mode is enabled. false otherwise.
bool ConhostInternalGetSet::GetAutoWrapMode() const
bool ConhostInternalGetSet::GetSystemMode(const Mode mode) const
{
const auto outputMode = _io.GetActiveOutputBuffer().OutputMode;
return WI_IsFlagSet(outputMode, ENABLE_WRAP_AT_EOL_OUTPUT);
}
// Method Description:
// - Retrieves the current Line Feed/New Line (LNM) mode.
// Arguments:
// - None
// Return Value:
// - true if a line feed also produces a carriage return. false otherwise.
bool ConhostInternalGetSet::GetLineFeedMode() const
{
auto& screenInfo = _io.GetActiveOutputBuffer();
return WI_IsFlagClear(screenInfo.OutputMode, DISABLE_NEWLINE_AUTO_RETURN);
switch (mode)
{
case Mode::AutoWrap:
return WI_IsFlagSet(_io.GetActiveOutputBuffer().OutputMode, ENABLE_WRAP_AT_EOL_OUTPUT);
case Mode::LineFeed:
return WI_IsFlagClear(_io.GetActiveOutputBuffer().OutputMode, DISABLE_NEWLINE_AUTO_RETURN);
case Mode::BracketedPaste:
return ServiceLocator::LocateGlobals().getConsoleInformation().GetBracketedPasteMode();
default:
THROW_HR(E_INVALIDARG);
}
}
// Routine Description:
@@ -245,29 +254,6 @@ unsigned int ConhostInternalGetSet::GetConsoleOutputCP() const
return ServiceLocator::LocateGlobals().getConsoleInformation().OutputCP;
}
// Routine Description:
// - Sets the XTerm bracketed paste mode. This controls whether pasted content is
// bracketed with control sequences to differentiate it from typed text.
// Arguments:
// - enable - set to true to enable bracketing, false to disable.
// Return Value:
// - <none>
void ConhostInternalGetSet::SetBracketedPasteMode(const bool enabled)
{
ServiceLocator::LocateGlobals().getConsoleInformation().SetBracketedPasteMode(enabled);
}
// Routine Description:
// - Gets the current state of XTerm bracketed paste mode.
// Arguments:
// - <none>
// Return Value:
// - true if the mode is enabled, false if not.
bool ConhostInternalGetSet::GetBracketedPasteMode() const
{
return ServiceLocator::LocateGlobals().getConsoleInformation().GetBracketedPasteMode();
}
// Routine Description:
// - Copies the given content to the clipboard.
// Arguments:

View File

@@ -38,13 +38,11 @@ public:
void SetTextAttributes(const TextAttribute& attrs) override;
void SetAutoWrapMode(const bool wrapAtEOL) override;
bool GetAutoWrapMode() const override;
void SetSystemMode(const Mode mode, const bool enabled) override;
bool GetSystemMode(const Mode mode) const override;
void WarningBell() override;
bool GetLineFeedMode() const override;
void SetWindowTitle(const std::wstring_view title) override;
void UseAlternateScreenBuffer() override;
@@ -60,8 +58,6 @@ public:
void SetConsoleOutputCP(const unsigned int codepage) override;
unsigned int GetConsoleOutputCP() const override;
void SetBracketedPasteMode(const bool enabled) override;
bool GetBracketedPasteMode() const override;
void CopyToClipboard(const std::wstring_view content) override;
void SetTaskbarProgress(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::TaskbarState state, const size_t progress) override;
void SetWorkingDirectory(const std::wstring_view uri) override;

File diff suppressed because it is too large Load Diff

View File

@@ -28,14 +28,43 @@ namespace til
// A basic, hashmap with linear probing. A `LoadFactor` of 2 equals
// a max. load of roughly 50% and a `LoadFactor` of 4 roughly 25%.
//
// `GrowthExponent` controls how fast the set grows and corresponds to
// a rate of 2^GrowthExponent. In other words, a `GrowthExponent` of 3
// equals a growth rate of 8x every time the capacity has been reached.
//
// It performs best with:
// * small and cheap T
// * >= 50% successful lookups
// * <= 50% load factor (LoadFactor >= 2, which is the minimum anyways)
template<typename T, size_t LoadFactor = 2>
template<typename T, size_t LoadFactor = 2, size_t GrowthExponent = 1>
struct linear_flat_set
{
static_assert(LoadFactor >= 2);
static_assert(GrowthExponent >= 1);
linear_flat_set() = default;
linear_flat_set(const linear_flat_set&) = delete;
linear_flat_set& operator=(const linear_flat_set&) = delete;
linear_flat_set(linear_flat_set&& other) noexcept :
_map{ std::move(other._map) },
_capacity{ std::exchange(other._capacity, 0) },
_load{ std::exchange(other._load, 0) },
_shift{ std::exchange(other._shift, initialShift) },
_mask{ std::exchange(other._mask, 0) }
{
}
linear_flat_set& operator=(linear_flat_set&& other) noexcept
{
_map = std::move(other._map);
_capacity = std::exchange(other._capacity, 0);
_load = std::exchange(other._load, 0);
_shift = std::exchange(other._shift, initialShift);
_mask = std::exchange(other._mask, 0);
return *this;
}
bool empty() const noexcept
{
@@ -52,6 +81,39 @@ namespace til
return { _map.get(), _capacity };
}
void clear() noexcept
{
if (_map)
{
std::fill_n(_map.get(), _capacity, T{});
_load = 0;
}
}
template<typename U>
T* lookup(U&& key) const noexcept
{
if (!_map)
{
return nullptr;
}
const auto hash = ::std::hash<T>{}(key) >> _shift;
for (auto i = hash;; ++i)
{
auto& slot = _map[i & _mask];
if (!slot)
{
return nullptr;
}
if (slot == key) [[likely]]
{
return &slot;
}
}
}
template<typename U>
std::pair<T&, bool> insert(U&& key)
{
@@ -88,14 +150,15 @@ namespace til
private:
__declspec(noinline) void _bumpSize()
{
// For instance at a GrowthExponent of 1:
// A _shift of 0 would result in a newShift of 0xfffff...
// A _shift of 1 would result in a newCapacity of 0
if (_shift < 2)
if (_shift <= GrowthExponent)
{
throw std::bad_array_new_length{};
}
const auto newShift = _shift - 1;
const auto newShift = _shift - GrowthExponent;
const auto newCapacity = size_t{ 1 } << (digits - newShift);
const auto newMask = newCapacity - 1;
auto newMap = std::make_unique<T[]>(newCapacity);
@@ -128,12 +191,13 @@ namespace til
}
static constexpr auto digits = std::numeric_limits<size_t>::digits;
// This results in an initial capacity of 8 items, independent of the LoadFactor.
static constexpr auto initialShift = digits - LoadFactor - 1;
std::unique_ptr<T[]> _map;
size_t _capacity = 0;
size_t _load = 0;
// This results in an initial capacity of 8 items, independent of the LoadFactor.
size_t _shift = digits - LoadFactor - 1;
size_t _shift = initialShift;
size_t _mask = 0;
};
}

View File

@@ -648,8 +648,11 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo
auto advanceWidth = 0.5f * fontSizeInPx;
{
static constexpr u32 codePoint = '0';
u16 glyphIndex;
if (SUCCEEDED(fontFace->GetGlyphIndicesW(&codePoint, 1, &glyphIndex)))
THROW_IF_FAILED(fontFace->GetGlyphIndicesW(&codePoint, 1, &glyphIndex));
if (glyphIndex)
{
DWRITE_GLYPH_METRICS glyphMetrics{};
THROW_IF_FAILED(fontFace->GetDesignGlyphMetrics(&glyphIndex, 1, &glyphMetrics, FALSE));

View File

@@ -705,7 +705,7 @@ void AtlasEngine::_flushBufferLine()
void AtlasEngine::_mapCharacters(const wchar_t* text, const u32 textLength, u32* mappedLength, IDWriteFontFace2** mappedFontFace) const
{
TextAnalysisSource analysisSource{ text, textLength };
TextAnalysisSource analysisSource{ _api.userLocaleName.c_str(), text, textLength };
const auto& textFormatAxis = _api.textFormatAxes[static_cast<size_t>(_api.attributes)];
// We don't read from scale anyways.
@@ -760,7 +760,7 @@ void AtlasEngine::_mapComplex(IDWriteFontFace2* mappedFontFace, u32 idx, u32 len
{
_api.analysisResults.clear();
TextAnalysisSource analysisSource{ _api.bufferLine.data(), gsl::narrow<UINT32>(_api.bufferLine.size()) };
TextAnalysisSource analysisSource{ _api.userLocaleName.c_str(), _api.bufferLine.data(), gsl::narrow<UINT32>(_api.bufferLine.size()) };
TextAnalysisSink analysisSink{ _api.analysisResults };
THROW_IF_FAILED(_p.textAnalyzer->AnalyzeScript(&analysisSource, idx, length, &analysisSink));

View File

@@ -32,11 +32,6 @@ using namespace Microsoft::Console::Render::Atlas;
[[nodiscard]] HRESULT AtlasEngine::Present() noexcept
try
{
if (!_p.dirtyRectInPx)
{
return S_OK;
}
if (!_p.dxgi.adapter || !_p.dxgi.factory->IsCurrent())
{
_recreateAdapter();
@@ -420,6 +415,12 @@ void AtlasEngine::_waitUntilCanRender() noexcept
void AtlasEngine::_present()
{
// Present1() dislikes being called with an empty dirty rect.
if (!_p.dirtyRectInPx)
{
return;
}
const til::rect fullRect{ 0, 0, _p.swapChain.targetSize.x, _p.swapChain.targetSize.y };
DXGI_PRESENT_PARAMETERS params{};

View File

@@ -4,8 +4,6 @@
#include "pch.h"
#include "Backend.h"
#include <dwmapi.h>
TIL_FAST_MATH_BEGIN
// Disable a bunch of warnings which get in the way of writing performant code.

View File

@@ -36,10 +36,23 @@ TIL_FAST_MATH_BEGIN
#pragma warning(disable : 26481) // Don't use pointer arithmetic. Use span instead (bounds.1).
#pragma warning(disable : 26482) // Only index into arrays using constant expressions (bounds.2).
// Initializing large arrays can be very costly compared to how cheap some of these functions are.
#define ALLOW_UNINITIALIZED_BEGIN _Pragma("warning(push)") _Pragma("warning(disable : 26494)")
#define ALLOW_UNINITIALIZED_END _Pragma("warning(pop)")
using namespace Microsoft::Console::Render::Atlas;
template<>
struct ::std::hash<BackendD3D::AtlasGlyphEntry>
struct std::hash<u16>
{
constexpr size_t operator()(u16 key) const noexcept
{
return til::flat_set_hash_integer(key);
}
};
template<>
struct std::hash<BackendD3D::AtlasGlyphEntry>
{
constexpr size_t operator()(u16 key) const noexcept
{
@@ -53,7 +66,7 @@ struct ::std::hash<BackendD3D::AtlasGlyphEntry>
};
template<>
struct ::std::hash<BackendD3D::AtlasFontFaceEntry>
struct std::hash<BackendD3D::AtlasFontFaceEntry>
{
using T = BackendD3D::AtlasFontFaceEntry;
@@ -707,9 +720,16 @@ void BackendD3D::_resetGlyphAtlas(const RenderingPayload& p)
stbrp_init_target(&_rectPacker, u, v, _rectPackerData.data(), _rectPackerData.size());
// This is a little imperfect, because it only releases the memory of the glyph mappings, not the memory held by
// any DirectWrite fonts. On the other side, the amount of fonts on a system is always finite, where "finite"
// is pretty low, relatively speaking. Additionally this allows us to cache the boxGlyphs map indefinitely.
// It's not great, but it's not terrible.
for (auto& slot : _glyphAtlasMap.container())
{
slot.inner.reset();
if (slot.inner)
{
slot.inner->glyphs.clear();
}
}
_d2dBeginDrawing();
@@ -975,7 +995,13 @@ void BackendD3D::_drawText(RenderingPayload& p)
// We need to goto here, because a retry will cause the atlas texture as well as the
// _glyphCache hashmap to be cleared, and so we'll have to call insert() again.
drawGlyphRetry:
auto& fontFaceEntry = *_glyphAtlasMap.insert(fontFaceKey).first.inner;
const auto [fontFaceEntryOuter, fontFaceInserted] = _glyphAtlasMap.insert(fontFaceKey);
auto& fontFaceEntry = *fontFaceEntryOuter.inner;
if (fontFaceInserted)
{
_initializeFontFaceEntry(fontFaceEntry);
}
while (x < m.glyphsTo)
{
@@ -1117,7 +1143,30 @@ void BackendD3D::_drawTextOverlapSplit(const RenderingPayload& p, u16 y)
}
}
bool BackendD3D::_drawGlyph(const RenderingPayload& p, const BackendD3D::AtlasFontFaceEntryInner& fontFaceEntry, BackendD3D::AtlasGlyphEntry& glyphEntry)
void BackendD3D::_initializeFontFaceEntry(AtlasFontFaceEntryInner& fontFaceEntry)
{
ALLOW_UNINITIALIZED_BEGIN
std::array<u32, 0x100> codepoints;
std::array<u16, 0x100> indices;
ALLOW_UNINITIALIZED_END
for (u32 i = 0; i < codepoints.size(); ++i)
{
codepoints[i] = 0x2500 + i;
}
THROW_IF_FAILED(fontFaceEntry.fontFace->GetGlyphIndicesW(codepoints.data(), codepoints.size(), indices.data()));
for (u32 i = 0; i < indices.size(); ++i)
{
if (const auto idx = indices[i])
{
fontFaceEntry.boxGlyphs.insert(idx);
}
}
}
bool BackendD3D::_drawGlyph(const RenderingPayload& p, const AtlasFontFaceEntryInner& fontFaceEntry, AtlasGlyphEntry& glyphEntry)
{
if (!fontFaceEntry.fontFace)
{
@@ -1200,22 +1249,20 @@ bool BackendD3D::_drawGlyph(const RenderingPayload& p, const BackendD3D::AtlasFo
#endif
const auto lineRendition = static_cast<LineRendition>(fontFaceEntry.lineRendition);
std::optional<D2D1_MATRIX_3X2_F> transform;
const auto needsTransform = lineRendition != LineRendition::SingleWidth;
if (lineRendition != LineRendition::SingleWidth)
static constexpr D2D1_MATRIX_3X2_F identityTransform{ .m11 = 1, .m22 = 1 };
D2D1_MATRIX_3X2_F transform = identityTransform;
if (needsTransform)
{
auto& t = transform.emplace();
t.m11 = 2.0f;
t.m22 = lineRendition >= LineRendition::DoubleHeightTop ? 2.0f : 1.0f;
_d2dRenderTarget->SetTransform(&t);
transform.m11 = 2.0f;
transform.m22 = lineRendition >= LineRendition::DoubleHeightTop ? 2.0f : 1.0f;
_d2dRenderTarget->SetTransform(&transform);
}
const auto restoreTransform = wil::scope_exit([&]() noexcept {
if (transform)
{
static constexpr D2D1_MATRIX_3X2_F identity{ .m11 = 1, .m22 = 1 };
_d2dRenderTarget->SetTransform(&identity);
}
_d2dRenderTarget->SetTransform(&identityTransform);
});
// This calculates the black box of the glyph, or in other words,
@@ -1239,7 +1286,7 @@ bool BackendD3D::_drawGlyph(const RenderingPayload& p, const BackendD3D::AtlasFo
bool isColorGlyph = false;
D2D1_RECT_F bounds = GlyphRunEmptyBounds;
const auto cleanup = wil::scope_exit([&]() {
const auto antialiasingCleanup = wil::scope_exit([&]() {
if (isColorGlyph)
{
_d2dRenderTarget4->SetTextAntialiasMode(static_cast<D2D1_TEXT_ANTIALIAS_MODE>(p.s->font->antialiasingMode));
@@ -1266,7 +1313,23 @@ bool BackendD3D::_drawGlyph(const RenderingPayload& p, const BackendD3D::AtlasFo
}
}
// box may be empty if the glyph is whitespace.
// Overhangs for box glyphs can produce unsightly effects, where the antialiased edges of horizontal
// and vertical lines overlap between neighboring glyphs and produce "boldened" intersections.
// It looks a little something like this:
// ---+---+---
// This avoids the issue in most cases by simply clipping the glyph to the size of a single cell.
// The downside is that it fails to work well for custom line heights, etc.
const auto isBoxGlyph = fontFaceEntry.boxGlyphs.lookup(glyphEntry.glyphIndex) != nullptr;
if (isBoxGlyph)
{
// NOTE: As mentioned above, the "origin" of a glyph's coordinate system is its baseline.
bounds.left = std::max(bounds.left, 0.0f);
bounds.top = std::max(bounds.top, static_cast<f32>(-p.s->font->baseline) * transform.m22);
bounds.right = std::min(bounds.right, static_cast<f32>(p.s->font->cellSize.x) * transform.m11);
bounds.bottom = std::min(bounds.bottom, static_cast<f32>(p.s->font->descender) * transform.m22);
}
// The bounds may be empty if the glyph is whitespace.
if (bounds.left >= bounds.right || bounds.top >= bounds.bottom)
{
return true;
@@ -1292,16 +1355,32 @@ bool BackendD3D::_drawGlyph(const RenderingPayload& p, const BackendD3D::AtlasFo
static_cast<f32>(rect.y - bt),
};
if (transform)
{
auto& t = *transform;
t.dx = (1.0f - t.m11) * baselineOrigin.x;
t.dy = (1.0f - t.m22) * baselineOrigin.y;
_d2dRenderTarget->SetTransform(&t);
}
_d2dBeginDrawing();
if (isBoxGlyph)
{
const D2D1_RECT_F clipRect{
static_cast<f32>(rect.x) / transform.m11,
static_cast<f32>(rect.y) / transform.m22,
static_cast<f32>(rect.x + rect.w) / transform.m11,
static_cast<f32>(rect.y + rect.h) / transform.m22,
};
_d2dRenderTarget4->PushAxisAlignedClip(&clipRect, D2D1_ANTIALIAS_MODE_ALIASED);
}
const auto boxGlyphCleanup = wil::scope_exit([&]() {
if (isBoxGlyph)
{
_d2dRenderTarget4->PopAxisAlignedClip();
}
});
if (needsTransform)
{
transform.dx = (1.0f - transform.m11) * baselineOrigin.x;
transform.dy = (1.0f - transform.m22) * baselineOrigin.y;
_d2dRenderTarget->SetTransform(&transform);
}
if (!isColorGlyph)
{
_d2dRenderTarget->DrawGlyphRun(baselineOrigin, &glyphRun, _brush.get(), DWRITE_MEASURING_MODE_NATURAL);

View File

@@ -151,6 +151,9 @@ namespace Microsoft::Console::Render::Atlas
LineRendition lineRendition = LineRendition::SingleWidth;
til::linear_flat_set<AtlasGlyphEntry> glyphs;
// boxGlyphs gets an increased growth rate of 2^2 = 4x, because presumably fonts either contain very
// few or almost all of the box glyphs. This reduces the cost of _initializeFontFaceEntry quite a bit.
til::linear_flat_set<u16, 2, 2> boxGlyphs;
};
struct AtlasFontFaceEntry
@@ -214,6 +217,7 @@ namespace Microsoft::Console::Render::Atlas
void _uploadBackgroundBitmap(const RenderingPayload& p);
void _drawText(RenderingPayload& p);
ATLAS_ATTR_COLD void _drawTextOverlapSplit(const RenderingPayload& p, u16 y);
ATLAS_ATTR_COLD static void _initializeFontFaceEntry(AtlasFontFaceEntryInner& fontFaceEntry);
ATLAS_ATTR_COLD [[nodiscard]] bool _drawGlyph(const RenderingPayload& p, const AtlasFontFaceEntryInner& fontFaceEntry, AtlasGlyphEntry& glyphEntry);
bool _drawSoftFontGlyph(const RenderingPayload& p, const AtlasFontFaceEntryInner& fontFaceEntry, AtlasGlyphEntry& glyphEntry);
void _drawGlyphPrepareRetry(const RenderingPayload& p);

View File

@@ -9,9 +9,10 @@
using namespace Microsoft::Console::Render::Atlas;
TextAnalysisSource::TextAnalysisSource(const wchar_t* _text, const UINT32 _textLength) noexcept :
_text{ _text },
_textLength{ _textLength }
TextAnalysisSource::TextAnalysisSource(const wchar_t* locale, const wchar_t* text, const UINT32 textLength) noexcept :
_locale{ locale },
_text{ text },
_textLength{ textLength }
{
}
@@ -94,7 +95,7 @@ HRESULT TextAnalysisSource::GetLocaleName(UINT32 textPosition, UINT32* textLengt
__assume(localeName != nullptr);
*textLength = _textLength - textPosition;
*localeName = nullptr;
*localeName = _locale;
return S_OK;
}

View File

@@ -9,7 +9,7 @@ namespace Microsoft::Console::Render::Atlas
{
struct TextAnalysisSource final : IDWriteTextAnalysisSource
{
TextAnalysisSource(const wchar_t* _text, const UINT32 _textLength) noexcept;
TextAnalysisSource(const wchar_t* locale, const wchar_t* text, const UINT32 textLength) noexcept;
#ifndef NDEBUG
~TextAnalysisSource();
#endif
@@ -24,6 +24,7 @@ namespace Microsoft::Console::Render::Atlas
HRESULT __stdcall GetNumberSubstitution(UINT32 textPosition, UINT32* textLength, IDWriteNumberSubstitution** numberSubstitution) noexcept override;
private:
const wchar_t* _locale;
const wchar_t* _text;
const UINT32 _textLength;
#ifndef NDEBUG

View File

@@ -553,5 +553,4 @@ namespace Microsoft::Console::Render::Atlas
virtual void Render(RenderingPayload& payload) = 0;
virtual bool RequiresContinuousRedraw() noexcept = 0;
};
}

View File

@@ -418,6 +418,7 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes
enum ModeParams : VTInt
{
IRM_InsertReplaceMode = ANSIStandardMode(4),
LNM_LineFeedNewLineMode = ANSIStandardMode(20),
DECCKM_CursorKeysMode = DECPrivateMode(1),
DECANM_AnsiMode = DECPrivateMode(2),
DECCOLM_SetNumberOfColumns = DECPrivateMode(3),
@@ -430,6 +431,7 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes
XTERM_EnableDECCOLMSupport = DECPrivateMode(40),
DECNKM_NumericKeypadMode = DECPrivateMode(66),
DECBKM_BackarrowKeyMode = DECPrivateMode(67),
DECLRMM_LeftRightMarginMode = DECPrivateMode(69),
VT200_MOUSE_MODE = DECPrivateMode(1000),
BUTTON_EVENT_MOUSE_MODE = DECPrivateMode(1002),
ANY_EVENT_MOUSE_MODE = DECPrivateMode(1003),

View File

@@ -54,6 +54,7 @@ public:
virtual bool SetKeypadMode(const bool applicationMode) = 0; // DECKPAM, DECKPNM
virtual bool SetAnsiMode(const bool ansiMode) = 0; // DECANM
virtual bool SetTopBottomScrollingMargins(const VTInt topMargin, const VTInt bottomMargin) = 0; // DECSTBM
virtual bool SetLeftRightScrollingMargins(const VTInt leftMargin, const VTInt rightMargin) = 0; // DECSLRM
virtual bool WarningBell() = 0; // BEL
virtual bool CarriageReturn() = 0; // CR
virtual bool LineFeed(const DispatchTypes::LineFeedType lineFeedType) = 0; // IND, NEL, LF, FF, VT

View File

@@ -48,11 +48,17 @@ namespace Microsoft::Console::VirtualTerminal
virtual void SetTextAttributes(const TextAttribute& attrs) = 0;
virtual void SetAutoWrapMode(const bool wrapAtEOL) = 0;
virtual bool GetAutoWrapMode() const = 0;
enum class Mode : size_t
{
AutoWrap,
LineFeed,
BracketedPaste
};
virtual void SetSystemMode(const Mode mode, const bool enabled) = 0;
virtual bool GetSystemMode(const Mode mode) const = 0;
virtual void WarningBell() = 0;
virtual bool GetLineFeedMode() const = 0;
virtual void SetWindowTitle(const std::wstring_view title) = 0;
virtual void UseAlternateScreenBuffer() = 0;
virtual void UseMainScreenBuffer() = 0;
@@ -64,8 +70,6 @@ namespace Microsoft::Console::VirtualTerminal
virtual void SetConsoleOutputCP(const unsigned int codepage) = 0;
virtual unsigned int GetConsoleOutputCP() const = 0;
virtual void SetBracketedPasteMode(const bool enabled) = 0;
virtual bool GetBracketedPasteMode() const = 0;
virtual void CopyToClipboard(const std::wstring_view content) = 0;
virtual void SetTaskbarProgress(const DispatchTypes::TaskbarState state, const size_t progress) = 0;
virtual void SetWorkingDirectory(const std::wstring_view uri) = 0;

View File

@@ -74,15 +74,25 @@ void AdaptDispatch::_WriteToBuffer(const std::wstring_view string)
auto& textBuffer = _api.GetTextBuffer();
auto& cursor = textBuffer.GetCursor();
auto cursorPosition = cursor.GetPosition();
const auto wrapAtEOL = _api.GetAutoWrapMode();
const auto wrapAtEOL = _api.GetSystemMode(ITerminalApi::Mode::AutoWrap);
const auto attributes = textBuffer.GetCurrentAttributes();
const auto viewport = _api.GetViewport();
const auto [topMargin, bottomMargin] = _GetVerticalMargins(viewport, true);
const auto [leftMargin, rightMargin] = _GetHorizontalMargins(textBuffer.GetSize().Width());
auto lineWidth = textBuffer.GetLineWidth(cursorPosition.y);
if (cursorPosition.x <= rightMargin && cursorPosition.y >= topMargin && cursorPosition.y <= bottomMargin)
{
lineWidth = std::min(lineWidth, rightMargin + 1);
}
// Turn off the cursor until we're done, so it isn't refreshed unnecessarily.
cursor.SetIsOn(false);
RowWriteState state{
.text = string,
.columnLimit = textBuffer.GetLineWidth(cursorPosition.y),
.columnLimit = lineWidth,
};
while (!state.text.empty())
@@ -98,7 +108,12 @@ void AdaptDispatch::_WriteToBuffer(const std::wstring_view string)
_DoLineFeed(textBuffer, true, true);
cursorPosition = cursor.GetPosition();
// We need to recalculate the width when moving to a new line.
state.columnLimit = textBuffer.GetLineWidth(cursorPosition.y);
lineWidth = textBuffer.GetLineWidth(cursorPosition.y);
if (cursorPosition.y >= topMargin && cursorPosition.y <= bottomMargin)
{
lineWidth = std::min(lineWidth, rightMargin + 1);
}
state.columnLimit = lineWidth;
}
}
@@ -278,6 +293,29 @@ std::pair<int, int> AdaptDispatch::_GetVerticalMargins(const til::rect& viewport
return { topMargin, bottomMargin };
}
// Routine Description:
// - Returns the coordinates of the horizontal scroll margins.
// Arguments:
// - bufferWidth - The width of the buffer
// Return Value:
// - A std::pair containing the left and right coordinates (inclusive).
std::pair<int, int> AdaptDispatch::_GetHorizontalMargins(const til::CoordType bufferWidth) noexcept
{
// If the left is out of range, reset the margins completely.
const auto rightmostColumn = bufferWidth - 1;
if (_scrollMargins.left >= rightmostColumn)
{
_scrollMargins.left = _scrollMargins.right = 0;
}
// If margins aren't set, use the full extent of the buffer.
const auto marginsSet = _scrollMargins.left < _scrollMargins.right;
auto leftMargin = marginsSet ? _scrollMargins.left : 0;
auto rightMargin = marginsSet ? _scrollMargins.right : rightmostColumn;
// If the right is out of range, clamp it to the rightmost column.
rightMargin = std::min(rightMargin, rightmostColumn);
return { leftMargin, rightMargin };
}
// Routine Description:
// - Generalizes cursor movement to a specific position, which can be absolute or relative.
// Arguments:
@@ -292,8 +330,10 @@ bool AdaptDispatch::_CursorMovePosition(const Offset rowOffset, const Offset col
const auto viewport = _api.GetViewport();
auto& textBuffer = _api.GetTextBuffer();
auto& cursor = textBuffer.GetCursor();
const auto bufferWidth = textBuffer.GetSize().Width();
const auto cursorPosition = cursor.GetPosition();
const auto [topMargin, bottomMargin] = _GetVerticalMargins(viewport, true);
const auto [leftMargin, rightMargin] = _GetHorizontalMargins(bufferWidth);
// For relative movement, the given offsets will be relative to
// the current cursor position.
@@ -307,39 +347,54 @@ bool AdaptDispatch::_CursorMovePosition(const Offset rowOffset, const Offset col
row = _modes.test(Mode::Origin) ? topMargin : viewport.top;
}
// And if the column is absolute, it'll be relative to column 0.
// And if the column is absolute, it'll be relative to column 0,
// or the left margin, depending on the origin mode.
// Horizontal positions are not affected by the viewport.
if (colOffset.IsAbsolute)
{
col = 0;
col = _modes.test(Mode::Origin) ? leftMargin : 0;
}
// Adjust the base position by the given offsets and clamp the results.
// The row is constrained within the viewport's vertical boundaries,
// while the column is constrained by the buffer width.
row = std::clamp(row + rowOffset.Value, viewport.top, viewport.bottom - 1);
col = std::clamp(col + colOffset.Value, 0, textBuffer.GetSize().Width() - 1);
col = std::clamp(col + colOffset.Value, 0, bufferWidth - 1);
// If the operation needs to be clamped inside the margins, or the origin
// mode is relative (which always requires margin clamping), then the row
// may need to be adjusted further.
// and column may need to be adjusted further.
if (clampInMargins || _modes.test(Mode::Origin))
{
// See microsoft/terminal#2929 - If the cursor is _below_ the top
// margin, it should stay below the top margin. If it's _above_ the
// bottom, it should stay above the bottom. Cursor movements that stay
// outside the margins shouldn't necessarily be affected. For example,
// moving up while below the bottom margin shouldn't just jump straight
// to the bottom margin. See
// ScreenBufferTests::CursorUpDownOutsideMargins for a test of that
// behavior.
if (cursorPosition.y >= topMargin)
// Vertical margins only apply if the original position is inside the
// horizontal margins. Also, the cursor will only be clamped inside the
// top margin if it was already below the top margin to start with, and
// it will only be clamped inside the bottom margin if it was already
// above the bottom margin to start with.
if (cursorPosition.x >= leftMargin && cursorPosition.x <= rightMargin)
{
row = std::max(row, topMargin);
if (cursorPosition.y >= topMargin)
{
row = std::max(row, topMargin);
}
if (cursorPosition.y <= bottomMargin)
{
row = std::min(row, bottomMargin);
}
}
if (cursorPosition.y <= bottomMargin)
// Similarly, horizontal margins only apply if the new row is inside the
// vertical margins. And the cursor is only clamped inside the horizontal
// margins if it was already inside to start with.
if (row >= topMargin && row <= bottomMargin)
{
row = std::min(row, bottomMargin);
if (cursorPosition.x >= leftMargin)
{
col = std::max(col, leftMargin);
}
if (cursorPosition.x <= rightMargin)
{
col = std::min(col, rightMargin);
}
}
}
@@ -448,6 +503,7 @@ bool AdaptDispatch::CursorSaveState()
// Although if origin mode is set, the cursor is relative to the margin origin.
if (_modes.test(Mode::Origin))
{
cursorPosition.x -= _GetHorizontalMargins(textBuffer.GetSize().Width()).first;
cursorPosition.y -= _GetVerticalMargins(viewport, false).first;
}
@@ -520,14 +576,35 @@ void AdaptDispatch::_ScrollRectVertically(TextBuffer& textBuffer, const til::rec
const auto absoluteDelta = std::min(std::abs(delta), scrollRect.height());
if (absoluteDelta < scrollRect.height())
{
// For now we're assuming the scrollRect is always the full width of the
// buffer, but this will likely need to be extended to support scrolling
// of arbitrary widths at some point in the future.
const auto top = delta > 0 ? scrollRect.top : scrollRect.top + absoluteDelta;
const auto width = scrollRect.width();
const auto height = scrollRect.height() - absoluteDelta;
const auto actualDelta = delta > 0 ? absoluteDelta : -absoluteDelta;
textBuffer.ScrollRows(top, height, actualDelta);
textBuffer.TriggerRedraw(Viewport::FromExclusive(scrollRect));
if (width == textBuffer.GetSize().Width())
{
// If the scrollRect is the full width of the buffer, we can scroll
// more efficiently by rotating the row storage.
textBuffer.ScrollRows(top, height, actualDelta);
textBuffer.TriggerRedraw(Viewport::FromExclusive(scrollRect));
}
else
{
// Otherwise we have to move the content up or down by copying the
// requested buffer range one cell at a time.
const auto srcOrigin = til::point{ scrollRect.left, top };
const auto dstOrigin = til::point{ scrollRect.left, top + actualDelta };
const auto srcView = Viewport::FromDimensions(srcOrigin, width, height);
const auto dstView = Viewport::FromDimensions(dstOrigin, width, height);
const auto walkDirection = Viewport::DetermineWalkDirection(srcView, dstView);
auto srcPos = srcView.GetWalkOrigin(walkDirection);
auto dstPos = dstView.GetWalkOrigin(walkDirection);
do
{
const auto current = OutputCell(*textBuffer.GetCellDataAt(srcPos));
textBuffer.WriteLine(OutputCellIterator({ &current, 1 }), dstPos);
srcView.WalkInBounds(srcPos, walkDirection);
} while (dstView.WalkInBounds(dstPos, walkDirection));
}
}
// Rows revealed by the scroll are filled with standard erase attributes.
@@ -597,13 +674,21 @@ void AdaptDispatch::_ScrollRectHorizontally(TextBuffer& textBuffer, const til::r
// - <none>
void AdaptDispatch::_InsertDeleteCharacterHelper(const VTInt delta)
{
const auto viewport = _api.GetViewport();
auto& textBuffer = _api.GetTextBuffer();
const auto row = textBuffer.GetCursor().GetPosition().y;
const auto startCol = textBuffer.GetCursor().GetPosition().x;
const auto endCol = textBuffer.GetLineWidth(row);
_ScrollRectHorizontally(textBuffer, { startCol, row, endCol, row + 1 }, delta);
// The ICH and DCH controls are expected to reset the delayed wrap flag.
textBuffer.GetCursor().ResetDelayEOLWrap();
const auto col = textBuffer.GetCursor().GetPosition().x;
const auto lineWidth = textBuffer.GetLineWidth(row);
const auto [topMargin, bottomMargin] = _GetVerticalMargins(viewport, true);
const auto [leftMargin, rightMargin] = (row >= topMargin && row <= bottomMargin) ?
_GetHorizontalMargins(lineWidth) :
std::make_pair(0, lineWidth - 1);
if (col >= leftMargin && col <= rightMargin)
{
_ScrollRectHorizontally(textBuffer, { col, row, rightMargin + 1, row + 1 }, delta);
// The ICH and DCH controls are expected to reset the delayed wrap flag.
textBuffer.GetCursor().ResetDelayEOLWrap();
}
}
// Routine Description:
@@ -972,15 +1057,17 @@ til::rect AdaptDispatch::_CalculateRectArea(const VTInt top, const VTInt left, c
// We start by calculating the margin offsets and maximum dimensions.
// If the origin mode isn't set, we use the viewport extent.
const auto [topMargin, bottomMargin] = _GetVerticalMargins(viewport, false);
const auto [leftMargin, rightMargin] = _GetHorizontalMargins(bufferSize.width);
const auto yOffset = _modes.test(Mode::Origin) ? topMargin : 0;
const auto yMaximum = _modes.test(Mode::Origin) ? bottomMargin + 1 : viewport.height();
const auto xMaximum = bufferSize.width;
const auto xOffset = _modes.test(Mode::Origin) ? leftMargin : 0;
const auto xMaximum = _modes.test(Mode::Origin) ? rightMargin + 1 : bufferSize.width;
auto fillRect = til::inclusive_rect{};
fillRect.left = left;
fillRect.left = left + xOffset;
fillRect.top = top + yOffset;
// Right and bottom default to the maximum dimensions.
fillRect.right = (right ? right : xMaximum);
fillRect.right = (right ? right + xOffset : xMaximum);
fillRect.bottom = (bottom ? bottom + yOffset : yMaximum);
// We also clamp everything to the maximum dimensions, and subtract 1
@@ -1339,13 +1426,17 @@ bool AdaptDispatch::RequestChecksumRectangularArea(const VTInt id, const VTInt p
// - True.
bool AdaptDispatch::SetLineRendition(const LineRendition rendition)
{
auto& textBuffer = _api.GetTextBuffer();
textBuffer.SetCurrentLineRendition(rendition);
// There is some variation in how this was handled by the different DEC
// terminals, but the STD 070 reference (on page D-13) makes it clear that
// the delayed wrap (aka the Last Column Flag) was expected to be reset when
// line rendition controls were executed.
textBuffer.GetCursor().ResetDelayEOLWrap();
// The line rendition can't be changed if left/right margins are allowed.
if (!_modes.test(Mode::AllowDECSLRM))
{
auto& textBuffer = _api.GetTextBuffer();
textBuffer.SetCurrentLineRendition(rendition);
// There is some variation in how this was handled by the different DEC
// terminals, but the STD 070 reference (on page D-13) makes it clear that
// the delayed wrap (aka the Last Column Flag) was expected to be reset when
// line rendition controls were executed.
textBuffer.GetCursor().ResetDelayEOLWrap();
}
return true;
}
@@ -1526,11 +1617,11 @@ void AdaptDispatch::_CursorPositionReport(const bool extendedReport)
cursorPosition.x++;
cursorPosition.y++;
// If the origin mode is relative, line numbers start at top margin of the scrolling region.
// If the origin mode is set, the cursor is relative to the margin origin.
if (_modes.test(Mode::Origin))
{
const auto topMargin = _GetVerticalMargins(viewport, false).first;
cursorPosition.y -= topMargin;
cursorPosition.x -= _GetHorizontalMargins(textBuffer.GetSize().Width()).first;
cursorPosition.y -= _GetVerticalMargins(viewport, false).first;
}
// Now send it back into the input channel of the console.
@@ -1590,7 +1681,8 @@ void AdaptDispatch::_ScrollMovement(const VTInt delta)
auto& textBuffer = _api.GetTextBuffer();
const auto bufferWidth = textBuffer.GetSize().Width();
const auto [topMargin, bottomMargin] = _GetVerticalMargins(viewport, true);
_ScrollRectVertically(textBuffer, { 0, topMargin, bufferWidth, bottomMargin + 1 }, delta);
const auto [leftMargin, rightMargin] = _GetHorizontalMargins(bufferWidth);
_ScrollRectVertically(textBuffer, { leftMargin, topMargin, rightMargin + 1, bottomMargin + 1 }, delta);
}
// Routine Description:
@@ -1634,10 +1726,11 @@ void AdaptDispatch::_SetColumnMode(const bool enable)
const auto viewportWidth = (enable ? DispatchTypes::s_sDECCOLMSetColumns : DispatchTypes::s_sDECCOLMResetColumns);
_api.ResizeWindow(viewportWidth, viewportHeight);
_modes.set(Mode::Column, enable);
_modes.reset(Mode::Origin);
_modes.reset(Mode::Origin, Mode::AllowDECSLRM);
CursorPosition(1, 1);
EraseInDisplay(DispatchTypes::EraseType::All);
_DoSetTopBottomScrollingMargins(0, 0);
_DoSetLeftRightScrollingMargins(0, 0);
}
}
@@ -1696,6 +1789,15 @@ bool AdaptDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param, con
case DispatchTypes::ModeParams::IRM_InsertReplaceMode:
_modes.set(Mode::InsertReplace, enable);
return true;
case DispatchTypes::ModeParams::LNM_LineFeedNewLineMode:
// VT apps expect that the system and input modes are the same, so if
// they become out of sync, we just act as if LNM mode isn't supported.
if (_api.GetSystemMode(ITerminalApi::Mode::LineFeed) == _terminalInput.GetInputMode(TerminalInput::Mode::LineFeed))
{
_api.SetSystemMode(ITerminalApi::Mode::LineFeed, enable);
_terminalInput.SetInputMode(TerminalInput::Mode::LineFeed, enable);
}
return true;
case DispatchTypes::ModeParams::DECCKM_CursorKeysMode:
_terminalInput.SetInputMode(TerminalInput::Mode::CursorKey, enable);
return !_PassThroughInputModes();
@@ -1719,7 +1821,7 @@ bool AdaptDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param, con
CursorPosition(1, 1);
return true;
case DispatchTypes::ModeParams::DECAWM_AutoWrapMode:
_api.SetAutoWrapMode(enable);
_api.SetSystemMode(ITerminalApi::Mode::AutoWrap, enable);
// Resetting DECAWM should also reset the delayed wrap flag.
if (!enable)
{
@@ -1744,6 +1846,17 @@ bool AdaptDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param, con
case DispatchTypes::ModeParams::DECBKM_BackarrowKeyMode:
_terminalInput.SetInputMode(TerminalInput::Mode::BackarrowKey, enable);
return !_PassThroughInputModes();
case DispatchTypes::ModeParams::DECLRMM_LeftRightMarginMode:
_modes.set(Mode::AllowDECSLRM, enable);
_DoSetLeftRightScrollingMargins(0, 0);
if (enable)
{
// If we've allowed left/right margins, we can't have line renditions.
const auto viewport = _api.GetViewport();
auto& textBuffer = _api.GetTextBuffer();
textBuffer.ResetLineRenditionRange(viewport.top, viewport.bottom);
}
return true;
case DispatchTypes::ModeParams::VT200_MOUSE_MODE:
_terminalInput.SetInputMode(TerminalInput::Mode::DefaultMouseTracking, enable);
return !_PassThroughInputModes();
@@ -1771,7 +1884,7 @@ bool AdaptDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param, con
_SetAlternateScreenBufferMode(enable);
return true;
case DispatchTypes::ModeParams::XTERM_BracketedPasteMode:
_api.SetBracketedPasteMode(enable);
_api.SetSystemMode(ITerminalApi::Mode::BracketedPaste, enable);
return !_api.IsConsolePty();
case DispatchTypes::ModeParams::W32IM_Win32InputMode:
_terminalInput.SetInputMode(TerminalInput::Mode::Win32, enable);
@@ -1820,6 +1933,14 @@ bool AdaptDispatch::RequestMode(const DispatchTypes::ModeParams param)
case DispatchTypes::ModeParams::IRM_InsertReplaceMode:
enabled = _modes.test(Mode::InsertReplace);
break;
case DispatchTypes::ModeParams::LNM_LineFeedNewLineMode:
// VT apps expect that the system and input modes are the same, so if
// they become out of sync, we just act as if LNM mode isn't supported.
if (_api.GetSystemMode(ITerminalApi::Mode::LineFeed) == _terminalInput.GetInputMode(TerminalInput::Mode::LineFeed))
{
enabled = _terminalInput.GetInputMode(TerminalInput::Mode::LineFeed);
}
break;
case DispatchTypes::ModeParams::DECCKM_CursorKeysMode:
enabled = _terminalInput.GetInputMode(TerminalInput::Mode::CursorKey);
break;
@@ -1840,7 +1961,7 @@ bool AdaptDispatch::RequestMode(const DispatchTypes::ModeParams param)
enabled = _modes.test(Mode::Origin);
break;
case DispatchTypes::ModeParams::DECAWM_AutoWrapMode:
enabled = _api.GetAutoWrapMode();
enabled = _api.GetSystemMode(ITerminalApi::Mode::AutoWrap);
break;
case DispatchTypes::ModeParams::DECARM_AutoRepeatMode:
enabled = _terminalInput.GetInputMode(TerminalInput::Mode::AutoRepeat);
@@ -1864,6 +1985,9 @@ bool AdaptDispatch::RequestMode(const DispatchTypes::ModeParams param)
case DispatchTypes::ModeParams::DECBKM_BackarrowKeyMode:
enabled = _terminalInput.GetInputMode(TerminalInput::Mode::BackarrowKey);
break;
case DispatchTypes::ModeParams::DECLRMM_LeftRightMarginMode:
enabled = _modes.test(Mode::AllowDECSLRM);
break;
case DispatchTypes::ModeParams::VT200_MOUSE_MODE:
enabled = _terminalInput.GetInputMode(TerminalInput::Mode::DefaultMouseTracking);
break;
@@ -1889,7 +2013,7 @@ bool AdaptDispatch::RequestMode(const DispatchTypes::ModeParams param)
enabled = _usingAltBuffer;
break;
case DispatchTypes::ModeParams::XTERM_BracketedPasteMode:
enabled = _api.GetBracketedPasteMode();
enabled = _api.GetSystemMode(ITerminalApi::Mode::BracketedPaste);
break;
case DispatchTypes::ModeParams::W32IM_Win32InputMode:
enabled = _terminalInput.GetInputMode(TerminalInput::Mode::Win32);
@@ -1934,17 +2058,18 @@ void AdaptDispatch::_InsertDeleteLineHelper(const int32_t delta)
const auto bufferWidth = textBuffer.GetSize().Width();
auto& cursor = textBuffer.GetCursor();
const auto col = cursor.GetPosition().x;
const auto row = cursor.GetPosition().y;
const auto [topMargin, bottomMargin] = _GetVerticalMargins(viewport, true);
if (row >= topMargin && row <= bottomMargin)
const auto [leftMargin, rightMargin] = _GetHorizontalMargins(bufferWidth);
if (row >= topMargin && row <= bottomMargin && col >= leftMargin && col <= rightMargin)
{
// We emulate inserting and deleting by scrolling the area between the cursor and the bottom margin.
_ScrollRectVertically(textBuffer, { 0, row, bufferWidth, bottomMargin + 1 }, delta);
_ScrollRectVertically(textBuffer, { leftMargin, row, rightMargin + 1, bottomMargin + 1 }, delta);
// The IL and DL controls are also expected to move the cursor to the left margin.
// For now this is just column 0, since we don't yet support DECSLRM.
cursor.SetXPosition(0);
cursor.SetXPosition(leftMargin);
_ApplyCursorMovementFlags(cursor);
}
}
@@ -2008,10 +2133,12 @@ bool AdaptDispatch::SetAnsiMode(const bool ansiMode)
// Arguments:
// - topMargin - the line number for the top margin.
// - bottomMargin - the line number for the bottom margin.
// - homeCursor - move the cursor to the home position.
// Return Value:
// - <none>
void AdaptDispatch::_DoSetTopBottomScrollingMargins(const VTInt topMargin,
const VTInt bottomMargin)
const VTInt bottomMargin,
const bool homeCursor)
{
// so notes time: (input -> state machine out -> adapter out -> conhost internal)
// having only a top param is legal ([3;r -> 3,0 -> 3,h -> 3,h,true)
@@ -2054,6 +2181,12 @@ void AdaptDispatch::_DoSetTopBottomScrollingMargins(const VTInt topMargin,
}
_scrollMargins.top = actualTop;
_scrollMargins.bottom = actualBottom;
// If requested, we may also need to move the cursor to the home
// position, but only if the requested margins were valid.
if (homeCursor)
{
CursorPosition(1, 1);
}
}
}
@@ -2070,10 +2203,90 @@ void AdaptDispatch::_DoSetTopBottomScrollingMargins(const VTInt topMargin,
bool AdaptDispatch::SetTopBottomScrollingMargins(const VTInt topMargin,
const VTInt bottomMargin)
{
// When this is called, the cursor should also be moved to home.
// Other functions that only need to set/reset the margins should call _DoSetTopBottomScrollingMargins
_DoSetTopBottomScrollingMargins(topMargin, bottomMargin);
CursorPosition(1, 1);
_DoSetTopBottomScrollingMargins(topMargin, bottomMargin, true);
return true;
}
// Routine Description:
// - DECSLRM - Set Scrolling Region
// This control function sets the left and right margins for the current page.
// You cannot perform scrolling outside the margins.
// Default: Margins are at the page limits.
// Arguments:
// - leftMargin - the column number for the left margin.
// - rightMargin - the column number for the right margin.
// - homeCursor - move the cursor to the home position.
// Return Value:
// - <none>
void AdaptDispatch::_DoSetLeftRightScrollingMargins(const VTInt leftMargin,
const VTInt rightMargin,
const bool homeCursor)
{
til::CoordType actualLeft = leftMargin;
til::CoordType actualRight = rightMargin;
const auto& textBuffer = _api.GetTextBuffer();
const auto bufferWidth = textBuffer.GetSize().Width();
// The default left margin is column 1
if (actualLeft == 0)
{
actualLeft = 1;
}
// The default right margin is the buffer width
if (actualRight == 0)
{
actualRight = bufferWidth;
}
// The left margin must be less than the right margin, and the
// right margin must be less than or equal to the buffer width
if (actualLeft < actualRight && actualRight <= bufferWidth)
{
if (actualLeft == 1 && actualRight == bufferWidth)
{
// Client requests setting margins to the entire screen
// - clear them instead of setting them.
actualLeft = 0;
actualRight = 0;
}
else
{
// In VT, the origin is 1,1. For our array, it's 0,0. So subtract 1.
actualLeft -= 1;
actualRight -= 1;
}
_scrollMargins.left = actualLeft;
_scrollMargins.right = actualRight;
// If requested, we may also need to move the cursor to the home
// position, but only if the requested margins were valid.
if (homeCursor)
{
CursorPosition(1, 1);
}
}
}
// Routine Description:
// - DECSLRM - Set Scrolling Region
// This control function sets the left and right margins for the current page.
// You cannot perform scrolling outside the margins.
// Default: Margins are at the page limits.
// Arguments:
// - leftMargin - the column number for the left margin.
// - rightMargin - the column number for the right margin.
// Return Value:
// - True.
bool AdaptDispatch::SetLeftRightScrollingMargins(const VTInt leftMargin,
const VTInt rightMargin)
{
if (_modes.test(Mode::AllowDECSLRM))
{
_DoSetLeftRightScrollingMargins(leftMargin, rightMargin, true);
}
else
{
// When DECSLRM isn't allowed, `CSI s` is interpreted as ANSISYSSC.
CursorSaveState();
}
return true;
}
@@ -2113,9 +2326,10 @@ bool AdaptDispatch::CarriageReturn()
void AdaptDispatch::_DoLineFeed(TextBuffer& textBuffer, const bool withReturn, const bool wrapForced)
{
const auto viewport = _api.GetViewport();
const auto [topMargin, bottomMargin] = _GetVerticalMargins(viewport, true);
const auto bufferWidth = textBuffer.GetSize().Width();
const auto bufferHeight = textBuffer.GetSize().Height();
const auto [topMargin, bottomMargin] = _GetVerticalMargins(viewport, true);
const auto [leftMargin, rightMargin] = _GetHorizontalMargins(bufferWidth);
auto& cursor = textBuffer.GetCursor();
const auto currentPosition = cursor.GetPosition();
@@ -2125,18 +2339,30 @@ void AdaptDispatch::_DoLineFeed(TextBuffer& textBuffer, const bool withReturn, c
// When explicitly moving down a row, clear the wrap status.
textBuffer.GetRowByOffset(currentPosition.y).SetWrapForced(wrapForced);
if (currentPosition.y != bottomMargin)
// If a carriage return was requested, we move to the leftmost column or
// the left margin, depending on whether we started within the margins.
if (withReturn)
{
// If we're not at the bottom margin then there's no scrolling,
// so we make sure we don't move past the bottom of the viewport.
const auto clampToMargin = currentPosition.y >= topMargin &&
currentPosition.y <= bottomMargin &&
currentPosition.x >= leftMargin;
newPosition.x = clampToMargin ? leftMargin : 0;
}
if (currentPosition.y != bottomMargin || newPosition.x < leftMargin || newPosition.x > rightMargin)
{
// If we're not at the bottom margin, or outside the horizontal margins,
// then there's no scrolling, so we make sure we don't move past the
// bottom of the viewport.
newPosition.y = std::min(currentPosition.y + 1, viewport.bottom - 1);
newPosition = textBuffer.ClampPositionWithinLine(newPosition);
}
else if (topMargin > viewport.top)
else if (topMargin > viewport.top || leftMargin > 0 || rightMargin < bufferWidth - 1)
{
// If the top margin isn't at the top of the viewport, then we're
// just scrolling the margin area and the cursor stays where it is.
_ScrollRectVertically(textBuffer, { 0, topMargin, bufferWidth, bottomMargin + 1 }, -1);
// If the top margin isn't at the top of the viewport, or the
// horizontal margins are set, then we're just scrolling the margin
// area and the cursor stays where it is.
_ScrollRectVertically(textBuffer, { leftMargin, topMargin, rightMargin + 1, bottomMargin + 1 }, -1);
}
else if (viewport.bottom < bufferHeight)
{
@@ -2183,9 +2409,6 @@ void AdaptDispatch::_DoLineFeed(TextBuffer& textBuffer, const bool withReturn, c
}
}
// If a carriage return was requested, we also move to the leftmost column.
newPosition.x = withReturn ? 0 : newPosition.x;
cursor.SetPosition(newPosition);
_ApplyCursorMovementFlags(cursor);
}
@@ -2203,7 +2426,7 @@ bool AdaptDispatch::LineFeed(const DispatchTypes::LineFeedType lineFeedType)
switch (lineFeedType)
{
case DispatchTypes::LineFeedType::DependsOnMode:
_DoLineFeed(textBuffer, _api.GetLineFeedMode(), false);
_DoLineFeed(textBuffer, _api.GetSystemMode(ITerminalApi::Mode::LineFeed), false);
return true;
case DispatchTypes::LineFeedType::WithoutReturn:
_DoLineFeed(textBuffer, false, false);
@@ -2229,14 +2452,15 @@ bool AdaptDispatch::ReverseLineFeed()
auto& textBuffer = _api.GetTextBuffer();
auto& cursor = textBuffer.GetCursor();
const auto cursorPosition = cursor.GetPosition();
const auto bufferWidth = textBuffer.GetSize().Width();
const auto [leftMargin, rightMargin] = _GetHorizontalMargins(bufferWidth);
const auto [topMargin, bottomMargin] = _GetVerticalMargins(viewport, true);
// If the cursor is at the top of the margin area, we shift the buffer
// contents down, to emulate inserting a line at that point.
if (cursorPosition.y == topMargin)
if (cursorPosition.y == topMargin && cursorPosition.x >= leftMargin && cursorPosition.x <= rightMargin)
{
const auto bufferWidth = textBuffer.GetSize().Width();
_ScrollRectVertically(textBuffer, { 0, topMargin, bufferWidth, bottomMargin + 1 }, 1);
_ScrollRectVertically(textBuffer, { leftMargin, topMargin, rightMargin + 1, bottomMargin + 1 }, 1);
}
else if (cursorPosition.y > viewport.top)
{
@@ -2290,12 +2514,19 @@ bool AdaptDispatch::ForwardTab(const VTInt numTabs)
{
auto& textBuffer = _api.GetTextBuffer();
auto& cursor = textBuffer.GetCursor();
const auto width = textBuffer.GetLineWidth(cursor.GetPosition().y);
auto column = cursor.GetPosition().x;
const auto row = cursor.GetPosition().y;
const auto width = textBuffer.GetLineWidth(row);
auto tabsPerformed = 0;
const auto viewport = _api.GetViewport();
const auto [topMargin, bottomMargin] = _GetVerticalMargins(viewport, true);
const auto [leftMargin, rightMargin] = _GetHorizontalMargins(width);
const auto clampToMargin = row >= topMargin && row <= bottomMargin && column <= rightMargin;
const auto maxColumn = clampToMargin ? rightMargin : width - 1;
_InitTabStopsForWidth(width);
while (column + 1 < width && tabsPerformed < numTabs)
while (column < maxColumn && tabsPerformed < numTabs)
{
column++;
if (til::at(_tabStopColumns, column))
@@ -2332,12 +2563,19 @@ bool AdaptDispatch::BackwardsTab(const VTInt numTabs)
{
auto& textBuffer = _api.GetTextBuffer();
auto& cursor = textBuffer.GetCursor();
const auto width = textBuffer.GetLineWidth(cursor.GetPosition().y);
auto column = cursor.GetPosition().x;
const auto row = cursor.GetPosition().y;
const auto width = textBuffer.GetLineWidth(row);
auto tabsPerformed = 0;
const auto viewport = _api.GetViewport();
const auto [topMargin, bottomMargin] = _GetVerticalMargins(viewport, true);
const auto [leftMargin, rightMargin] = _GetHorizontalMargins(width);
const auto clampToMargin = row >= topMargin && row <= bottomMargin && column >= leftMargin;
const auto minColumn = clampToMargin ? leftMargin : 0;
_InitTabStopsForWidth(width);
while (column > 0 && tabsPerformed < numTabs)
while (column > minColumn && tabsPerformed < numTabs)
{
column--;
if (til::at(_tabStopColumns, column))
@@ -2589,13 +2827,18 @@ bool AdaptDispatch::AcceptC1Controls(const bool enabled)
bool AdaptDispatch::SoftReset()
{
_api.GetTextBuffer().GetCursor().SetIsVisible(true); // Cursor enabled.
_modes.reset(Mode::InsertReplace, Mode::Origin); // Replace mode; Absolute cursor addressing.
_api.SetAutoWrapMode(true); // Wrap at end of line.
// Replace mode; Absolute cursor addressing; Disallow left/right margins.
_modes.reset(Mode::InsertReplace, Mode::Origin, Mode::AllowDECSLRM);
_api.SetSystemMode(ITerminalApi::Mode::AutoWrap, true); // Wrap at end of line.
_terminalInput.SetInputMode(TerminalInput::Mode::CursorKey, false); // Normal characters.
_terminalInput.SetInputMode(TerminalInput::Mode::Keypad, false); // Numeric characters.
// Top margin = 1; bottom margin = page length.
_DoSetTopBottomScrollingMargins(0, 0);
// Left margin = 1; right margin = page width.
_DoSetLeftRightScrollingMargins(0, 0);
_termOutput = {}; // Reset all character set designations.
if (_initialCodePage.has_value())
@@ -2663,11 +2906,20 @@ bool AdaptDispatch::HardReset()
// Cursor to 1,1 - the Soft Reset guarantees this is absolute
CursorPosition(1, 1);
// We only reset the system line feed mode if the input mode is set. If it
// isn't set, that either means they're both reset, and there's nothing for
// us to do, or they're out of sync, which implies the system mode was set
// via the console API, so it's not our responsibility.
if (_terminalInput.GetInputMode(TerminalInput::Mode::LineFeed))
{
_api.SetSystemMode(ITerminalApi::Mode::LineFeed, false);
}
// Reset input modes to their initial state
_terminalInput.ResetInputModes();
// Reset bracketed paste mode
_api.SetBracketedPasteMode(false);
_api.SetSystemMode(ITerminalApi::Mode::BracketedPaste, false);
// Restore cursor blinking mode.
_api.GetTextBuffer().GetCursor().SetBlinkingAllowed(true);
@@ -2718,10 +2970,11 @@ bool AdaptDispatch::ScreenAlignmentPattern()
auto attr = textBuffer.GetCurrentAttributes();
attr.SetStandardErase();
_api.SetTextAttributes(attr);
// Reset the origin mode to absolute.
_modes.reset(Mode::Origin);
// Reset the origin mode to absolute, and disallow left/right margins.
_modes.reset(Mode::Origin, Mode::AllowDECSLRM);
// Clear the scrolling margins.
_DoSetTopBottomScrollingMargins(0, 0);
_DoSetLeftRightScrollingMargins(0, 0);
// Set the cursor position to home.
CursorPosition(1, 1);
@@ -3623,6 +3876,9 @@ ITermDispatch::StringHandler AdaptDispatch::RequestSetting()
case VTID("r"):
_ReportDECSTBMSetting();
break;
case VTID("s"):
_ReportDECSLRMSetting();
break;
case VTID("\"q"):
_ReportDECSCASetting();
break;
@@ -3751,6 +4007,30 @@ void AdaptDispatch::_ReportDECSTBMSetting()
_api.ReturnResponse({ response.data(), response.size() });
}
// Method Description:
// - Reports the DECSLRM margin range in response to a DECRQSS query.
// Arguments:
// - None
// Return Value:
// - None
void AdaptDispatch::_ReportDECSLRMSetting()
{
using namespace std::string_view_literals;
// A valid response always starts with DCS 1 $ r.
fmt::basic_memory_buffer<wchar_t, 64> response;
response.append(L"\033P1$r"sv);
const auto bufferWidth = _api.GetTextBuffer().GetSize().Width();
const auto [marginLeft, marginRight] = _GetHorizontalMargins(bufferWidth);
// VT origin is at 1,1 so we need to add 1 to these margins.
fmt::format_to(std::back_inserter(response), FMT_COMPILE(L"{};{}"), marginLeft + 1, marginRight + 1);
// The 's' indicates this is an DECSLRM response, and ST ends the sequence.
response.append(L"s\033\\"sv);
_api.ReturnResponse({ response.data(), response.size() });
}
// Method Description:
// - Reports the DECSCA protected attribute in response to a DECRQSS query.
// Arguments:
@@ -3899,9 +4179,10 @@ void AdaptDispatch::_ReportCursorInformation()
cursorPosition.x++;
cursorPosition.y++;
// If the origin mode is relative, line numbers start at top of the scrolling region.
// If the origin mode is set, the cursor is relative to the margin origin.
if (_modes.test(Mode::Origin))
{
cursorPosition.x -= _GetHorizontalMargins(textBuffer.GetSize().Width()).first;
cursorPosition.y -= _GetVerticalMargins(viewport, false).first;
}

View File

@@ -90,6 +90,8 @@ namespace Microsoft::Console::VirtualTerminal
bool SetAnsiMode(const bool ansiMode) override; // DECANM
bool SetTopBottomScrollingMargins(const VTInt topMargin,
const VTInt bottomMargin) override; // DECSTBM
bool SetLeftRightScrollingMargins(const VTInt leftMargin,
const VTInt rightMargin) override; // DECSLRM
bool WarningBell() override; // BEL
bool CarriageReturn() override; // CR
bool LineFeed(const DispatchTypes::LineFeedType lineFeedType) override; // IND, NEL, LF, FF, VT
@@ -163,6 +165,7 @@ namespace Microsoft::Console::VirtualTerminal
Origin,
Column,
AllowDECCOLM,
AllowDECSLRM,
RectangularChangeExtent
};
enum class ScrollDirection
@@ -201,6 +204,7 @@ namespace Microsoft::Console::VirtualTerminal
void _WriteToBuffer(const std::wstring_view string);
std::pair<int, int> _GetVerticalMargins(const til::rect& viewport, const bool absolute) noexcept;
std::pair<int, int> _GetHorizontalMargins(const til::CoordType bufferWidth) noexcept;
bool _CursorMovePosition(const Offset rowOffset, const Offset colOffset, const bool clampInMargins);
void _ApplyCursorMovementFlags(Cursor& cursor) noexcept;
void _FillRect(TextBuffer& textBuffer, const til::rect& fillRect, const wchar_t fillChar, const TextAttribute fillAttrs);
@@ -217,7 +221,12 @@ namespace Microsoft::Console::VirtualTerminal
void _ScrollMovement(const VTInt delta);
void _DoSetTopBottomScrollingMargins(const VTInt topMargin,
const VTInt bottomMargin);
const VTInt bottomMargin,
const bool homeCursor = false);
void _DoSetLeftRightScrollingMargins(const VTInt leftMargin,
const VTInt rightMargin,
const bool homeCursor = false);
void _DoLineFeed(TextBuffer& textBuffer, const bool withReturn, const bool wrapForced);
void _OperatingStatus() const;
@@ -239,6 +248,7 @@ namespace Microsoft::Console::VirtualTerminal
void _ReportSGRSetting() const;
void _ReportDECSTBMSetting();
void _ReportDECSLRMSetting();
void _ReportDECSCASetting() const;
void _ReportDECSACESetting() const;
void _ReportDECACSetting(const VTInt itemNumber) const;

View File

@@ -47,6 +47,7 @@ public:
bool SetKeypadMode(const bool /*applicationMode*/) override { return false; } // DECKPAM, DECKPNM
bool SetAnsiMode(const bool /*ansiMode*/) override { return false; } // DECANM
bool SetTopBottomScrollingMargins(const VTInt /*topMargin*/, const VTInt /*bottomMargin*/) override { return false; } // DECSTBM
bool SetLeftRightScrollingMargins(const VTInt /*leftMargin*/, const VTInt /*rightMargin*/) override { return false; } // DECSLRM
bool WarningBell() override { return false; } // BEL
bool CarriageReturn() override { return false; } // CR
bool LineFeed(const DispatchTypes::LineFeedType /*lineFeedType*/) override { return false; } // IND, NEL, LF, FF, VT

View File

@@ -96,17 +96,6 @@ public:
Log::Comment(L"SetViewportPosition MOCK called...");
}
void SetAutoWrapMode(const bool /*wrapAtEOL*/) override
{
Log::Comment(L"SetAutoWrapMode MOCK called...");
}
bool GetAutoWrapMode() const override
{
Log::Comment(L"GetAutoWrapMode MOCK called...");
return true;
}
bool IsVtInputEnabled() const override
{
return false;
@@ -121,17 +110,23 @@ public:
_textBuffer->SetCurrentAttributes(attrs);
}
void SetSystemMode(const Mode mode, const bool enabled)
{
Log::Comment(L"SetSystemMode MOCK called...");
_systemMode.set(mode, enabled);
}
bool GetSystemMode(const Mode mode) const
{
Log::Comment(L"GetSystemMode MOCK called...");
return _systemMode.test(mode);
}
void WarningBell() override
{
Log::Comment(L"WarningBell MOCK called...");
}
bool GetLineFeedMode() const override
{
Log::Comment(L"GetLineFeedMode MOCK called...");
return _getLineFeedModeResult;
}
void SetWindowTitle(const std::wstring_view title)
{
Log::Comment(L"SetWindowTitle MOCK called...");
@@ -184,17 +179,6 @@ public:
return _expectedOutputCP;
}
void SetBracketedPasteMode(const bool /*enabled*/) override
{
Log::Comment(L"SetBracketedPasteMode MOCK called...");
}
bool GetBracketedPasteMode() const override
{
Log::Comment(L"GetBracketedPasteMode MOCK called...");
return false;
}
void CopyToClipboard(const std::wstring_view /*content*/)
{
Log::Comment(L"CopyToClipboard MOCK called...");
@@ -386,7 +370,7 @@ public:
bool _setTextAttributesResult = false;
bool _returnResponseResult = false;
bool _getLineFeedModeResult = false;
til::enumset<Mode> _systemMode{ Mode::AutoWrap };
bool _setWindowTitleResult = false;
std::wstring_view _expectedWindowTitle{};
@@ -1629,6 +1613,22 @@ public:
requestSetting(L"r");
_testGetSet->ValidateInputEvent(L"\033P1$r1;25r\033\\");
Log::Comment(L"Requesting DECSLRM margins (5 to 10).");
_testGetSet->PrepData();
// We need to enable DECLRMM for horizontal margins to work.
_pDispatch->SetMode(DispatchTypes::DECLRMM_LeftRightMarginMode);
_pDispatch->SetLeftRightScrollingMargins(5, 10);
requestSetting(L"s");
_testGetSet->ValidateInputEvent(L"\033P1$r5;10s\033\\");
Log::Comment(L"Requesting DECSLRM margins (full width).");
_testGetSet->PrepData();
_pDispatch->SetLeftRightScrollingMargins(0, 0);
requestSetting(L"s");
_testGetSet->ValidateInputEvent(L"\033P1$r1;100s\033\\");
// Reset DECLRMM once we're done with horizontal margin testing.
_pDispatch->ResetMode(DispatchTypes::DECLRMM_LeftRightMarginMode);
Log::Comment(L"Requesting SGR attributes (default).");
_testGetSet->PrepData();
TextAttribute attribute = {};
@@ -1755,7 +1755,39 @@ public:
_testGetSet->ValidateInputEvent(L"\033P0$r\033\\");
}
TEST_METHOD(RequestModeTests)
TEST_METHOD(RequestStandardModeTests)
{
// The mode numbers below correspond to the ANSIStandardMode values
// in the ModeParams enum in DispatchTypes.hpp.
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:modeNumber", L"{4, 20}")
END_TEST_METHOD_PROPERTIES()
VTInt modeNumber;
VERIFY_SUCCEEDED_RETURN(TestData::TryGetValue(L"modeNumber", modeNumber));
const auto mode = DispatchTypes::ANSIStandardMode(modeNumber);
// DISABLE_
Log::Comment(NoThrowString().Format(L"Setting standard mode %d", modeNumber));
_testGetSet->PrepData();
VERIFY_IS_TRUE(_pDispatch->SetMode(mode));
VERIFY_IS_TRUE(_pDispatch->RequestMode(mode));
wchar_t expectedResponse[20];
swprintf_s(expectedResponse, ARRAYSIZE(expectedResponse), L"\x1b[%d;1$y", modeNumber);
_testGetSet->ValidateInputEvent(expectedResponse);
Log::Comment(NoThrowString().Format(L"Resetting standard mode %d", modeNumber));
_testGetSet->PrepData();
VERIFY_IS_TRUE(_pDispatch->ResetMode(mode));
VERIFY_IS_TRUE(_pDispatch->RequestMode(mode));
swprintf_s(expectedResponse, ARRAYSIZE(expectedResponse), L"\x1b[%d;2$y", modeNumber);
_testGetSet->ValidateInputEvent(expectedResponse);
}
TEST_METHOD(RequestPrivateModeTests)
{
// The mode numbers below correspond to the DECPrivateMode values
// in the ModeParams enum in DispatchTypes.hpp. We don't include
@@ -1763,7 +1795,7 @@ public:
// and DECRQM would not then be applicable.
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:modeNumber", L"{1, 3, 5, 6, 8, 12, 25, 40, 66, 67, 1000, 1002, 1003, 1004, 1005, 1006, 1007, 1049, 9001}")
TEST_METHOD_PROPERTY(L"Data:modeNumber", L"{1, 3, 5, 6, 7, 8, 12, 25, 40, 66, 67, 69, 1000, 1002, 1003, 1004, 1005, 1006, 1007, 1049, 2004, 9001}")
END_TEST_METHOD_PROPERTIES()
VTInt modeNumber;
@@ -2262,13 +2294,13 @@ public:
VERIFY_ARE_EQUAL(til::point(0, 1), cursor.GetPosition());
Log::Comment(L"Test 3: Line feed depends on mode, and mode reset.");
_testGetSet->_getLineFeedModeResult = false;
_testGetSet->_systemMode.reset(ITerminalApi::Mode::LineFeed);
cursor.SetPosition({ 10, 0 });
VERIFY_IS_TRUE(_pDispatch->LineFeed(DispatchTypes::LineFeedType::DependsOnMode));
VERIFY_ARE_EQUAL(til::point(10, 1), cursor.GetPosition());
Log::Comment(L"Test 4: Line feed depends on mode, and mode set.");
_testGetSet->_getLineFeedModeResult = true;
_testGetSet->_systemMode.set(ITerminalApi::Mode::LineFeed);
cursor.SetPosition({ 10, 0 });
VERIFY_IS_TRUE(_pDispatch->LineFeed(DispatchTypes::LineFeedType::DependsOnMode));
VERIFY_ARE_EQUAL(til::point(0, 1), cursor.GetPosition());

View File

@@ -600,6 +600,15 @@ bool TerminalInput::HandleKey(const IInputEvent* const pInEvent)
return true;
}
// When the Line Feed mode is set, a VK_RETURN key should send both CR and LF.
// When reset, we fall through to the default behavior, which is to send just
// CR, or when the Ctrl modifier is pressed, just LF.
if (keyEvent.GetVirtualKeyCode() == VK_RETURN && _inputMode.test(Mode::LineFeed))
{
_SendInputSequence(L"\r\n");
return true;
}
// Many keyboard layouts have an AltGr key, which makes widely used characters accessible.
// For instance on a German keyboard layout "[" is written by pressing AltGr+8.
// Furthermore Ctrl+Alt is traditionally treated as an alternative way to AltGr by Windows.

View File

@@ -38,6 +38,7 @@ namespace Microsoft::Console::VirtualTerminal
enum class Mode : size_t
{
LineFeed,
Ansi,
AutoRepeat,
Keypad,

View File

@@ -491,10 +491,15 @@ bool OutputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParamete
success = _dispatch->CursorPosition(parameters.at(0), parameters.at(1));
TermTelemetry::Instance().Log(TermTelemetry::Codes::CUP);
break;
case CsiActionCodes::DECSTBM_SetScrollingRegion:
case CsiActionCodes::DECSTBM_SetTopBottomMargins:
success = _dispatch->SetTopBottomScrollingMargins(parameters.at(0).value_or(0), parameters.at(1).value_or(0));
TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSTBM);
break;
case CsiActionCodes::DECSLRM_SetLeftRightMargins:
// Note that this can also be ANSISYSSC, depending on the state of DECLRMM.
success = _dispatch->SetLeftRightScrollingMargins(parameters.at(0).value_or(0), parameters.at(1).value_or(0));
TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSTBM);
break;
case CsiActionCodes::ICH_InsertCharacter:
success = _dispatch->InsertCharacter(parameters.at(0));
TermTelemetry::Instance().Log(TermTelemetry::Codes::ICH);
@@ -588,12 +593,8 @@ bool OutputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParamete
success = _dispatch->ScrollDown(parameters.at(0));
TermTelemetry::Instance().Log(TermTelemetry::Codes::SD);
break;
case CsiActionCodes::ANSISYSSC_CursorSave:
success = parameters.empty() && _dispatch->CursorSaveState();
TermTelemetry::Instance().Log(TermTelemetry::Codes::ANSISYSSC);
break;
case CsiActionCodes::ANSISYSRC_CursorRestore:
success = parameters.empty() && _dispatch->CursorRestoreState();
success = _dispatch->CursorRestoreState();
TermTelemetry::Instance().Log(TermTelemetry::Codes::ANSISYSRC);
break;
case CsiActionCodes::IL_InsertLine:

View File

@@ -137,8 +137,8 @@ namespace Microsoft::Console::VirtualTerminal
SGR_SetGraphicsRendition = VTID("m"),
DSR_DeviceStatusReport = VTID("n"),
DSR_PrivateDeviceStatusReport = VTID("?n"),
DECSTBM_SetScrollingRegion = VTID("r"),
ANSISYSSC_CursorSave = VTID("s"), // NOTE: Overlaps with DECLRMM/DECSLRM. Fix when/if implemented.
DECSTBM_SetTopBottomMargins = VTID("r"),
DECSLRM_SetLeftRightMargins = VTID("s"),
DTTERM_WindowManipulation = VTID("t"), // NOTE: Overlaps with DECSLPP. Fix when/if implemented.
ANSISYSRC_CursorRestore = VTID("u"),
DECREQTPARM_RequestTerminalParameters = VTID("x"),

View File

@@ -241,9 +241,9 @@ void TermTelemetry::WriteFinalTraceLog() const
TraceLoggingUInt32(_uiTimesUsed[DL], "DL"),
TraceLoggingUInt32(_uiTimesUsed[SU], "SU"),
TraceLoggingUInt32(_uiTimesUsed[SD], "SD"),
TraceLoggingUInt32(_uiTimesUsed[ANSISYSSC], "ANSISYSSC"),
TraceLoggingUInt32(_uiTimesUsed[ANSISYSRC], "ANSISYSRC"),
TraceLoggingUInt32(_uiTimesUsed[DECSTBM], "DECSTBM"),
TraceLoggingUInt32(_uiTimesUsed[DECSLRM], "DECSLRM"),
TraceLoggingUInt32(_uiTimesUsed[NEL], "NEL"),
TraceLoggingUInt32(_uiTimesUsed[IND], "IND"),
TraceLoggingUInt32(_uiTimesUsed[RI], "RI"),

View File

@@ -66,11 +66,11 @@ namespace Microsoft::Console::VirtualTerminal
DCH,
SU,
SD,
ANSISYSSC,
ANSISYSRC,
IL,
DL,
DECSTBM,
DECSLRM,
NEL,
IND,
RI,

View File

@@ -1657,12 +1657,10 @@ class StateMachineExternalTest final
pDispatch->ClearState();
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');
mach.ProcessCharacter(L's');
VERIFY_IS_TRUE(pDispatch->_cursorSave);
pDispatch->ClearState();
// Note that CSI s is dispatched as SetLeftRightScrollingMargins rather
// than CursorSaveState, so we don't test that here. The CursorSaveState
// will only be triggered by this sequence (in AdaptDispatch) when the
// Left-Right-Margin mode (DECLRMM) is disabled.
mach.ProcessCharacter(AsciiChars::ESC);
mach.ProcessCharacter(L'[');

View File

@@ -112,4 +112,7 @@ namespace Microsoft::Console::Utils
// testing easier.
std::wstring_view TrimPaste(std::wstring_view textView) noexcept;
// Same deal, but in TerminalPage::_evaluatePathForCwd
std::wstring EvaluateStartingDirectory(std::wstring_view cwd, std::wstring_view startingDirectory);
}

View File

@@ -33,6 +33,8 @@ class UtilsTests
TEST_METHOD(TestTrimTrailingWhitespace);
TEST_METHOD(TestDontTrimTrailingWhitespace);
TEST_METHOD(TestEvaluateStartingDirectory);
void _VerifyXTermColorResult(const std::wstring_view wstr, DWORD colorValue);
void _VerifyXTermColorInvalid(const std::wstring_view wstr);
};
@@ -546,3 +548,65 @@ void UtilsTests::TestDontTrimTrailingWhitespace()
// * trim when there's a tab followed by only whitespace
// * not trim then there's a tab in the middle, and the string ends in whitespace
}
void UtilsTests::TestEvaluateStartingDirectory()
{
// Continue on failures
const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope;
auto test = [](auto& expected, auto& cwd, auto& startingDir) {
VERIFY_ARE_EQUAL(expected, EvaluateStartingDirectory(cwd, startingDir));
};
// A NOTE: EvaluateStartingDirectory makes no attempt to cannonicalize the
// path. So if you do any sort of relative paths, it'll literally just
// append.
{
std::wstring cwd = L"C:\\Windows\\System32";
// Literally blank
test(L"C:\\Windows\\System32\\", cwd, L"");
// Absolute Windows path
test(L"C:\\Windows", cwd, L"C:\\Windows");
test(L"C:/Users/migrie", cwd, L"C:/Users/migrie");
// Relative Windows path
test(L"C:\\Windows\\System32\\.", cwd, L"."); // ?
test(L"C:\\Windows\\System32\\.\\System32", cwd, L".\\System32"); // ?
test(L"C:\\Windows\\System32\\./dev", cwd, L"./dev");
// WSL '~' path
test(L"~", cwd, L"~");
test(L"~/dev", cwd, L"~/dev");
// WSL or Windows / path - this will ultimately be evaluated by the connection
test(L"/", cwd, L"/");
test(L"/dev", cwd, L"/dev");
}
{
std::wstring cwd = L"C:/Users/migrie";
// Literally blank
test(L"C:/Users/migrie\\", cwd, L"");
// Absolute Windows path
test(L"C:\\Windows", cwd, L"C:\\Windows");
test(L"C:/Users/migrie", cwd, L"C:/Users/migrie");
// Relative Windows path
test(L"C:/Users/migrie\\.", cwd, L"."); // ?
test(L"C:/Users/migrie\\.\\System32", cwd, L".\\System32"); // ?
test(L"C:/Users/migrie\\./dev", cwd, L"./dev");
// WSL '~' path
test(L"~", cwd, L"~");
test(L"~/dev", cwd, L"~/dev");
// WSL or Windows / path - this will ultimately be evaluated by the connection
test(L"/", cwd, L"/");
test(L"/dev", cwd, L"/dev");
}
}

View File

@@ -729,7 +729,7 @@ std::tuple<std::wstring, std::wstring> Utils::MangleStartingDirectoryForWSL(std:
const auto terminator{ commandLine.find_first_of(LR"(" )", 1) }; // look past the first character in case it starts with "
const auto start{ til::at(commandLine, 0) == L'"' ? 1 : 0 };
const std::filesystem::path executablePath{ commandLine.substr(start, terminator - start) };
const auto executableFilename{ executablePath.filename().wstring() };
const auto executableFilename{ executablePath.filename() };
if (executableFilename == L"wsl" || executableFilename == L"wsl.exe")
{
// We've got a WSL -- let's just make sure it's the right one.
@@ -784,7 +784,7 @@ std::tuple<std::wstring, std::wstring> Utils::MangleStartingDirectoryForWSL(std:
}
return {
fmt::format(LR"("{}" --cd "{}" {})", executablePath.wstring(), mangledDirectory, arguments),
fmt::format(LR"("{}" --cd "{}" {})", executablePath.native(), mangledDirectory, arguments),
std::wstring{}
};
}
@@ -828,3 +828,27 @@ std::wstring_view Utils::TrimPaste(std::wstring_view textView) noexcept
return textView.substr(0, lastNonSpace + 1);
}
std::wstring Utils::EvaluateStartingDirectory(
std::wstring_view currentDirectory,
std::wstring_view startingDirectory)
{
std::wstring resultPath{ startingDirectory };
// We only want to resolve the new WD against the CWD if it doesn't look
// like a Linux path (see GH#592)
// Append only if it DOESN'T look like a linux-y path. A linux-y path starts
// with `~` or `/`.
const bool looksLikeLinux =
resultPath.size() >= 1 &&
(til::at(resultPath, 0) == L'~' || til::at(resultPath, 0) == L'/');
if (!looksLikeLinux)
{
std::filesystem::path cwd{ currentDirectory };
cwd /= startingDirectory;
resultPath = cwd.wstring();
}
return resultPath;
}

View File

@@ -81,7 +81,7 @@ static wchar_t* _ConsoleHostPath()
// We tried the architecture infix version and failed, fall back to conhost.
return _InboxConsoleHostPath();
}
auto modulePathAsString{ modulePath.wstring() };
const auto& modulePathAsString = modulePath.native();
return wil::make_process_heap_string_nothrow(modulePathAsString.data(), modulePathAsString.size());
#endif // __INSIDE_WINDOWS
}();

View File

@@ -32,6 +32,10 @@ if (%1) == (rel) (
echo Manually building release
set _LAST_BUILD_CONF=Release
)
if (%1) == (audit) (
echo Manually building audit mode
set _LAST_BUILD_CONF=AuditMode
)
if (%1) == (no_clean) (
set _MSBUILD_TARGET=Build
)