mirror of
https://github.com/microsoft/terminal.git
synced 2026-05-20 05:54:23 +00:00
Compare commits
34 Commits
dev/cazamo
...
v1.21.1772
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a15717f69 | ||
|
|
846a8aa7a6 | ||
|
|
9cccef7224 | ||
|
|
196a60c7c2 | ||
|
|
aff77650e4 | ||
|
|
8f3b38fb81 | ||
|
|
d960c89634 | ||
|
|
332c5ad937 | ||
|
|
5f6783b01f | ||
|
|
334335fd43 | ||
|
|
30df31fd9b | ||
|
|
0030a1d52e | ||
|
|
082d166ac6 | ||
|
|
3e661bfad2 | ||
|
|
7b59a9eafb | ||
|
|
8038dc67cd | ||
|
|
bc365c9542 | ||
|
|
0d8e43c697 | ||
|
|
967dcfc5b0 | ||
|
|
e56a557c93 | ||
|
|
52eba74316 | ||
|
|
27de22d0e5 | ||
|
|
52ab4c467a | ||
|
|
ece2ef7f84 | ||
|
|
ef283da5cb | ||
|
|
18c3f72fe0 | ||
|
|
4d1b543a9e | ||
|
|
d838ce5e67 | ||
|
|
1f54562f04 | ||
|
|
f0fc1b5701 | ||
|
|
a6819c5384 | ||
|
|
3c6b2af578 | ||
|
|
fe237afc25 | ||
|
|
3600ee42f0 |
@@ -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.21</PGOBranch>
|
||||
|
||||
<!-- Mandatory. Name of the NuGet package which will contain PGO databases for consumption by build system. -->
|
||||
<PGOPackageName>Microsoft.Internal.Windows.Terminal.PGODatabase</PGOPackageName>
|
||||
|
||||
@@ -100,36 +100,32 @@ jobs:
|
||||
flattenFolders: true
|
||||
|
||||
- ${{ if eq(parameters.codeSign, true) }}:
|
||||
- task: EsrpCodeSigning@5
|
||||
displayName: Submit *.nupkg to ESRP for code signing
|
||||
inputs:
|
||||
ConnectedServiceName: ${{ parameters.signingIdentity.serviceName }}
|
||||
AppRegistrationClientId: ${{ parameters.signingIdentity.appId }}
|
||||
AppRegistrationTenantId: ${{ parameters.signingIdentity.tenantId }}
|
||||
AuthAKVName: ${{ parameters.signingIdentity.akvName }}
|
||||
AuthCertName: ${{ parameters.signingIdentity.authCertName }}
|
||||
AuthSignCertName: ${{ parameters.signingIdentity.signCertName }}
|
||||
FolderPath: $(Build.ArtifactStagingDirectory)/nupkg
|
||||
Pattern: '*.nupkg'
|
||||
UseMinimatch: true
|
||||
signConfigType: inlineSignParams
|
||||
inlineOperation: >-
|
||||
[
|
||||
{
|
||||
"KeyCode": "CP-401405",
|
||||
"OperationCode": "NuGetSign",
|
||||
"Parameters": {},
|
||||
"ToolName": "sign",
|
||||
"ToolVersion": "1.0"
|
||||
},
|
||||
{
|
||||
"KeyCode": "CP-401405",
|
||||
"OperationCode": "NuGetVerify",
|
||||
"Parameters": {},
|
||||
"ToolName": "sign",
|
||||
"ToolVersion": "1.0"
|
||||
}
|
||||
]
|
||||
- template: steps-esrp-signing.yml
|
||||
parameters:
|
||||
displayName: Submit *.nupkg to ESRP for code signing
|
||||
signingIdentity: ${{ parameters.signingIdentity }}
|
||||
inputs:
|
||||
FolderPath: $(Build.ArtifactStagingDirectory)/nupkg
|
||||
Pattern: '*.nupkg'
|
||||
UseMinimatch: true
|
||||
signConfigType: inlineSignParams
|
||||
inlineOperation: >-
|
||||
[
|
||||
{
|
||||
"KeyCode": "CP-401405",
|
||||
"OperationCode": "NuGetSign",
|
||||
"Parameters": {},
|
||||
"ToolName": "sign",
|
||||
"ToolVersion": "1.0"
|
||||
},
|
||||
{
|
||||
"KeyCode": "CP-401405",
|
||||
"OperationCode": "NuGetVerify",
|
||||
"Parameters": {},
|
||||
"ToolName": "sign",
|
||||
"ToolVersion": "1.0"
|
||||
}
|
||||
]
|
||||
|
||||
- ${{ if eq(parameters.generateSbom, true) }}:
|
||||
- task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0
|
||||
|
||||
@@ -242,18 +242,14 @@ jobs:
|
||||
|
||||
# Code-sign everything we just put together.
|
||||
# We run the signing in Terminal.BinDir, because all of the signing batches are relative to the final architecture/configuration output folder.
|
||||
- task: EsrpCodeSigning@5
|
||||
displayName: Submit Signing Request
|
||||
inputs:
|
||||
ConnectedServiceName: ${{ parameters.signingIdentity.serviceName }}
|
||||
AppRegistrationClientId: ${{ parameters.signingIdentity.appId }}
|
||||
AppRegistrationTenantId: ${{ parameters.signingIdentity.tenantId }}
|
||||
AuthAKVName: ${{ parameters.signingIdentity.akvName }}
|
||||
AuthCertName: ${{ parameters.signingIdentity.authCertName }}
|
||||
AuthSignCertName: ${{ parameters.signingIdentity.signCertName }}
|
||||
FolderPath: '$(Terminal.BinDir)'
|
||||
signType: batchSigning
|
||||
batchSignPolicyFile: '$(Build.SourcesDirectory)/ESRPSigningConfig.json'
|
||||
- template: steps-esrp-signing.yml
|
||||
parameters:
|
||||
displayName: Submit Signing Request
|
||||
signingIdentity: ${{ parameters.signingIdentity }}
|
||||
inputs:
|
||||
FolderPath: '$(Terminal.BinDir)'
|
||||
signType: batchSigning
|
||||
batchSignPolicyFile: '$(Build.SourcesDirectory)/ESRPSigningConfig.json'
|
||||
|
||||
# We only need to re-pack the MSIX if we actually signed, so this can stay in the codeSign conditional
|
||||
- ${{ if or(parameters.buildTerminal, parameters.buildEverything) }}:
|
||||
|
||||
@@ -97,45 +97,41 @@ jobs:
|
||||
displayName: Create msixbundle
|
||||
|
||||
- ${{ if eq(parameters.codeSign, true) }}:
|
||||
- task: EsrpCodeSigning@5
|
||||
displayName: Submit *.msixbundle to ESRP for code signing
|
||||
inputs:
|
||||
ConnectedServiceName: ${{ parameters.signingIdentity.serviceName }}
|
||||
AppRegistrationClientId: ${{ parameters.signingIdentity.appId }}
|
||||
AppRegistrationTenantId: ${{ parameters.signingIdentity.tenantId }}
|
||||
AuthAKVName: ${{ parameters.signingIdentity.akvName }}
|
||||
AuthCertName: ${{ parameters.signingIdentity.authCertName }}
|
||||
AuthSignCertName: ${{ parameters.signingIdentity.signCertName }}
|
||||
FolderPath: $(System.ArtifactsDirectory)\bundle
|
||||
Pattern: $(BundleStemName)*.msixbundle
|
||||
UseMinimatch: true
|
||||
signConfigType: inlineSignParams
|
||||
inlineOperation: >-
|
||||
[
|
||||
{
|
||||
"KeyCode": "Dynamic",
|
||||
"CertTemplateName": "WINMSAPP1ST",
|
||||
"CertSubjectName": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US",
|
||||
"OperationCode": "SigntoolSign",
|
||||
"Parameters": {
|
||||
"OpusName": "Microsoft",
|
||||
"OpusInfo": "http://www.microsoft.com",
|
||||
"FileDigest": "/fd \"SHA256\"",
|
||||
"TimeStamp": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256"
|
||||
},
|
||||
"ToolName": "sign",
|
||||
"ToolVersion": "1.0"
|
||||
},
|
||||
{
|
||||
"KeyCode": "Dynamic",
|
||||
"CertTemplateName": "WINMSAPP1ST",
|
||||
"CertSubjectName": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US",
|
||||
"OperationCode": "SigntoolVerify",
|
||||
"Parameters": {},
|
||||
"ToolName": "sign",
|
||||
"ToolVersion": "1.0"
|
||||
}
|
||||
]
|
||||
- template: steps-esrp-signing.yml
|
||||
parameters:
|
||||
displayName: Submit *.msixbundle to ESRP for code signing
|
||||
signingIdentity: ${{ parameters.signingIdentity }}
|
||||
inputs:
|
||||
FolderPath: $(System.ArtifactsDirectory)\bundle
|
||||
Pattern: $(BundleStemName)*.msixbundle
|
||||
UseMinimatch: true
|
||||
signConfigType: inlineSignParams
|
||||
inlineOperation: >-
|
||||
[
|
||||
{
|
||||
"KeyCode": "Dynamic",
|
||||
"CertTemplateName": "WINMSAPP1ST",
|
||||
"CertSubjectName": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US",
|
||||
"OperationCode": "SigntoolSign",
|
||||
"Parameters": {
|
||||
"OpusName": "Microsoft",
|
||||
"OpusInfo": "http://www.microsoft.com",
|
||||
"FileDigest": "/fd \"SHA256\"",
|
||||
"TimeStamp": "/tr \"http://rfc3161.gtm.corp.microsoft.com/TSS/HttpTspServer\" /td sha256"
|
||||
},
|
||||
"ToolName": "sign",
|
||||
"ToolVersion": "1.0"
|
||||
},
|
||||
{
|
||||
"KeyCode": "Dynamic",
|
||||
"CertTemplateName": "WINMSAPP1ST",
|
||||
"CertSubjectName": "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US",
|
||||
"OperationCode": "SigntoolVerify",
|
||||
"Parameters": {},
|
||||
"ToolName": "sign",
|
||||
"ToolVersion": "1.0"
|
||||
}
|
||||
]
|
||||
|
||||
- ${{ if eq(parameters.generateSbom, true) }}:
|
||||
- task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0
|
||||
|
||||
@@ -85,36 +85,32 @@ jobs:
|
||||
versionEnvVar: XES_PACKAGEVERSIONNUMBER
|
||||
|
||||
- ${{ if eq(parameters.codeSign, true) }}:
|
||||
- task: EsrpCodeSigning@5
|
||||
displayName: Submit *.nupkg to ESRP for code signing
|
||||
inputs:
|
||||
ConnectedServiceName: ${{ parameters.signingIdentity.serviceName }}
|
||||
AppRegistrationClientId: ${{ parameters.signingIdentity.appId }}
|
||||
AppRegistrationTenantId: ${{ parameters.signingIdentity.tenantId }}
|
||||
AuthAKVName: ${{ parameters.signingIdentity.akvName }}
|
||||
AuthCertName: ${{ parameters.signingIdentity.authCertName }}
|
||||
AuthSignCertName: ${{ parameters.signingIdentity.signCertName }}
|
||||
FolderPath: $(Build.ArtifactStagingDirectory)/nupkg
|
||||
Pattern: '*.nupkg'
|
||||
UseMinimatch: true
|
||||
signConfigType: inlineSignParams
|
||||
inlineOperation: >-
|
||||
[
|
||||
{
|
||||
"KeyCode": "CP-401405",
|
||||
"OperationCode": "NuGetSign",
|
||||
"Parameters": {},
|
||||
"ToolName": "sign",
|
||||
"ToolVersion": "1.0"
|
||||
},
|
||||
{
|
||||
"KeyCode": "CP-401405",
|
||||
"OperationCode": "NuGetVerify",
|
||||
"Parameters": {},
|
||||
"ToolName": "sign",
|
||||
"ToolVersion": "1.0"
|
||||
}
|
||||
]
|
||||
- template: steps-esrp-signing.yml
|
||||
parameters:
|
||||
displayName: Submit *.nupkg to ESRP for code signing
|
||||
signingIdentity: ${{ parameters.signingIdentity }}
|
||||
inputs:
|
||||
FolderPath: $(Build.ArtifactStagingDirectory)/nupkg
|
||||
Pattern: '*.nupkg'
|
||||
UseMinimatch: true
|
||||
signConfigType: inlineSignParams
|
||||
inlineOperation: >-
|
||||
[
|
||||
{
|
||||
"KeyCode": "CP-401405",
|
||||
"OperationCode": "NuGetSign",
|
||||
"Parameters": {},
|
||||
"ToolName": "sign",
|
||||
"ToolVersion": "1.0"
|
||||
},
|
||||
{
|
||||
"KeyCode": "CP-401405",
|
||||
"OperationCode": "NuGetVerify",
|
||||
"Parameters": {},
|
||||
"ToolName": "sign",
|
||||
"ToolVersion": "1.0"
|
||||
}
|
||||
]
|
||||
|
||||
- ${{ if eq(parameters.generateSbom, true) }}:
|
||||
- task: AzureArtifacts.manifest-generator-task.manifest-generator-task.ManifestGeneratorTask@0
|
||||
|
||||
22
build/pipelines/templates-v2/steps-esrp-signing.yml
Normal file
22
build/pipelines/templates-v2/steps-esrp-signing.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
parameters:
|
||||
- name: displayName
|
||||
type: string
|
||||
default: ESRP Code Signing
|
||||
- name: inputs
|
||||
type: object
|
||||
default: {}
|
||||
- name: signingIdentity
|
||||
type: object
|
||||
default: {}
|
||||
|
||||
steps:
|
||||
- task: EsrpCodeSigning@5
|
||||
displayName: ${{ parameters.displayName }}
|
||||
inputs:
|
||||
ConnectedServiceName: ${{ parameters.signingIdentity.serviceName }}
|
||||
AppRegistrationClientId: ${{ parameters.signingIdentity.appId }}
|
||||
AppRegistrationTenantId: ${{ parameters.signingIdentity.tenantId }}
|
||||
AuthAKVName: ${{ parameters.signingIdentity.akvName }}
|
||||
AuthCertName: ${{ parameters.signingIdentity.authCertName }}
|
||||
AuthSignCertName: ${{ parameters.signingIdentity.signCertName }}
|
||||
${{ insert }}: ${{ parameters.inputs }}
|
||||
@@ -186,12 +186,9 @@ catch (...)
|
||||
static UBool U_CALLCONV utextAccess(UText* ut, int64_t nativeIndex, UBool forward) noexcept
|
||||
try
|
||||
{
|
||||
if (nativeIndex < 0)
|
||||
{
|
||||
nativeIndex = 0;
|
||||
}
|
||||
|
||||
auto neededIndex = nativeIndex;
|
||||
// This will make it simpler for us to search the row that contains the nativeIndex,
|
||||
// because we'll now only need to check for `start<=index<limit` and nothing else.
|
||||
if (!forward)
|
||||
{
|
||||
neededIndex--;
|
||||
@@ -199,10 +196,12 @@ try
|
||||
|
||||
const auto& textBuffer = *static_cast<const TextBuffer*>(ut->context);
|
||||
const auto range = accessRowRange(ut);
|
||||
auto start = ut->chunkNativeStart;
|
||||
auto limit = ut->chunkNativeLimit;
|
||||
const auto startOld = ut->chunkNativeStart;
|
||||
const auto limitOld = ut->chunkNativeLimit;
|
||||
auto start = startOld;
|
||||
auto limit = limitOld;
|
||||
|
||||
if (neededIndex < start || neededIndex >= limit)
|
||||
if (neededIndex < startOld || neededIndex >= limitOld)
|
||||
{
|
||||
auto y = accessCurrentRow(ut);
|
||||
std::wstring_view text;
|
||||
@@ -215,8 +214,7 @@ try
|
||||
--y;
|
||||
if (y < range.begin)
|
||||
{
|
||||
assert(false);
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
const auto& row = textBuffer.GetRowByOffset(y);
|
||||
@@ -235,8 +233,7 @@ try
|
||||
++y;
|
||||
if (y >= range.end)
|
||||
{
|
||||
assert(false);
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
|
||||
const auto& row = textBuffer.GetRowByOffset(y);
|
||||
@@ -249,38 +246,50 @@ try
|
||||
} while (neededIndex >= limit);
|
||||
}
|
||||
|
||||
if (!wasWrapForced)
|
||||
assert(start >= 0);
|
||||
// If we have already calculated the total length we can also assert that the limit is in range.
|
||||
assert(ut->p == nullptr || static_cast<size_t>(limit) <= accessLength(ut));
|
||||
|
||||
// Even if we went out-of-bounds, we still need to update the chunkContents to contain the first/last chunk.
|
||||
if (limit != limitOld)
|
||||
{
|
||||
const auto newSize = text.size() + 1;
|
||||
const auto buffer = RefcountBuffer::EnsureCapacityForOverwrite(accessBuffer(ut), newSize);
|
||||
if (!wasWrapForced)
|
||||
{
|
||||
const auto newSize = text.size() + 1;
|
||||
const auto buffer = RefcountBuffer::EnsureCapacityForOverwrite(accessBuffer(ut), newSize);
|
||||
|
||||
memcpy(&buffer->data[0], text.data(), text.size() * sizeof(wchar_t));
|
||||
til::at(buffer->data, text.size()) = L'\n';
|
||||
memcpy(&buffer->data[0], text.data(), text.size() * sizeof(wchar_t));
|
||||
til::at(buffer->data, text.size()) = L'\n';
|
||||
|
||||
text = { &buffer->data[0], newSize };
|
||||
accessBuffer(ut) = buffer;
|
||||
}
|
||||
text = { &buffer->data[0], newSize };
|
||||
accessBuffer(ut) = buffer;
|
||||
}
|
||||
|
||||
accessCurrentRow(ut) = y;
|
||||
ut->chunkNativeStart = start;
|
||||
ut->chunkNativeLimit = limit;
|
||||
ut->chunkLength = gsl::narrow_cast<int32_t>(text.size());
|
||||
accessCurrentRow(ut) = y;
|
||||
ut->chunkNativeStart = start;
|
||||
ut->chunkNativeLimit = limit;
|
||||
ut->chunkLength = gsl::narrow_cast<int32_t>(text.size());
|
||||
#pragma warning(suppress : 26490) // Don't use reinterpret_cast (type.1).
|
||||
ut->chunkContents = reinterpret_cast<const char16_t*>(text.data());
|
||||
ut->nativeIndexingLimit = ut->chunkLength;
|
||||
ut->chunkContents = reinterpret_cast<const char16_t*>(text.data());
|
||||
ut->nativeIndexingLimit = ut->chunkLength;
|
||||
}
|
||||
}
|
||||
|
||||
auto offset = gsl::narrow_cast<int32_t>(nativeIndex - start);
|
||||
|
||||
// The ICU documentation is a little bit misleading. It states:
|
||||
// > @param forward [...] If true, start<=index<limit. If false, [...] start<index<=limit.
|
||||
// but that's just for finding the target chunk. The chunkOffset is not actually constrained to that!
|
||||
// std::clamp will perform a<=b<=c, which is what we want.
|
||||
const auto clampedIndex = std::clamp(nativeIndex, start, limit);
|
||||
auto offset = gsl::narrow_cast<int32_t>(clampedIndex - start);
|
||||
// Don't leave the offset on a trailing surrogate pair. See U16_SET_CP_START.
|
||||
// This assumes that the TextBuffer contains valid UTF-16 which may theoretically not be the case.
|
||||
if (offset > 0 && offset < ut->chunkLength && U16_IS_TRAIL(til::at(ut->chunkContents, offset)))
|
||||
{
|
||||
offset--;
|
||||
}
|
||||
|
||||
ut->chunkOffset = offset;
|
||||
return true;
|
||||
|
||||
return neededIndex >= start && neededIndex < limit;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
|
||||
@@ -2789,7 +2789,7 @@ void TextBuffer::Serialize(const wchar_t* destination) const
|
||||
// In other words, we can only skip \x1b[K = Erase in Line, if both the first/last attribute are the default attribute.
|
||||
static constexpr TextAttribute defaultAttr;
|
||||
const auto trimTrailingWhitespaces = it == last && lastCharX < newX;
|
||||
const auto clearToEndOfLine = trimTrailingWhitespaces && beg->value != defaultAttr || beg->value != defaultAttr;
|
||||
const auto clearToEndOfLine = trimTrailingWhitespaces && (beg->value != defaultAttr || last->value != defaultAttr);
|
||||
|
||||
if (trimTrailingWhitespaces)
|
||||
{
|
||||
|
||||
@@ -130,6 +130,8 @@ namespace winrt::Microsoft::Terminal::Remoting::implementation
|
||||
// We connected to a monarch instance, not us though. This won't hit
|
||||
// in isolated mode.
|
||||
|
||||
LOG_IF_FAILED(CoAllowSetForegroundWindow(winrt::get_unknown(_monarch), nullptr));
|
||||
|
||||
// Send the commandline over to the monarch process
|
||||
if (_proposeToMonarch(args))
|
||||
{
|
||||
|
||||
@@ -27,10 +27,10 @@ static const int CombinedPaneBorderSize = 2 * PaneBorderSize;
|
||||
static const int AnimationDurationInMilliseconds = 200;
|
||||
static const Duration AnimationDuration = DurationHelper::FromTimeSpan(winrt::Windows::Foundation::TimeSpan(std::chrono::milliseconds(AnimationDurationInMilliseconds)));
|
||||
|
||||
Pane::Pane(const IPaneContent& content, const bool lastFocused) :
|
||||
_content{ content },
|
||||
Pane::Pane(IPaneContent content, const bool lastFocused) :
|
||||
_lastActive{ lastFocused }
|
||||
{
|
||||
_setPaneContent(std::move(content));
|
||||
_root.Children().Append(_borderFirst);
|
||||
|
||||
const auto& control{ _content.GetRoot() };
|
||||
@@ -467,7 +467,7 @@ std::shared_ptr<Pane> Pane::NextPane(const std::shared_ptr<Pane> targetPane)
|
||||
std::shared_ptr<Pane> nextPane = nullptr;
|
||||
auto foundTarget = false;
|
||||
|
||||
auto foundNext = WalkTree([&](auto pane) {
|
||||
auto foundNext = WalkTree([&](const auto& pane) {
|
||||
// If we are a parent pane we don't want to move to one of our children
|
||||
if (foundTarget && targetPane->_HasChild(pane))
|
||||
{
|
||||
@@ -985,6 +985,7 @@ void Pane::_ContentLostFocusHandler(const winrt::Windows::Foundation::IInspectab
|
||||
// - <none>
|
||||
void Pane::Close()
|
||||
{
|
||||
_setPaneContent(nullptr);
|
||||
// Fire our Closed event to tell our parent that we should be removed.
|
||||
Closed.raise(nullptr, nullptr);
|
||||
}
|
||||
@@ -996,7 +997,7 @@ void Pane::Shutdown()
|
||||
{
|
||||
if (_IsLeaf())
|
||||
{
|
||||
_content.Close();
|
||||
_setPaneContent(nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1341,7 +1342,7 @@ std::shared_ptr<Pane> Pane::DetachPane(std::shared_ptr<Pane> pane)
|
||||
detached->_ApplySplitDefinitions();
|
||||
|
||||
// Trigger the detached event on each child
|
||||
detached->WalkTree([](auto pane) {
|
||||
detached->WalkTree([](const auto& pane) {
|
||||
pane->Detached.raise(pane);
|
||||
});
|
||||
|
||||
@@ -1400,7 +1401,7 @@ void Pane::_CloseChild(const bool closeFirst)
|
||||
_borders = _GetCommonBorders();
|
||||
|
||||
// take the control, profile, id and isDefTermSession of the pane that _wasn't_ closed.
|
||||
_content = remainingChild->_content;
|
||||
_setPaneContent(remainingChild->_takePaneContent());
|
||||
_id = remainingChild->Id();
|
||||
|
||||
// Revoke the old event handlers. Remove both the handlers for the panes
|
||||
@@ -1542,7 +1543,7 @@ void Pane::_CloseChild(const bool closeFirst)
|
||||
{
|
||||
// update our path to our first remaining leaf
|
||||
_parentChildPath = _firstChild;
|
||||
_firstChild->WalkTree([](auto p) {
|
||||
_firstChild->WalkTree([](const auto& p) {
|
||||
if (p->_IsLeaf())
|
||||
{
|
||||
return true;
|
||||
@@ -1705,6 +1706,35 @@ void Pane::_SetupChildCloseHandlers()
|
||||
});
|
||||
}
|
||||
|
||||
// With this method you take ownership of the control from this Pane.
|
||||
// Assign it to another Pane with _setPaneContent() or Close() it.
|
||||
IPaneContent Pane::_takePaneContent()
|
||||
{
|
||||
_closeRequestedRevoker.revoke();
|
||||
// we cannot return std::move(_content) because we don't want _content to be null,
|
||||
// since _content gets accessed even after Close is called
|
||||
return _content;
|
||||
}
|
||||
|
||||
// This method safely sets the content of the Pane. It'll ensure to revoke and
|
||||
// assign event handlers, and to Close() the existing content if there's any.
|
||||
// The new content can be nullptr to remove any content.
|
||||
void Pane::_setPaneContent(IPaneContent content)
|
||||
{
|
||||
// The IPaneContent::Close() implementation may be buggy and raise the CloseRequested event again.
|
||||
// _takePaneContent() avoids this as it revokes the event handler.
|
||||
if (_takePaneContent())
|
||||
{
|
||||
_content.Close();
|
||||
}
|
||||
|
||||
if (content)
|
||||
{
|
||||
_content = std::move(content);
|
||||
_closeRequestedRevoker = _content.CloseRequested(winrt::auto_revoke, [this](auto&&, auto&&) { Close(); });
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Sets up row/column definitions for this pane. There are three total
|
||||
// row/cols. The middle one is for the separator. The first and third are for
|
||||
@@ -2255,8 +2285,7 @@ std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> Pane::_Split(SplitDirect
|
||||
else
|
||||
{
|
||||
// Move our control, guid, isDefTermSession into the first one.
|
||||
_firstChild = std::make_shared<Pane>(_content);
|
||||
_content = nullptr;
|
||||
_firstChild = std::make_shared<Pane>(_takePaneContent());
|
||||
_firstChild->_broadcastEnabled = _broadcastEnabled;
|
||||
}
|
||||
|
||||
@@ -2398,7 +2427,7 @@ void Pane::Id(uint32_t id) noexcept
|
||||
bool Pane::FocusPane(const uint32_t id)
|
||||
{
|
||||
// Always clear the parent child path if we are focusing a leaf
|
||||
return WalkTree([=](auto p) {
|
||||
return WalkTree([=](const auto& p) {
|
||||
p->_parentChildPath.reset();
|
||||
if (p->_id == id)
|
||||
{
|
||||
@@ -2421,7 +2450,7 @@ bool Pane::FocusPane(const uint32_t id)
|
||||
// - true if focus was set
|
||||
bool Pane::FocusPane(const std::shared_ptr<Pane> pane)
|
||||
{
|
||||
return WalkTree([&](auto p) {
|
||||
return WalkTree([&](const auto& p) {
|
||||
if (p == pane)
|
||||
{
|
||||
p->_Focus();
|
||||
@@ -2451,6 +2480,11 @@ bool Pane::_HasChild(const std::shared_ptr<Pane> child)
|
||||
});
|
||||
}
|
||||
|
||||
winrt::TerminalApp::TerminalPaneContent Pane::_getTerminalContent() const
|
||||
{
|
||||
return _IsLeaf() ? _content.try_as<winrt::TerminalApp::TerminalPaneContent>() : nullptr;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Recursive function that finds a pane with the given ID
|
||||
// Arguments:
|
||||
|
||||
@@ -62,7 +62,7 @@ struct PaneResources
|
||||
class Pane : public std::enable_shared_from_this<Pane>
|
||||
{
|
||||
public:
|
||||
Pane(const winrt::TerminalApp::IPaneContent& content,
|
||||
Pane(winrt::TerminalApp::IPaneContent content,
|
||||
const bool lastFocused = false);
|
||||
|
||||
Pane(std::shared_ptr<Pane> first,
|
||||
@@ -248,13 +248,13 @@ private:
|
||||
|
||||
std::optional<uint32_t> _id;
|
||||
std::weak_ptr<Pane> _parentChildPath{};
|
||||
|
||||
bool _lastActive{ false };
|
||||
winrt::event_token _firstClosedToken{ 0 };
|
||||
winrt::event_token _secondClosedToken{ 0 };
|
||||
|
||||
winrt::Windows::UI::Xaml::UIElement::GotFocus_revoker _gotFocusRevoker;
|
||||
winrt::Windows::UI::Xaml::UIElement::LostFocus_revoker _lostFocusRevoker;
|
||||
winrt::TerminalApp::IPaneContent::CloseRequested_revoker _closeRequestedRevoker;
|
||||
|
||||
Borders _borders{ Borders::None };
|
||||
|
||||
@@ -264,11 +264,10 @@ private:
|
||||
bool _IsLeaf() const noexcept;
|
||||
bool _HasFocusedChild() const noexcept;
|
||||
void _SetupChildCloseHandlers();
|
||||
winrt::TerminalApp::IPaneContent _takePaneContent();
|
||||
void _setPaneContent(winrt::TerminalApp::IPaneContent content);
|
||||
bool _HasChild(const std::shared_ptr<Pane> child);
|
||||
winrt::TerminalApp::TerminalPaneContent _getTerminalContent() const
|
||||
{
|
||||
return _IsLeaf() ? _content.try_as<winrt::TerminalApp::TerminalPaneContent>() : nullptr;
|
||||
}
|
||||
winrt::TerminalApp::TerminalPaneContent _getTerminalContent() const;
|
||||
|
||||
std::pair<std::shared_ptr<Pane>, std::shared_ptr<Pane>> _Split(winrt::Microsoft::Terminal::Settings::Model::SplitDirection splitType,
|
||||
const float splitSize,
|
||||
|
||||
@@ -205,7 +205,7 @@
|
||||
<value>Dividir pestaña</value>
|
||||
</data>
|
||||
<data name="SplitPaneText" xml:space="preserve">
|
||||
<value>Panel dividido</value>
|
||||
<value>Dividir panel</value>
|
||||
</data>
|
||||
<data name="SearchWebText" xml:space="preserve">
|
||||
<value>Búsqueda en la web</value>
|
||||
|
||||
@@ -421,13 +421,13 @@
|
||||
<value>Apri nuova scheda</value>
|
||||
</data>
|
||||
<data name="NewPaneRun.Text" xml:space="preserve">
|
||||
<value>ALT + clic per dividere la finestra corrente</value>
|
||||
<value>ALT+CLIC per dividere la finestra corrente</value>
|
||||
</data>
|
||||
<data name="NewWindowRun.Text" xml:space="preserve">
|
||||
<value>MAIUSC+clic per aprire una nuova finestra</value>
|
||||
<value>MAIUSC+CLIC per aprire una nuova finestra</value>
|
||||
</data>
|
||||
<data name="ElevatedRun.Text" xml:space="preserve">
|
||||
<value>CTRL+Clic per aprire come amministratore</value>
|
||||
<value>CTRL+CLIC per aprire come amministratore</value>
|
||||
</data>
|
||||
<data name="WindowCloseButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Chiudi</value>
|
||||
|
||||
@@ -45,7 +45,6 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
void ScratchpadContent::Close()
|
||||
{
|
||||
CloseRequested.raise(*this, nullptr);
|
||||
}
|
||||
|
||||
INewContentArgs ScratchpadContent::GetNewTerminalArgs(const BuildStartupKind /* kind */) const
|
||||
|
||||
@@ -47,7 +47,6 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
void SettingsPaneContent::Close()
|
||||
{
|
||||
CloseRequested.raise(*this, nullptr);
|
||||
}
|
||||
|
||||
INewContentArgs SettingsPaneContent::GetNewTerminalArgs(const BuildStartupKind /*kind*/) const
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
xmlns:model="using:Microsoft.Terminal.Settings.Model"
|
||||
xmlns:mtu="using:Microsoft.Terminal.UI"
|
||||
xmlns:mux="using:Microsoft.UI.Xaml.Controls"
|
||||
MinWidth="256"
|
||||
AllowFocusOnInteraction="True"
|
||||
AutomationProperties.Name="{x:Bind ControlName, Mode=OneWay}"
|
||||
IsTabStop="True"
|
||||
@@ -99,6 +100,24 @@
|
||||
GeneralItemTemplate="{StaticResource GeneralItemTemplate}"
|
||||
NestedItemTemplate="{StaticResource NestedItemTemplate}" />
|
||||
|
||||
<!--
|
||||
Remove all item animations from the suggestions UI. They're
|
||||
entirely too slow to let that UI be usable.
|
||||
-->
|
||||
<Style x:Key="NoAnimationsPlease"
|
||||
TargetType="ListView">
|
||||
<Setter Property="ItemContainerTransitions">
|
||||
<Setter.Value>
|
||||
<TransitionCollection>
|
||||
<!-- (deleted transitions are left for reference for what we removed) -->
|
||||
<ContentThemeTransition />
|
||||
<!--<AddDeleteThemeTransition/>-->
|
||||
<!--<ReorderThemeTransition/>-->
|
||||
<!--<EntranceThemeTransition IsStaggeringEnabled="False"/>-->
|
||||
</TransitionCollection>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
|
||||
@@ -203,7 +222,8 @@
|
||||
ItemClick="_listItemClicked"
|
||||
ItemsSource="{x:Bind FilteredActions}"
|
||||
SelectionChanged="_listItemSelectionChanged"
|
||||
SelectionMode="Single" />
|
||||
SelectionMode="Single"
|
||||
Style="{StaticResource NoAnimationsPlease}" />
|
||||
|
||||
</Grid>
|
||||
|
||||
|
||||
@@ -739,7 +739,7 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
|
||||
// Clean read-only mode to prevent additional prompt if closing the pane triggers closing of a hosting tab
|
||||
pane->WalkTree([](auto p) {
|
||||
pane->WalkTree([](const auto& p) {
|
||||
if (const auto control{ p->GetTerminalControl() })
|
||||
{
|
||||
if (control.ReadOnly())
|
||||
|
||||
@@ -1899,7 +1899,10 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
void TerminalPage::PersistState()
|
||||
{
|
||||
if (_startupState != StartupState::Initialized)
|
||||
// This method may be called for a window even if it hasn't had a tab yet or lost all of them.
|
||||
// We shouldn't persist such windows.
|
||||
const auto tabCount = _tabs.Size();
|
||||
if (_startupState != StartupState::Initialized || tabCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -1915,7 +1918,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
// if the focused tab was not the last tab, restore that
|
||||
auto idx = _GetFocusedTabIndex();
|
||||
if (idx && idx != _tabs.Size() - 1)
|
||||
if (idx && idx != tabCount - 1)
|
||||
{
|
||||
ActionAndArgs action;
|
||||
action.Action(ShortcutAction::SwitchToTab);
|
||||
@@ -3807,7 +3810,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// recipe for disaster. We won't ever open up a tab in this window.
|
||||
newTerminalArgs.Elevate(false);
|
||||
const auto newPane = _MakePane(newTerminalArgs, nullptr, connection);
|
||||
newPane->WalkTree([](auto pane) {
|
||||
newPane->WalkTree([](const auto& pane) {
|
||||
pane->FinalizeConfigurationGivenDefault();
|
||||
});
|
||||
_CreateNewTabFromPane(newPane);
|
||||
|
||||
@@ -78,8 +78,6 @@ namespace winrt::TerminalApp::implementation
|
||||
_bellPlayer = nullptr;
|
||||
_bellPlayerCreated = false;
|
||||
}
|
||||
|
||||
CloseRequested.raise(*this, nullptr);
|
||||
}
|
||||
|
||||
winrt::hstring TerminalPaneContent::Icon() const
|
||||
@@ -239,19 +237,20 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
if (_profile)
|
||||
{
|
||||
if (_isDefTermSession && _profile.CloseOnExit() == CloseOnExitMode::Automatic)
|
||||
{
|
||||
// For 'automatic', we only care about the connection state if we were launched by Terminal
|
||||
// Since we were launched via defterm, ignore the connection state (i.e. we treat the
|
||||
// close on exit mode as 'always', see GH #13325 for discussion)
|
||||
Close();
|
||||
}
|
||||
|
||||
const auto mode = _profile.CloseOnExit();
|
||||
if ((mode == CloseOnExitMode::Always) ||
|
||||
((mode == CloseOnExitMode::Graceful || mode == CloseOnExitMode::Automatic) && newConnectionState == ConnectionState::Closed))
|
||||
|
||||
if (
|
||||
// This one is obvious: If the user asked for "always" we do just that.
|
||||
(mode == CloseOnExitMode::Always) ||
|
||||
// Otherwise, and unless the user asked for the opposite of "always",
|
||||
// close the pane when the connection closed gracefully (not failed).
|
||||
(mode != CloseOnExitMode::Never && newConnectionState == ConnectionState::Closed) ||
|
||||
// However, defterm handoff can result in Windows Terminal randomly opening which may be annoying,
|
||||
// so by default we should at least always close the pane, even if the command failed.
|
||||
// See GH #13325 for discussion.
|
||||
(mode == CloseOnExitMode::Automatic && _isDefTermSession))
|
||||
{
|
||||
Close();
|
||||
CloseRequested.raise(nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -331,7 +330,7 @@ namespace winrt::TerminalApp::implementation
|
||||
void TerminalPaneContent::_closeTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
|
||||
const winrt::Windows::Foundation::IInspectable& /*args*/)
|
||||
{
|
||||
Close();
|
||||
CloseRequested.raise(nullptr, nullptr);
|
||||
}
|
||||
|
||||
void TerminalPaneContent::_restartTerminalRequestedHandler(const winrt::Windows::Foundation::IInspectable& /*sender*/,
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
auto firstId = _nextPaneId;
|
||||
|
||||
_rootPane->WalkTree([&](std::shared_ptr<Pane> pane) {
|
||||
_rootPane->WalkTree([&](const auto& pane) {
|
||||
// update the IDs on each pane
|
||||
if (pane->_IsLeaf())
|
||||
{
|
||||
@@ -203,7 +203,7 @@ namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
ASSERT_UI_THREAD();
|
||||
|
||||
_rootPane->WalkTree([&](std::shared_ptr<Pane> pane) {
|
||||
_rootPane->WalkTree([&](const auto& pane) {
|
||||
// Attach event handlers to each new pane
|
||||
_AttachEventHandlersToPane(pane);
|
||||
if (auto content = pane->GetContent())
|
||||
@@ -275,7 +275,7 @@ namespace winrt::TerminalApp::implementation
|
||||
_UpdateHeaderControlMaxWidth();
|
||||
|
||||
// Update the settings on all our panes.
|
||||
_rootPane->WalkTree([&](auto pane) {
|
||||
_rootPane->WalkTree([&](const auto& pane) {
|
||||
pane->UpdateSettings(settings);
|
||||
return false;
|
||||
});
|
||||
@@ -534,7 +534,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
// Add the new event handlers to the new pane(s)
|
||||
// and update their ids.
|
||||
pane->WalkTree([&](auto p) {
|
||||
pane->WalkTree([&](const auto& p) {
|
||||
_AttachEventHandlersToPane(p);
|
||||
if (p->_IsLeaf())
|
||||
{
|
||||
@@ -624,7 +624,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// manually.
|
||||
_rootPane->Closed(_rootClosedToken);
|
||||
auto p = _rootPane;
|
||||
p->WalkTree([](auto pane) {
|
||||
p->WalkTree([](const auto& pane) {
|
||||
pane->Detached.raise(pane);
|
||||
});
|
||||
|
||||
@@ -650,7 +650,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
// Add the new event handlers to the new pane(s)
|
||||
// and update their ids.
|
||||
pane->WalkTree([&](auto p) {
|
||||
pane->WalkTree([&](const auto& p) {
|
||||
_AttachEventHandlersToPane(p);
|
||||
if (p->_IsLeaf())
|
||||
{
|
||||
@@ -947,32 +947,6 @@ namespace winrt::TerminalApp::implementation
|
||||
auto dispatcher = TabViewItem().Dispatcher();
|
||||
ContentEventTokens events{};
|
||||
|
||||
events.CloseRequested = content.CloseRequested(
|
||||
winrt::auto_revoke,
|
||||
[dispatcher, weakThis](auto sender, auto&&) -> winrt::fire_and_forget {
|
||||
// Don't forget! this ^^^^^^^^ sender can't be a reference, this is a async callback.
|
||||
|
||||
// The lambda lives in the `std::function`-style container owned by `control`. That is, when the
|
||||
// `control` gets destroyed the lambda struct also gets destroyed. In other words, we need to
|
||||
// copy `weakThis` onto the stack, because that's the only thing that gets captured in coroutines.
|
||||
// See: https://devblogs.microsoft.com/oldnewthing/20211103-00/?p=105870
|
||||
const auto weakThisCopy = weakThis;
|
||||
co_await wil::resume_foreground(dispatcher);
|
||||
// Check if Tab's lifetime has expired
|
||||
if (auto tab{ weakThisCopy.get() })
|
||||
{
|
||||
if (const auto content{ sender.try_as<TerminalApp::IPaneContent>() })
|
||||
{
|
||||
tab->_rootPane->WalkTree([content](std::shared_ptr<Pane> pane) {
|
||||
if (pane->GetContent() == content)
|
||||
{
|
||||
pane->Close();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
events.TitleChanged = content.TitleChanged(
|
||||
winrt::auto_revoke,
|
||||
[dispatcher, weakThis](auto&&, auto&&) -> winrt::fire_and_forget {
|
||||
|
||||
@@ -134,7 +134,6 @@ namespace winrt::TerminalApp::implementation
|
||||
winrt::TerminalApp::IPaneContent::ConnectionStateChanged_revoker ConnectionStateChanged;
|
||||
winrt::TerminalApp::IPaneContent::ReadOnlyChanged_revoker ReadOnlyChanged;
|
||||
winrt::TerminalApp::IPaneContent::FocusRequested_revoker FocusRequested;
|
||||
winrt::TerminalApp::IPaneContent::CloseRequested_revoker CloseRequested;
|
||||
|
||||
// These events literally only apply if the content is a TermControl.
|
||||
winrt::Microsoft::Terminal::Control::TermControl::KeySent_revoker KeySent;
|
||||
|
||||
@@ -263,7 +263,7 @@ namespace winrt::TerminalApp::implementation
|
||||
AppLogic::Current()->NotifyRootInitialized();
|
||||
}
|
||||
|
||||
void TerminalWindow::Quit()
|
||||
void TerminalWindow::PersistState()
|
||||
{
|
||||
if (_root)
|
||||
{
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace winrt::TerminalApp::implementation
|
||||
|
||||
void Create();
|
||||
|
||||
void Quit();
|
||||
void PersistState();
|
||||
|
||||
winrt::fire_and_forget UpdateSettings(winrt::TerminalApp::SettingsLoadEventArgs args);
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace TerminalApp
|
||||
Boolean ShouldImmediatelyHandoffToElevated();
|
||||
void HandoffToElevated();
|
||||
|
||||
void Quit();
|
||||
void PersistState();
|
||||
|
||||
Windows.UI.Xaml.UIElement GetRoot();
|
||||
|
||||
|
||||
@@ -180,12 +180,14 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
const HANDLE hRef,
|
||||
const HANDLE hServerProcess,
|
||||
const HANDLE hClientProcess,
|
||||
TERMINAL_STARTUP_INFO startupInfo) :
|
||||
const TERMINAL_STARTUP_INFO& startupInfo) :
|
||||
_rows{ 25 },
|
||||
_cols{ 80 },
|
||||
_inPipe{ hIn },
|
||||
_outPipe{ hOut }
|
||||
{
|
||||
_sessionId = Utils::CreateGuid();
|
||||
|
||||
THROW_IF_FAILED(ConptyPackPseudoConsole(hServerProcess, hRef, hSig, &_hPC));
|
||||
_piClient.hProcess = hClientProcess;
|
||||
|
||||
@@ -429,12 +431,10 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
try
|
||||
{
|
||||
// GH#11556 - make sure to format the error code to this string as an UNSIGNED int
|
||||
winrt::hstring exitText{ fmt::format(std::wstring_view{ RS_(L"ProcessExited") }, fmt::format(_errorFormat, status)) };
|
||||
TerminalOutput.raise(L"\r\n");
|
||||
TerminalOutput.raise(exitText);
|
||||
TerminalOutput.raise(L"\r\n");
|
||||
TerminalOutput.raise(RS_(L"CtrlDToClose"));
|
||||
TerminalOutput.raise(L"\r\n");
|
||||
const auto msg1 = fmt::format(std::wstring_view{ RS_(L"ProcessExited") }, fmt::format(_errorFormat, status));
|
||||
const auto msg2 = RS_(L"CtrlDToClose");
|
||||
const auto msg = fmt::format(FMT_COMPILE(L"\r\n{}\r\n{}\r\n"), msg1, msg2);
|
||||
TerminalOutput.raise(msg);
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ namespace winrt::Microsoft::Terminal::TerminalConnection::implementation
|
||||
const HANDLE hRef,
|
||||
const HANDLE hServerProcess,
|
||||
const HANDLE hClientProcess,
|
||||
TERMINAL_STARTUP_INFO startupInfo);
|
||||
const TERMINAL_STARTUP_INFO& startupInfo);
|
||||
|
||||
ConptyConnection() noexcept = default;
|
||||
void Initialize(const Windows::Foundation::Collections::ValueSet& settings);
|
||||
|
||||
@@ -161,7 +161,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
std::chrono::milliseconds{ 100 },
|
||||
[weakTerminal = std::weak_ptr{ _terminal }, weakThis = get_weak(), dispatcher = _dispatcher]() {
|
||||
dispatcher.TryEnqueue(DispatcherQueuePriority::Normal, [weakThis]() {
|
||||
if (const auto self = weakThis.get(); !self->_IsClosing())
|
||||
if (const auto self = weakThis.get(); self && !self->_IsClosing())
|
||||
{
|
||||
self->OutputIdle.raise(*self, nullptr);
|
||||
}
|
||||
@@ -179,7 +179,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
_dispatcher,
|
||||
std::chrono::milliseconds{ 8 },
|
||||
[weakThis = get_weak()](const auto& update) {
|
||||
if (auto core{ weakThis.get() }; !core->_IsClosing())
|
||||
if (auto core{ weakThis.get() }; core && !core->_IsClosing())
|
||||
{
|
||||
core->ScrollPositionChanged.raise(*core, update);
|
||||
}
|
||||
@@ -2235,20 +2235,39 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
std::vector<winrt::hstring> commands;
|
||||
const auto bufferCommands{ textBuffer.Commands() };
|
||||
for (const auto& commandInBuffer : bufferCommands)
|
||||
{
|
||||
const auto strEnd = commandInBuffer.find_last_not_of(UNICODE_SPACE);
|
||||
|
||||
auto trimToHstring = [](const auto& s) -> winrt::hstring {
|
||||
const auto strEnd = s.find_last_not_of(UNICODE_SPACE);
|
||||
if (strEnd != std::string::npos)
|
||||
{
|
||||
const auto trimmed = commandInBuffer.substr(0, strEnd + 1);
|
||||
const auto trimmed = s.substr(0, strEnd + 1);
|
||||
return winrt::hstring{ trimmed };
|
||||
}
|
||||
return winrt::hstring{ L"" };
|
||||
};
|
||||
|
||||
commands.push_back(winrt::hstring{ trimmed });
|
||||
const auto currentCommand = _terminal->CurrentCommand();
|
||||
const auto trimmedCurrentCommand = trimToHstring(currentCommand);
|
||||
|
||||
for (const auto& commandInBuffer : bufferCommands)
|
||||
{
|
||||
if (const auto hstr{ trimToHstring(commandInBuffer) };
|
||||
(!hstr.empty() && hstr != trimmedCurrentCommand))
|
||||
{
|
||||
commands.push_back(hstr);
|
||||
}
|
||||
}
|
||||
|
||||
auto context = winrt::make_self<CommandHistoryContext>(std::move(commands));
|
||||
context->CurrentCommandline(winrt::hstring{ _terminal->CurrentCommand() });
|
||||
// If the very last thing in the list of recent commands, is exactly the
|
||||
// same as the current command, then let's not include it in the
|
||||
// history. It's literally the thing the user has typed, RIGHT now.
|
||||
if (!commands.empty() && commands.back() == trimmedCurrentCommand)
|
||||
{
|
||||
commands.pop_back();
|
||||
}
|
||||
|
||||
auto context = winrt::make_self<CommandHistoryContext>(std::move(commands));
|
||||
context->CurrentCommandline(trimmedCurrentCommand);
|
||||
return *context;
|
||||
}
|
||||
|
||||
|
||||
@@ -217,6 +217,10 @@ Installieren Sie entweder die fehlende Schriftart, oder wählen Sie eine andere
|
||||
<value>Die folgenden Schriftarten wurden nicht gefunden: {0}. Installieren Sie sie, oder wählen Sie andere Schriftarten aus.</value>
|
||||
<comment>{Locked="{0}"} This is a warning dialog shown when the user selects a font that isn't installed.</comment>
|
||||
</data>
|
||||
<data name="RendererErrorMacType" xml:space="preserve">
|
||||
<value>Ihre Version von MacType ist mit dieser Anwendung nicht kompatibel. Bitte aktualisieren Sie auf Version 2023.5.31 oder höher.</value>
|
||||
<comment>{Locked="2023.5.31","MacType"}</comment>
|
||||
</data>
|
||||
<data name="RendererErrorOther" xml:space="preserve">
|
||||
<value>Unerwarteter Rendererfehler: {0:#010x} {1}</value>
|
||||
<comment>{Locked="{0:#010x}","{1}"} {0:#010x} is a placeholder for a Windows error code (e.g. 0x88985002). {1} is the corresponding message.</comment>
|
||||
|
||||
@@ -217,6 +217,10 @@ Please either install the missing font or choose another one.</value>
|
||||
<value>Unable to find the following fonts: {0}. Please either install them or choose different fonts.</value>
|
||||
<comment>{Locked="{0}"} This is a warning dialog shown when the user selects a font that isn't installed.</comment>
|
||||
</data>
|
||||
<data name="RendererErrorMacType" xml:space="preserve">
|
||||
<value>Your version of MacType is incompatible with this application. Please update to version 2023.5.31 or later.</value>
|
||||
<comment>{Locked="2023.5.31","MacType"}</comment>
|
||||
</data>
|
||||
<data name="RendererErrorOther" xml:space="preserve">
|
||||
<value>Renderer encountered an unexpected error: {0:#010x} {1}</value>
|
||||
<comment>{Locked="{0:#010x}","{1}"} {0:#010x} is a placeholder for a Windows error code (e.g. 0x88985002). {1} is the corresponding message.</comment>
|
||||
|
||||
@@ -217,6 +217,10 @@ Instale la fuente que falta o elija otra.</value>
|
||||
<value>No se pueden encontrar las siguientes fuentes: {0}. Instálelas o elija fuentes diferentes.</value>
|
||||
<comment>{Locked="{0}"} This is a warning dialog shown when the user selects a font that isn't installed.</comment>
|
||||
</data>
|
||||
<data name="RendererErrorMacType" xml:space="preserve">
|
||||
<value>Su versión de MacType no es compatible con esta aplicación. Actualice a la versión 2023.5.31 o posterior.</value>
|
||||
<comment>{Locked="2023.5.31","MacType"}</comment>
|
||||
</data>
|
||||
<data name="RendererErrorOther" xml:space="preserve">
|
||||
<value>El representador encontró un error inesperado: {0:#010x} {1}</value>
|
||||
<comment>{Locked="{0:#010x}","{1}"} {0:#010x} is a placeholder for a Windows error code (e.g. 0x88985002). {1} is the corresponding message.</comment>
|
||||
|
||||
@@ -217,6 +217,10 @@ Installez la police manquante ou choisissez-en une autre.</value>
|
||||
<value>Impossible de trouver les polices suivantes : {0}. Installez-les ou choisissez d’autres polices.</value>
|
||||
<comment>{Locked="{0}"} This is a warning dialog shown when the user selects a font that isn't installed.</comment>
|
||||
</data>
|
||||
<data name="RendererErrorMacType" xml:space="preserve">
|
||||
<value>Votre version de MacType est incompatible avec cette application. Veuillez mettre à jour la version 2023.5.31 ou une version ultérieure.</value>
|
||||
<comment>{Locked="2023.5.31","MacType"}</comment>
|
||||
</data>
|
||||
<data name="RendererErrorOther" xml:space="preserve">
|
||||
<value>Le convertisseur a rencontré une erreur inattendue : {0:#010x} {1}</value>
|
||||
<comment>{Locked="{0:#010x}","{1}"} {0:#010x} is a placeholder for a Windows error code (e.g. 0x88985002). {1} is the corresponding message.</comment>
|
||||
|
||||
@@ -176,7 +176,7 @@
|
||||
<value>Riprendi</value>
|
||||
</data>
|
||||
<data name="HowToOpenRun.Text" xml:space="preserve">
|
||||
<value>CTRL+clic per aprire il collegamento</value>
|
||||
<value>CTRL+CLIC per aprire il collegamento</value>
|
||||
</data>
|
||||
<data name="TermControl_NoMatch" xml:space="preserve">
|
||||
<value>Nessun risultato</value>
|
||||
@@ -217,6 +217,10 @@ Installare il tipo di carattere mancante o sceglierne un altro.</value>
|
||||
<value>Impossibile trovare i tipi di carattere seguenti: {0}. Installarli o scegliere tipi di carattere diversi.</value>
|
||||
<comment>{Locked="{0}"} This is a warning dialog shown when the user selects a font that isn't installed.</comment>
|
||||
</data>
|
||||
<data name="RendererErrorMacType" xml:space="preserve">
|
||||
<value>La versione di MacType è incompatibile con questa applicazione. Aggiornare alla versione 2023.5.31 o successiva.</value>
|
||||
<comment>{Locked="2023.5.31","MacType"}</comment>
|
||||
</data>
|
||||
<data name="RendererErrorOther" xml:space="preserve">
|
||||
<value>Errore imprevisto del renderer: {0:#010x} {1}</value>
|
||||
<comment>{Locked="{0:#010x}","{1}"} {0:#010x} is a placeholder for a Windows error code (e.g. 0x88985002). {1} is the corresponding message.</comment>
|
||||
|
||||
@@ -217,6 +217,10 @@
|
||||
<value>次のフォントが見つかりません: {0}。インストールするか、別のフォントを選択してください。</value>
|
||||
<comment>{Locked="{0}"} This is a warning dialog shown when the user selects a font that isn't installed.</comment>
|
||||
</data>
|
||||
<data name="RendererErrorMacType" xml:space="preserve">
|
||||
<value>お使いのバージョンの MacType は、このアプリケーションと互換性がありません。バージョン 2023.5.31 以降に更新してください。</value>
|
||||
<comment>{Locked="2023.5.31","MacType"}</comment>
|
||||
</data>
|
||||
<data name="RendererErrorOther" xml:space="preserve">
|
||||
<value>レンダラーで予期しないエラーが発生しました: {0:#010x} {1}</value>
|
||||
<comment>{Locked="{0:#010x}","{1}"} {0:#010x} is a placeholder for a Windows error code (e.g. 0x88985002). {1} is the corresponding message.</comment>
|
||||
|
||||
@@ -217,6 +217,10 @@
|
||||
<value>다음 글꼴을 찾을 수 없습니다. {0}. 설치하거나 다른 글꼴을 선택하세요.</value>
|
||||
<comment>{Locked="{0}"} This is a warning dialog shown when the user selects a font that isn't installed.</comment>
|
||||
</data>
|
||||
<data name="RendererErrorMacType" xml:space="preserve">
|
||||
<value>MacType 버전이 이 애플리케이션과 호환되지 않습니다. 버전 2023.5.31 이상으로 업데이트하세요.</value>
|
||||
<comment>{Locked="2023.5.31","MacType"}</comment>
|
||||
</data>
|
||||
<data name="RendererErrorOther" xml:space="preserve">
|
||||
<value>렌더러에서 예기치 않은 오류가 발생했습니다. {0:#010x} {1}</value>
|
||||
<comment>{Locked="{0:#010x}","{1}"} {0:#010x} is a placeholder for a Windows error code (e.g. 0x88985002). {1} is the corresponding message.</comment>
|
||||
|
||||
@@ -217,6 +217,10 @@ Instale a fonte ausente ou escolha outra.</value>
|
||||
<value>Não é possível localizar as seguintes fontes: {0}. Instale-os ou escolha fontes diferentes.</value>
|
||||
<comment>{Locked="{0}"} This is a warning dialog shown when the user selects a font that isn't installed.</comment>
|
||||
</data>
|
||||
<data name="RendererErrorMacType" xml:space="preserve">
|
||||
<value>Sua versão do MacType é incompatível com este aplicativo. Atualize para a versão 2023.5.31 ou posterior.</value>
|
||||
<comment>{Locked="2023.5.31","MacType"}</comment>
|
||||
</data>
|
||||
<data name="RendererErrorOther" xml:space="preserve">
|
||||
<value>O renderizador encontrou um erro inesperado: {0:#010x} {1}</value>
|
||||
<comment>{Locked="{0:#010x}","{1}"} {0:#010x} is a placeholder for a Windows error code (e.g. 0x88985002). {1} is the corresponding message.</comment>
|
||||
|
||||
@@ -217,6 +217,10 @@
|
||||
<value>Цйǻьľė ŧò ƒіņð τнė ƒσℓľоŵíпğ ƒŏптš: {0}. Рĺёąšė èìťђėг ïŋŝţăĺℓ тħēm ôѓ ςђоōѕз ðįƒƒеřęʼnţ ƒóπţś. !!! !!! !!! !!! !!! !!! !!! !!! !!! !</value>
|
||||
<comment>{Locked="{0}"} This is a warning dialog shown when the user selects a font that isn't installed.</comment>
|
||||
</data>
|
||||
<data name="RendererErrorMacType" xml:space="preserve">
|
||||
<value>Ύοϋг νέяšîǿŋ бƒ MacType їš ìⁿçомрāţΐвŀè шιťћ тĥĩѕ ªрφℓíċąţіōņ. Рĺэаşέ űφđаτє τő νзřѕíбʼn 2023.5.31 бŗ ℓâţëґ. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!</value>
|
||||
<comment>{Locked="2023.5.31","MacType"}</comment>
|
||||
</data>
|
||||
<data name="RendererErrorOther" xml:space="preserve">
|
||||
<value>Гêʼnďěѓēŗ эήçομήťєřĕď åń µŋέхρэсťèð ęґяöя: {0:#010×} {1} !!! !!! !!! !!! !!! !</value>
|
||||
<comment>{Locked="{0:#010x}","{1}"} {0:#010x} is a placeholder for a Windows error code (e.g. 0x88985002). {1} is the corresponding message.</comment>
|
||||
|
||||
@@ -217,6 +217,10 @@
|
||||
<value>Цйǻьľė ŧò ƒіņð τнė ƒσℓľоŵíпğ ƒŏптš: {0}. Рĺёąšė èìťђėг ïŋŝţăĺℓ тħēm ôѓ ςђоōѕз ðįƒƒеřęʼnţ ƒóπţś. !!! !!! !!! !!! !!! !!! !!! !!! !!! !</value>
|
||||
<comment>{Locked="{0}"} This is a warning dialog shown when the user selects a font that isn't installed.</comment>
|
||||
</data>
|
||||
<data name="RendererErrorMacType" xml:space="preserve">
|
||||
<value>Ύοϋг νέяšîǿŋ бƒ MacType їš ìⁿçомрāţΐвŀè шιťћ тĥĩѕ ªрφℓíċąţіōņ. Рĺэаşέ űφđаτє τő νзřѕíбʼn 2023.5.31 бŗ ℓâţëґ. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!</value>
|
||||
<comment>{Locked="2023.5.31","MacType"}</comment>
|
||||
</data>
|
||||
<data name="RendererErrorOther" xml:space="preserve">
|
||||
<value>Гêʼnďěѓēŗ эήçομήťєřĕď åń µŋέхρэсťèð ęґяöя: {0:#010×} {1} !!! !!! !!! !!! !!! !</value>
|
||||
<comment>{Locked="{0:#010x}","{1}"} {0:#010x} is a placeholder for a Windows error code (e.g. 0x88985002). {1} is the corresponding message.</comment>
|
||||
|
||||
@@ -217,6 +217,10 @@
|
||||
<value>Цйǻьľė ŧò ƒіņð τнė ƒσℓľоŵíпğ ƒŏптš: {0}. Рĺёąšė èìťђėг ïŋŝţăĺℓ тħēm ôѓ ςђоōѕз ðįƒƒеřęʼnţ ƒóπţś. !!! !!! !!! !!! !!! !!! !!! !!! !!! !</value>
|
||||
<comment>{Locked="{0}"} This is a warning dialog shown when the user selects a font that isn't installed.</comment>
|
||||
</data>
|
||||
<data name="RendererErrorMacType" xml:space="preserve">
|
||||
<value>Ύοϋг νέяšîǿŋ бƒ MacType їš ìⁿçомрāţΐвŀè шιťћ тĥĩѕ ªрφℓíċąţіōņ. Рĺэаşέ űφđаτє τő νзřѕíбʼn 2023.5.31 бŗ ℓâţëґ. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!</value>
|
||||
<comment>{Locked="2023.5.31","MacType"}</comment>
|
||||
</data>
|
||||
<data name="RendererErrorOther" xml:space="preserve">
|
||||
<value>Гêʼnďěѓēŗ эήçομήťєřĕď åń µŋέхρэсťèð ęґяöя: {0:#010×} {1} !!! !!! !!! !!! !!! !</value>
|
||||
<comment>{Locked="{0:#010x}","{1}"} {0:#010x} is a placeholder for a Windows error code (e.g. 0x88985002). {1} is the corresponding message.</comment>
|
||||
|
||||
@@ -217,6 +217,10 @@
|
||||
<value>Не удалось найти следующие шрифты: {0}. Установите их или выберите другие шрифты.</value>
|
||||
<comment>{Locked="{0}"} This is a warning dialog shown when the user selects a font that isn't installed.</comment>
|
||||
</data>
|
||||
<data name="RendererErrorMacType" xml:space="preserve">
|
||||
<value>Эта версия MacType несовместима с этим приложением. Обновите версию до 2023.5.31 или более поздней.</value>
|
||||
<comment>{Locked="2023.5.31","MacType"}</comment>
|
||||
</data>
|
||||
<data name="RendererErrorOther" xml:space="preserve">
|
||||
<value>В обработчике произошла непредвиденная ошибка: {0:#010x} {1}</value>
|
||||
<comment>{Locked="{0:#010x}","{1}"} {0:#010x} is a placeholder for a Windows error code (e.g. 0x88985002). {1} is the corresponding message.</comment>
|
||||
|
||||
@@ -217,6 +217,10 @@
|
||||
<value>找不到以下字体: {0}。请安装它们或选择其他字体。</value>
|
||||
<comment>{Locked="{0}"} This is a warning dialog shown when the user selects a font that isn't installed.</comment>
|
||||
</data>
|
||||
<data name="RendererErrorMacType" xml:space="preserve">
|
||||
<value>你的 MacType 版本与此应用程序不兼容。请更新到版本 2023.5.31 或更高版本。</value>
|
||||
<comment>{Locked="2023.5.31","MacType"}</comment>
|
||||
</data>
|
||||
<data name="RendererErrorOther" xml:space="preserve">
|
||||
<value>呈现器遇到错误: {0:#010x} {1}</value>
|
||||
<comment>{Locked="{0:#010x}","{1}"} {0:#010x} is a placeholder for a Windows error code (e.g. 0x88985002). {1} is the corresponding message.</comment>
|
||||
|
||||
@@ -217,6 +217,10 @@
|
||||
<value>找不到下列字型: {0}。請安裝它們,或選擇其他字型。</value>
|
||||
<comment>{Locked="{0}"} This is a warning dialog shown when the user selects a font that isn't installed.</comment>
|
||||
</data>
|
||||
<data name="RendererErrorMacType" xml:space="preserve">
|
||||
<value>您的 MacType 版本與此應用程式不相容。請更新至 2023.5.31 版或更新版本。</value>
|
||||
<comment>{Locked="2023.5.31","MacType"}</comment>
|
||||
</data>
|
||||
<data name="RendererErrorOther" xml:space="preserve">
|
||||
<value>轉譯器發生意外的錯誤: {0:#010x} {1}</value>
|
||||
<comment>{Locked="{0:#010x}","{1}"} {0:#010x} is a placeholder for a Windows error code (e.g. 0x88985002). {1} is the corresponding message.</comment>
|
||||
|
||||
@@ -154,12 +154,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// search box remains in Visible state (though not really *visible*) during the
|
||||
// first load. So, we only need to apply this check here (after checking that
|
||||
// we're done initializing).
|
||||
if (Visibility() == Visibility::Visible)
|
||||
if (IsOpen())
|
||||
{
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop ongoing close animation if any
|
||||
if (CloseAnimation().GetCurrentState() == Media::Animation::ClockState::Active)
|
||||
{
|
||||
CloseAnimation().Stop();
|
||||
}
|
||||
|
||||
VisualStateManager::GoToState(*this, L"Opened", false);
|
||||
|
||||
// Call the callback only after we're in Opened state. Setting focus
|
||||
@@ -195,6 +201,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
}
|
||||
}
|
||||
|
||||
bool SearchBoxControl::IsOpen()
|
||||
{
|
||||
return Visibility() == Visibility::Visible && CloseAnimation().GetCurrentState() != Media::Animation::ClockState::Active;
|
||||
}
|
||||
|
||||
winrt::hstring SearchBoxControl::Text()
|
||||
{
|
||||
return TextBox().Text();
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
void TextBoxKeyDown(const winrt::Windows::Foundation::IInspectable& /*sender*/, const winrt::Windows::UI::Xaml::Input::KeyRoutedEventArgs& e);
|
||||
void Open(std::function<void()> callback);
|
||||
void Close();
|
||||
bool IsOpen();
|
||||
|
||||
winrt::hstring Text();
|
||||
bool GoForward();
|
||||
|
||||
@@ -248,7 +248,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
dispatcher,
|
||||
TerminalWarningBellInterval,
|
||||
[weakThis = get_weak()]() {
|
||||
if (auto control{ weakThis.get() }; !control->_IsClosing())
|
||||
if (auto control{ weakThis.get() }; control && !control->_IsClosing())
|
||||
{
|
||||
control->WarningBell.raise(*control, nullptr);
|
||||
}
|
||||
@@ -258,7 +258,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
dispatcher,
|
||||
ScrollBarUpdateInterval,
|
||||
[weakThis = get_weak()](const auto& update) {
|
||||
if (auto control{ weakThis.get() }; !control->_IsClosing())
|
||||
if (auto control{ weakThis.get() }; control && !control->_IsClosing())
|
||||
{
|
||||
control->_throttledUpdateScrollbar(update);
|
||||
}
|
||||
@@ -301,7 +301,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
_originalSelectedSecondaryElements.Append(e);
|
||||
}
|
||||
ContextMenu().Closed([weakThis = get_weak()](auto&&, auto&&) {
|
||||
if (auto control{ weakThis.get() }; !control->_IsClosing())
|
||||
if (auto control{ weakThis.get() }; control && !control->_IsClosing())
|
||||
{
|
||||
const auto& menu{ control->ContextMenu() };
|
||||
menu.PrimaryCommands().Clear();
|
||||
@@ -317,7 +317,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
}
|
||||
});
|
||||
SelectionContextMenu().Closed([weakThis = get_weak()](auto&&, auto&&) {
|
||||
if (auto control{ weakThis.get() }; !control->_IsClosing())
|
||||
if (auto control{ weakThis.get() }; control && !control->_IsClosing())
|
||||
{
|
||||
const auto& menu{ control->SelectionContextMenu() };
|
||||
menu.PrimaryCommands().Clear();
|
||||
@@ -493,7 +493,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
}
|
||||
}
|
||||
|
||||
if (_searchBox && _searchBox->Visibility() == Visibility::Visible)
|
||||
if (_searchBox && _searchBox->IsOpen())
|
||||
{
|
||||
const auto core = winrt::get_self<ControlCore>(_core);
|
||||
const auto& searchMatches = core->SearchResultRows();
|
||||
@@ -538,13 +538,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// but since code paths differ, extra work is required to ensure correctness.
|
||||
if (!_core.HasMultiLineSelection())
|
||||
{
|
||||
_core.SnapSearchResultToSelection(true);
|
||||
const auto selectedLine{ _core.SelectedText(true) };
|
||||
_searchBox->PopulateTextbox(selectedLine);
|
||||
}
|
||||
}
|
||||
|
||||
_searchBox->Open([weakThis = get_weak()]() {
|
||||
if (const auto self = weakThis.get(); !self->_IsClosing())
|
||||
if (const auto self = weakThis.get(); self && !self->_IsClosing())
|
||||
{
|
||||
self->_searchBox->SetFocusOnTextbox();
|
||||
self->_refreshSearch();
|
||||
@@ -554,13 +555,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
}
|
||||
}
|
||||
|
||||
// This is called when a Find Next/Previous Match action is triggered.
|
||||
void TermControl::SearchMatch(const bool goForward)
|
||||
{
|
||||
if (_IsClosing())
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!_searchBox || _searchBox->Visibility() != Visibility::Visible)
|
||||
if (!_searchBox || !_searchBox->IsOpen())
|
||||
{
|
||||
CreateSearchBoxControl();
|
||||
}
|
||||
@@ -597,11 +599,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
const bool goForward,
|
||||
const bool caseSensitive)
|
||||
{
|
||||
_handleSearchResults(_core.Search(text, goForward, caseSensitive, false));
|
||||
if (_searchBox && _searchBox->IsOpen())
|
||||
{
|
||||
_handleSearchResults(_core.Search(text, goForward, caseSensitive, false));
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - The handler for the "search criteria changed" event. Clears selection and initiates a new search.
|
||||
// - The handler for the "search criteria changed" event. Initiates a new search.
|
||||
// Arguments:
|
||||
// - text: the text to search
|
||||
// - goForward: indicates whether the search should be performed forward (if set to true) or backward
|
||||
@@ -612,9 +617,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
const bool goForward,
|
||||
const bool caseSensitive)
|
||||
{
|
||||
if (_searchBox && _searchBox->Visibility() == Visibility::Visible)
|
||||
if (_searchBox && _searchBox->IsOpen())
|
||||
{
|
||||
_handleSearchResults(_core.Search(text, goForward, caseSensitive, false));
|
||||
// We only want to update the search results based on the new text. Set
|
||||
// `resetOnly` to true so we don't accidentally update the current match index.
|
||||
const auto result = _core.Search(text, goForward, caseSensitive, true);
|
||||
_handleSearchResults(result);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -632,6 +640,19 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
_searchBox->Close();
|
||||
_core.ClearSearch();
|
||||
|
||||
// Clear search highlights scroll marks (by triggering an update after closing the search box)
|
||||
if (_showMarksInScrollbar)
|
||||
{
|
||||
const auto scrollBar = ScrollBar();
|
||||
ScrollBarUpdate update{
|
||||
.newValue = scrollBar.Value(),
|
||||
.newMaximum = scrollBar.Maximum(),
|
||||
.newMinimum = scrollBar.Minimum(),
|
||||
.newViewportSize = scrollBar.ViewportSize(),
|
||||
};
|
||||
_updateScrollBar->Run(update);
|
||||
}
|
||||
|
||||
// Set focus back to terminal control
|
||||
this->Focus(FocusState::Programmatic);
|
||||
}
|
||||
@@ -1055,8 +1076,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
// MSFT 33353327: We're purposefully not using _initializedTerminal to ensure we're fully initialized.
|
||||
// Doing so makes us return nullptr when XAML requests an automation peer.
|
||||
// Instead, we need to give XAML an automation peer, then fix it later.
|
||||
if (!_IsClosing())
|
||||
if (!_IsClosing() && !_detached)
|
||||
{
|
||||
// It's unexpected that interactivity is null even when we're not closing or in detached state.
|
||||
THROW_HR_IF_NULL(E_UNEXPECTED, _interactivity);
|
||||
|
||||
// create a custom automation peer with this code pattern:
|
||||
// (https://docs.microsoft.com/en-us/windows/uwp/design/accessibility/custom-automation-peers)
|
||||
if (const auto& interactivityAutoPeer{ _interactivity.OnCreateAutomationPeer() })
|
||||
@@ -1120,7 +1144,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
co_return;
|
||||
}
|
||||
|
||||
const auto hr = args.Result();
|
||||
// HRESULT is a signed 32-bit integer which would result in a hex output like "-0x7766FFF4",
|
||||
// but canonically HRESULTs are displayed unsigned as "0x8899000C". See GH#11556.
|
||||
const auto hr = std::bit_cast<uint32_t>(args.Result());
|
||||
const auto parameter = args.Parameter();
|
||||
winrt::hstring message;
|
||||
|
||||
@@ -1136,6 +1162,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
case DWRITE_E_NOFONT:
|
||||
message = winrt::hstring{ fmt::format(std::wstring_view{ RS_(L"RendererErrorFontNotFound") }, parameter) };
|
||||
break;
|
||||
case ATLAS_ENGINE_ERROR_MAC_TYPE:
|
||||
message = RS_(L"RendererErrorMacType");
|
||||
break;
|
||||
default:
|
||||
{
|
||||
wchar_t buf[512];
|
||||
@@ -3588,7 +3617,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
|
||||
void TermControl::_refreshSearch()
|
||||
{
|
||||
if (!_searchBox || _searchBox->Visibility() != Visibility::Visible)
|
||||
if (!_searchBox || !_searchBox->IsOpen())
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -3611,7 +3640,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
||||
return;
|
||||
}
|
||||
|
||||
_searchBox->SetStatus(results.TotalMatches, results.CurrentMatch);
|
||||
// Only show status when we have a search term
|
||||
if (_searchBox->Text().empty())
|
||||
{
|
||||
_searchBox->ClearStatus();
|
||||
}
|
||||
else
|
||||
{
|
||||
_searchBox->SetStatus(results.TotalMatches, results.CurrentMatch);
|
||||
}
|
||||
|
||||
if (results.SearchInvalidated)
|
||||
{
|
||||
|
||||
@@ -497,6 +497,10 @@ std::wstring Terminal::GetHyperlinkAtBufferPosition(const til::point bufferPos)
|
||||
result = GetHyperlinkIntervalFromViewportPosition(viewportPos);
|
||||
if (result.has_value())
|
||||
{
|
||||
// GetPlainText and _ConvertToBufferCell work with inclusive coordinates, but interval's
|
||||
// stop point is (horizontally) exclusive, so let's just update it.
|
||||
result->stop.x--;
|
||||
|
||||
result->start = _ConvertToBufferCell(result->start);
|
||||
result->stop = _ConvertToBufferCell(result->stop);
|
||||
}
|
||||
|
||||
@@ -895,6 +895,9 @@
|
||||
<value>Schriftart</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>Sie können mehrere Schriftarten verwenden, indem Sie sie durch ein ASCII-Komma voneinander trennen.</value>
|
||||
</data>
|
||||
<data name="Profile_FontSize.Header" xml:space="preserve">
|
||||
<value>Schriftgrad</value>
|
||||
<comment>Header for a control to determine the size of the text in the app.</comment>
|
||||
@@ -928,7 +931,7 @@
|
||||
<comment>The main label of a toggle. When enabled, certain characters (glyphs) are replaced with better looking ones.</comment>
|
||||
</data>
|
||||
<data name="Profile_EnableBuiltinGlyphs.HelpText" xml:space="preserve">
|
||||
<value>Wenn diese Option aktiviert ist, zeichnet das Terminal benutzerdefinierte Glyphen für Blockelement- und Feldzeichnungszeichen, anstatt die Schriftart zu verwenden. Dieses Feature funktioniert nur, wenn GPU-Beschleunigung verfügbar ist.</value>
|
||||
<value>Wenn diese Option aktiviert ist, zeichnet das Terminal benutzerdefinierte Glyphen für Blockelement- und Rahmenelementzeichen, anstatt die Schriftart zu verwenden.</value>
|
||||
<comment>A longer description of the "Profile_EnableBuiltinGlyphs" toggle. "glyphs", "block element" and "box drawing characters" are technical terms from the Unicode specification.</comment>
|
||||
</data>
|
||||
<data name="Profile_EnableColorGlyphs.Header" xml:space="preserve">
|
||||
|
||||
@@ -895,6 +895,9 @@
|
||||
<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>You can use multiple fonts by separating them with an ASCII comma.</value>
|
||||
</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>
|
||||
@@ -928,7 +931,7 @@
|
||||
<comment>The main label of a toggle. When enabled, certain characters (glyphs) are replaced with better looking ones.</comment>
|
||||
</data>
|
||||
<data name="Profile_EnableBuiltinGlyphs.HelpText" xml:space="preserve">
|
||||
<value>When enabled, the terminal draws custom glyphs for block element and box drawing characters instead of using the font. This feature only works when GPU Acceleration is available.</value>
|
||||
<value>When enabled, the terminal draws custom glyphs for block element and box drawing characters instead of using the font.</value>
|
||||
<comment>A longer description of the "Profile_EnableBuiltinGlyphs" toggle. "glyphs", "block element" and "box drawing characters" are technical terms from the Unicode specification.</comment>
|
||||
</data>
|
||||
<data name="Profile_EnableColorGlyphs.Header" xml:space="preserve">
|
||||
|
||||
@@ -733,10 +733,16 @@
|
||||
<value>Examinar...</value>
|
||||
<comment>Button label that opens a file picker in a new window. The "..." is standard to mean it will open a new window.</comment>
|
||||
</data>
|
||||
<data name="Profile_AddFontAxisButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Agregar nuevo eje de fuente</value>
|
||||
</data>
|
||||
<data name="Profile_AddNewFontAxis.Text" xml:space="preserve">
|
||||
<value>Agregar nuevo</value>
|
||||
<comment>Button label that adds a new font axis for the current font.</comment>
|
||||
</data>
|
||||
<data name="Profile_AddFontFeatureButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Agregar nueva característica de fuente</value>
|
||||
</data>
|
||||
<data name="Profile_AddNewFontFeature.Text" xml:space="preserve">
|
||||
<value>Agregar nuevo</value>
|
||||
<comment>Button label that adds a new font feature for the current font.</comment>
|
||||
@@ -889,6 +895,9 @@
|
||||
<value>Tipo de fuente</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>Puede usar varias fuentes separándolas con una coma ASCII.</value>
|
||||
</data>
|
||||
<data name="Profile_FontSize.Header" xml:space="preserve">
|
||||
<value>Tamaño de la fuente</value>
|
||||
<comment>Header for a control to determine the size of the text in the app.</comment>
|
||||
@@ -922,7 +931,7 @@
|
||||
<comment>The main label of a toggle. When enabled, certain characters (glyphs) are replaced with better looking ones.</comment>
|
||||
</data>
|
||||
<data name="Profile_EnableBuiltinGlyphs.HelpText" xml:space="preserve">
|
||||
<value>Cuando está habilitado, el terminal dibuja glifos personalizados para los caracteres de dibujo de elementos de bloque y cuadros, en lugar de usar la fuente. Esta característica solo funciona cuando la aceleración de GPU está disponible.</value>
|
||||
<value>Cuando está habilitado, el terminal dibuja glifos personalizados para los caracteres de dibujo de elementos de bloque y cuadros, en lugar de usar la fuente.</value>
|
||||
<comment>A longer description of the "Profile_EnableBuiltinGlyphs" toggle. "glyphs", "block element" and "box drawing characters" are technical terms from the Unicode specification.</comment>
|
||||
</data>
|
||||
<data name="Profile_EnableColorGlyphs.Header" xml:space="preserve">
|
||||
|
||||
@@ -895,6 +895,9 @@
|
||||
<value>Type de police</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>Vous pouvez utiliser plusieurs polices en les séparant par une virgule ASCII.</value>
|
||||
</data>
|
||||
<data name="Profile_FontSize.Header" xml:space="preserve">
|
||||
<value>Taille de police</value>
|
||||
<comment>Header for a control to determine the size of the text in the app.</comment>
|
||||
@@ -928,7 +931,7 @@
|
||||
<comment>The main label of a toggle. When enabled, certain characters (glyphs) are replaced with better looking ones.</comment>
|
||||
</data>
|
||||
<data name="Profile_EnableBuiltinGlyphs.HelpText" xml:space="preserve">
|
||||
<value>Lorsque cette option est activée, le terminal dessine des glyphes personnalisés pour les éléments de bloc et les caractères de dessin de boîte au lieu d’utiliser la police. Cette fonctionnalité ne fonctionne que lorsque l’accélération GPU est disponible.</value>
|
||||
<value>Lorsque cette option est activée, le terminal trace des glyphes personnalisés pour les éléments de bloc et pour les caractères de dessin de boîte au lieu d'utiliser la police de caractère.</value>
|
||||
<comment>A longer description of the "Profile_EnableBuiltinGlyphs" toggle. "glyphs", "block element" and "box drawing characters" are technical terms from the Unicode specification.</comment>
|
||||
</data>
|
||||
<data name="Profile_EnableColorGlyphs.Header" xml:space="preserve">
|
||||
|
||||
@@ -733,10 +733,16 @@
|
||||
<value>Sfoglia...</value>
|
||||
<comment>Button label that opens a file picker in a new window. The "..." is standard to mean it will open a new window.</comment>
|
||||
</data>
|
||||
<data name="Profile_AddFontAxisButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Aggiungi nuovo asse dei tipi di carattere</value>
|
||||
</data>
|
||||
<data name="Profile_AddNewFontAxis.Text" xml:space="preserve">
|
||||
<value>Aggiungi nuovo</value>
|
||||
<comment>Button label that adds a new font axis for the current font.</comment>
|
||||
</data>
|
||||
<data name="Profile_AddFontFeatureButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Aggiungi nuova funzionalità dei tipi di carattere</value>
|
||||
</data>
|
||||
<data name="Profile_AddNewFontFeature.Text" xml:space="preserve">
|
||||
<value>Aggiungi nuovo</value>
|
||||
<comment>Button label that adds a new font feature for the current font.</comment>
|
||||
@@ -889,6 +895,9 @@
|
||||
<value>Tipo di carattere</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>Puoi usare più tipi di carattere separandoli con una virgola ASCII.</value>
|
||||
</data>
|
||||
<data name="Profile_FontSize.Header" xml:space="preserve">
|
||||
<value>Dimensioni del carattere</value>
|
||||
<comment>Header for a control to determine the size of the text in the app.</comment>
|
||||
@@ -922,7 +931,7 @@
|
||||
<comment>The main label of a toggle. When enabled, certain characters (glyphs) are replaced with better looking ones.</comment>
|
||||
</data>
|
||||
<data name="Profile_EnableBuiltinGlyphs.HelpText" xml:space="preserve">
|
||||
<value>Se abilitata, il terminale disegna glifi personalizzati per i caratteri di disegno a blocchi di elementi e caselle anziché usare il tipo di carattere. Questa funzionalità funziona solo quando è disponibile l'accelerazione GPU.</value>
|
||||
<value>Se abilitato, il terminale disegna glifi personalizzati per i caratteri di disegno a blocchi di elementi e caselle anziché usare il tipo di carattere.</value>
|
||||
<comment>A longer description of the "Profile_EnableBuiltinGlyphs" toggle. "glyphs", "block element" and "box drawing characters" are technical terms from the Unicode specification.</comment>
|
||||
</data>
|
||||
<data name="Profile_EnableColorGlyphs.Header" xml:space="preserve">
|
||||
|
||||
@@ -733,10 +733,16 @@
|
||||
<value>参照...</value>
|
||||
<comment>Button label that opens a file picker in a new window. The "..." is standard to mean it will open a new window.</comment>
|
||||
</data>
|
||||
<data name="Profile_AddFontAxisButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>新しいフォント軸の追加</value>
|
||||
</data>
|
||||
<data name="Profile_AddNewFontAxis.Text" xml:space="preserve">
|
||||
<value>新規追加</value>
|
||||
<comment>Button label that adds a new font axis for the current font.</comment>
|
||||
</data>
|
||||
<data name="Profile_AddFontFeatureButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>新しいフォント機能の追加</value>
|
||||
</data>
|
||||
<data name="Profile_AddNewFontFeature.Text" xml:space="preserve">
|
||||
<value>新規追加</value>
|
||||
<comment>Button label that adds a new font feature for the current font.</comment>
|
||||
@@ -889,6 +895,9 @@
|
||||
<value>フォント フェイス</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>複数のフォントを使用するには、複数のフォントを ASCII コンマで区切ります。</value>
|
||||
</data>
|
||||
<data name="Profile_FontSize.Header" xml:space="preserve">
|
||||
<value>フォント サイズ</value>
|
||||
<comment>Header for a control to determine the size of the text in the app.</comment>
|
||||
@@ -922,7 +931,7 @@
|
||||
<comment>The main label of a toggle. When enabled, certain characters (glyphs) are replaced with better looking ones.</comment>
|
||||
</data>
|
||||
<data name="Profile_EnableBuiltinGlyphs.HelpText" xml:space="preserve">
|
||||
<value>有効にすると、ターミナルは、フォントを使用する代わりに、ブロック要素とボックス描画文字のカスタム グリフを描画します。この機能は、GPU アクセラレーションが使用可能な場合にのみ機能します。</value>
|
||||
<value>有効にすると、ターミナルは、フォントを使用する代わりに、ブロック要素とボックス描画文字のカスタム グリフを描画します。</value>
|
||||
<comment>A longer description of the "Profile_EnableBuiltinGlyphs" toggle. "glyphs", "block element" and "box drawing characters" are technical terms from the Unicode specification.</comment>
|
||||
</data>
|
||||
<data name="Profile_EnableColorGlyphs.Header" xml:space="preserve">
|
||||
|
||||
@@ -733,10 +733,16 @@
|
||||
<value>찾아보기...</value>
|
||||
<comment>Button label that opens a file picker in a new window. The "..." is standard to mean it will open a new window.</comment>
|
||||
</data>
|
||||
<data name="Profile_AddFontAxisButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>새 글꼴 추가 축</value>
|
||||
</data>
|
||||
<data name="Profile_AddNewFontAxis.Text" xml:space="preserve">
|
||||
<value>새로 추가</value>
|
||||
<comment>Button label that adds a new font axis for the current font.</comment>
|
||||
</data>
|
||||
<data name="Profile_AddFontFeatureButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>새 글꼴 추가 기능</value>
|
||||
</data>
|
||||
<data name="Profile_AddNewFontFeature.Text" xml:space="preserve">
|
||||
<value>새로 추가</value>
|
||||
<comment>Button label that adds a new font feature for the current font.</comment>
|
||||
@@ -889,6 +895,9 @@
|
||||
<value>글꼴</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>여러 글꼴을 ASCII 쉼표로 구분하여 사용할 수 있습니다.</value>
|
||||
</data>
|
||||
<data name="Profile_FontSize.Header" xml:space="preserve">
|
||||
<value>글꼴 크기</value>
|
||||
<comment>Header for a control to determine the size of the text in the app.</comment>
|
||||
@@ -922,7 +931,7 @@
|
||||
<comment>The main label of a toggle. When enabled, certain characters (glyphs) are replaced with better looking ones.</comment>
|
||||
</data>
|
||||
<data name="Profile_EnableBuiltinGlyphs.HelpText" xml:space="preserve">
|
||||
<value>사용하도록 설정하면 터미널은 글꼴을 사용하는 대신 블록 요소 및 상자 그리기 문자에 대한 사용자 지정 문자 모양을 그립니다. 이 기능은 GPU 가속을 사용할 수 있는 경우에만 작동합니다.</value>
|
||||
<value>사용하도록 설정하면 터미널은 글꼴을 사용하는 대신 블록 요소 및 상자 그리기 문자에 대한 사용자 지정 문자 모양을 그립니다.</value>
|
||||
<comment>A longer description of the "Profile_EnableBuiltinGlyphs" toggle. "glyphs", "block element" and "box drawing characters" are technical terms from the Unicode specification.</comment>
|
||||
</data>
|
||||
<data name="Profile_EnableColorGlyphs.Header" xml:space="preserve">
|
||||
@@ -1294,7 +1303,7 @@
|
||||
<comment>This is the formal name for a font weight.</comment>
|
||||
</data>
|
||||
<data name="Profile_FontWeightLight.Content" xml:space="preserve">
|
||||
<value>밝게</value>
|
||||
<value>가늘게</value>
|
||||
<comment>This is the formal name for a font weight.</comment>
|
||||
</data>
|
||||
<data name="Profile_FontWeightMedium.Content" xml:space="preserve">
|
||||
@@ -1306,7 +1315,7 @@
|
||||
<comment>This is the formal name for a font weight.</comment>
|
||||
</data>
|
||||
<data name="Profile_FontWeightSemi-bold.Content" xml:space="preserve">
|
||||
<value>약간 굵은</value>
|
||||
<value>약간 굵게</value>
|
||||
<comment>This is the formal name for a font weight.</comment>
|
||||
</data>
|
||||
<data name="Profile_FontWeightSemi-light.Content" xml:space="preserve">
|
||||
@@ -1314,7 +1323,7 @@
|
||||
<comment>This is the formal name for a font weight.</comment>
|
||||
</data>
|
||||
<data name="Profile_FontWeightThin.Content" xml:space="preserve">
|
||||
<value>얇은</value>
|
||||
<value>얇게</value>
|
||||
<comment>This is the formal name for a font weight.</comment>
|
||||
</data>
|
||||
<data name="Profile_FontFeature_rlig" xml:space="preserve">
|
||||
|
||||
@@ -733,10 +733,16 @@
|
||||
<value>Procurar...</value>
|
||||
<comment>Button label that opens a file picker in a new window. The "..." is standard to mean it will open a new window.</comment>
|
||||
</data>
|
||||
<data name="Profile_AddFontAxisButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Adicionar novo eixo de fonte</value>
|
||||
</data>
|
||||
<data name="Profile_AddNewFontAxis.Text" xml:space="preserve">
|
||||
<value>Adicionar novo</value>
|
||||
<comment>Button label that adds a new font axis for the current font.</comment>
|
||||
</data>
|
||||
<data name="Profile_AddFontFeatureButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||
<value>Adicionar novo recurso de fonte</value>
|
||||
</data>
|
||||
<data name="Profile_AddNewFontFeature.Text" xml:space="preserve">
|
||||
<value>Adicionar novo</value>
|
||||
<comment>Button label that adds a new font feature for the current font.</comment>
|
||||
@@ -889,6 +895,9 @@
|
||||
<value>Tipo de fonte</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>Pode utilizar vários tipos de letra separando-os com uma vírgula ASCII.</value>
|
||||
</data>
|
||||
<data name="Profile_FontSize.Header" xml:space="preserve">
|
||||
<value>Tamanho da fonte</value>
|
||||
<comment>Header for a control to determine the size of the text in the app.</comment>
|
||||
@@ -922,7 +931,7 @@
|
||||
<comment>The main label of a toggle. When enabled, certain characters (glyphs) are replaced with better looking ones.</comment>
|
||||
</data>
|
||||
<data name="Profile_EnableBuiltinGlyphs.HelpText" xml:space="preserve">
|
||||
<value>Quando habilitado, o terminal desenha glifos personalizados para caracteres de desenho de elemento de bloco e caixa em vez de usar a fonte. Esse recurso só funciona quando a Aceleração de GPU está disponível.</value>
|
||||
<value>Quando habilitado, o terminal desenha glifos personalizados para elementos de bloco e caracteres de desenho de caixa em vez de usar a fonte.</value>
|
||||
<comment>A longer description of the "Profile_EnableBuiltinGlyphs" toggle. "glyphs", "block element" and "box drawing characters" are technical terms from the Unicode specification.</comment>
|
||||
</data>
|
||||
<data name="Profile_EnableColorGlyphs.Header" xml:space="preserve">
|
||||
|
||||
@@ -895,6 +895,9 @@
|
||||
<value>₣øñŧ ƒдсέ !!!</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>Ýоύ ĉąи ύŝè мύŀŧΐρļĕ ƒőйτš ъý ѕéραгдŧĭʼnğ ţнэm щїţђ áп ∆ŚČĨĨ çőмmа. !!! !!! !!! !!! !!! !!! !</value>
|
||||
</data>
|
||||
<data name="Profile_FontSize.Header" xml:space="preserve">
|
||||
<value>₣ŏňτ şίźε !!!</value>
|
||||
<comment>Header for a control to determine the size of the text in the app.</comment>
|
||||
@@ -928,7 +931,7 @@
|
||||
<comment>The main label of a toggle. When enabled, certain characters (glyphs) are replaced with better looking ones.</comment>
|
||||
</data>
|
||||
<data name="Profile_EnableBuiltinGlyphs.HelpText" xml:space="preserve">
|
||||
<value>Ẅħĕň еņªъℓёð, тћз τēгмïπдℓ ðѓáшş çύšτом ģļўφħѕ ƒǿя ъĺôсĸ éℓêm℮ήŧ àñð ъο× ðгдωΐπĝ čћдŗąςŧέѓś ìπŝţėãđ ŏƒ ũŝіⁿğ ťћę ƒбηŧ. Ţћιş ƒзаτџŕè οʼnĺŷ ωóгκŝ ẁĥéŋ ĢΡŬ Àςĉеŀèѓдτισñ ĩѕ ǻνãîļåвłę. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!</value>
|
||||
<value>Ẅħĕň еņªъℓёð, тћз τēгмïπдℓ ðѓáшş çύšτом ģļўφħѕ ƒǿя ъĺôсĸ éℓêm℮ήŧ àñð ъο× ðгдωΐπĝ čћдŗąςŧέѓś ìπŝţėãđ ŏƒ ũŝіⁿğ ťћę ƒбηŧ. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!</value>
|
||||
<comment>A longer description of the "Profile_EnableBuiltinGlyphs" toggle. "glyphs", "block element" and "box drawing characters" are technical terms from the Unicode specification.</comment>
|
||||
</data>
|
||||
<data name="Profile_EnableColorGlyphs.Header" xml:space="preserve">
|
||||
|
||||
@@ -895,6 +895,9 @@
|
||||
<value>₣øñŧ ƒдсέ !!!</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>Ýоύ ĉąи ύŝè мύŀŧΐρļĕ ƒőйτš ъý ѕéραгдŧĭʼnğ ţнэm щїţђ áп ∆ŚČĨĨ çőмmа. !!! !!! !!! !!! !!! !!! !</value>
|
||||
</data>
|
||||
<data name="Profile_FontSize.Header" xml:space="preserve">
|
||||
<value>₣ŏňτ şίźε !!!</value>
|
||||
<comment>Header for a control to determine the size of the text in the app.</comment>
|
||||
@@ -928,7 +931,7 @@
|
||||
<comment>The main label of a toggle. When enabled, certain characters (glyphs) are replaced with better looking ones.</comment>
|
||||
</data>
|
||||
<data name="Profile_EnableBuiltinGlyphs.HelpText" xml:space="preserve">
|
||||
<value>Ẅħĕň еņªъℓёð, тћз τēгмïπдℓ ðѓáшş çύšτом ģļўφħѕ ƒǿя ъĺôсĸ éℓêm℮ήŧ àñð ъο× ðгдωΐπĝ čћдŗąςŧέѓś ìπŝţėãđ ŏƒ ũŝіⁿğ ťћę ƒбηŧ. Ţћιş ƒзаτџŕè οʼnĺŷ ωóгκŝ ẁĥéŋ ĢΡŬ Àςĉеŀèѓдτισñ ĩѕ ǻνãîļåвłę. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!</value>
|
||||
<value>Ẅħĕň еņªъℓёð, тћз τēгмïπдℓ ðѓáшş çύšτом ģļўφħѕ ƒǿя ъĺôсĸ éℓêm℮ήŧ àñð ъο× ðгдωΐπĝ čћдŗąςŧέѓś ìπŝţėãđ ŏƒ ũŝіⁿğ ťћę ƒбηŧ. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!</value>
|
||||
<comment>A longer description of the "Profile_EnableBuiltinGlyphs" toggle. "glyphs", "block element" and "box drawing characters" are technical terms from the Unicode specification.</comment>
|
||||
</data>
|
||||
<data name="Profile_EnableColorGlyphs.Header" xml:space="preserve">
|
||||
|
||||
@@ -895,6 +895,9 @@
|
||||
<value>₣øñŧ ƒдсέ !!!</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>Ýоύ ĉąи ύŝè мύŀŧΐρļĕ ƒőйτš ъý ѕéραгдŧĭʼnğ ţнэm щїţђ áп ∆ŚČĨĨ çőмmа. !!! !!! !!! !!! !!! !!! !</value>
|
||||
</data>
|
||||
<data name="Profile_FontSize.Header" xml:space="preserve">
|
||||
<value>₣ŏňτ şίźε !!!</value>
|
||||
<comment>Header for a control to determine the size of the text in the app.</comment>
|
||||
@@ -928,7 +931,7 @@
|
||||
<comment>The main label of a toggle. When enabled, certain characters (glyphs) are replaced with better looking ones.</comment>
|
||||
</data>
|
||||
<data name="Profile_EnableBuiltinGlyphs.HelpText" xml:space="preserve">
|
||||
<value>Ẅħĕň еņªъℓёð, тћз τēгмïπдℓ ðѓáшş çύšτом ģļўφħѕ ƒǿя ъĺôсĸ éℓêm℮ήŧ àñð ъο× ðгдωΐπĝ čћдŗąςŧέѓś ìπŝţėãđ ŏƒ ũŝіⁿğ ťћę ƒбηŧ. Ţћιş ƒзаτџŕè οʼnĺŷ ωóгκŝ ẁĥéŋ ĢΡŬ Àςĉеŀèѓдτισñ ĩѕ ǻνãîļåвłę. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!</value>
|
||||
<value>Ẅħĕň еņªъℓёð, тћз τēгмïπдℓ ðѓáшş çύšτом ģļўφħѕ ƒǿя ъĺôсĸ éℓêm℮ήŧ àñð ъο× ðгдωΐπĝ čћдŗąςŧέѓś ìπŝţėãđ ŏƒ ũŝіⁿğ ťћę ƒбηŧ. !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!</value>
|
||||
<comment>A longer description of the "Profile_EnableBuiltinGlyphs" toggle. "glyphs", "block element" and "box drawing characters" are technical terms from the Unicode specification.</comment>
|
||||
</data>
|
||||
<data name="Profile_EnableColorGlyphs.Header" xml:space="preserve">
|
||||
|
||||
@@ -895,6 +895,9 @@
|
||||
<value>Начертание шрифта</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>Вы можете использовать несколько шрифтов, разделив их запятой ASCII.</value>
|
||||
</data>
|
||||
<data name="Profile_FontSize.Header" xml:space="preserve">
|
||||
<value>Размер шрифта</value>
|
||||
<comment>Header for a control to determine the size of the text in the app.</comment>
|
||||
@@ -928,7 +931,7 @@
|
||||
<comment>The main label of a toggle. When enabled, certain characters (glyphs) are replaced with better looking ones.</comment>
|
||||
</data>
|
||||
<data name="Profile_EnableBuiltinGlyphs.HelpText" xml:space="preserve">
|
||||
<value>Если этот параметр включен, терминал рисует собственные глифы для элементов блока и символов рисования прямоугольников вместо использования шрифта. Эта функция работает только тогда, когда доступно ускорение графического процессора.</value>
|
||||
<value>Если этот параметр включен, терминал рисует собственные глифы для элементов блока и символов рисования прямоугольников вместо использования шрифта.</value>
|
||||
<comment>A longer description of the "Profile_EnableBuiltinGlyphs" toggle. "glyphs", "block element" and "box drawing characters" are technical terms from the Unicode specification.</comment>
|
||||
</data>
|
||||
<data name="Profile_EnableColorGlyphs.Header" xml:space="preserve">
|
||||
|
||||
@@ -895,6 +895,9 @@
|
||||
<value>字体</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>通过使用 ASCII 逗号分隔多个字体,可以使用这些字体。</value>
|
||||
</data>
|
||||
<data name="Profile_FontSize.Header" xml:space="preserve">
|
||||
<value>字号</value>
|
||||
<comment>Header for a control to determine the size of the text in the app.</comment>
|
||||
@@ -928,7 +931,7 @@
|
||||
<comment>The main label of a toggle. When enabled, certain characters (glyphs) are replaced with better looking ones.</comment>
|
||||
</data>
|
||||
<data name="Profile_EnableBuiltinGlyphs.HelpText" xml:space="preserve">
|
||||
<value>启用后,终端将为块元素和制表符绘制自定义标志符号,而不是使用字体。仅当 GPU 加速可用时,此功能才有效。</value>
|
||||
<value>启用后,终端会使用自定义字形来绘制块元素和框线绘图字符,而不是使用字体。</value>
|
||||
<comment>A longer description of the "Profile_EnableBuiltinGlyphs" toggle. "glyphs", "block element" and "box drawing characters" are technical terms from the Unicode specification.</comment>
|
||||
</data>
|
||||
<data name="Profile_EnableColorGlyphs.Header" xml:space="preserve">
|
||||
|
||||
@@ -895,6 +895,9 @@
|
||||
<value>字體</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>您可以使用 ASCII 逗號分隔多個字型。</value>
|
||||
</data>
|
||||
<data name="Profile_FontSize.Header" xml:space="preserve">
|
||||
<value>字型大小</value>
|
||||
<comment>Header for a control to determine the size of the text in the app.</comment>
|
||||
@@ -928,7 +931,7 @@
|
||||
<comment>The main label of a toggle. When enabled, certain characters (glyphs) are replaced with better looking ones.</comment>
|
||||
</data>
|
||||
<data name="Profile_EnableBuiltinGlyphs.HelpText" xml:space="preserve">
|
||||
<value>啟用時,終端機會為區塊元素和方塊繪圖字元繪製自訂字符,而非使用字型。此功能僅在可使用 GPU 加速時作用。</value>
|
||||
<value>啟用時,終端機會為區塊元素和方塊繪圖字元繪製自訂字符,而非使用字型。</value>
|
||||
<comment>A longer description of the "Profile_EnableBuiltinGlyphs" toggle. "glyphs", "block element" and "box drawing characters" are technical terms from the Unicode specification.</comment>
|
||||
</data>
|
||||
<data name="Profile_EnableColorGlyphs.Header" xml:space="preserve">
|
||||
|
||||
@@ -690,7 +690,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
auto data = winrt::to_string(json);
|
||||
|
||||
std::string errs;
|
||||
static std::unique_ptr<Json::CharReader> reader{ Json::CharReaderBuilder{}.newCharReader() };
|
||||
std::unique_ptr<Json::CharReader> reader{ Json::CharReaderBuilder{}.newCharReader() };
|
||||
Json::Value root;
|
||||
if (!reader->parse(data.data(), data.data() + data.size(), &root, &errs))
|
||||
{
|
||||
|
||||
@@ -124,7 +124,7 @@
|
||||
<value>Nueva pestaña</value>
|
||||
</data>
|
||||
<data name="SplitPaneParentCommandName" xml:space="preserve">
|
||||
<value>Panel dividido</value>
|
||||
<value>Dividir panel</value>
|
||||
</data>
|
||||
<data name="ApplicationDisplayNamePortable" xml:space="preserve">
|
||||
<value>Terminal (portátil)</value>
|
||||
@@ -187,7 +187,7 @@
|
||||
<value>Cerrar todas las pestañas después de la pestaña actual</value>
|
||||
</data>
|
||||
<data name="CloseWindowCommandKey" xml:space="preserve">
|
||||
<value>Cierre la ventana</value>
|
||||
<value>Cerrar ventana</value>
|
||||
</data>
|
||||
<data name="OpenSystemMenuCommandKey" xml:space="preserve">
|
||||
<value>Abrir menú del sistema</value>
|
||||
@@ -276,10 +276,10 @@
|
||||
<value>Mover el foco al panel menor</value>
|
||||
</data>
|
||||
<data name="SwapPaneCommandKey" xml:space="preserve">
|
||||
<value>Panel de intercambio</value>
|
||||
<value>Intercambiar panel</value>
|
||||
</data>
|
||||
<data name="SwapPaneWithArgCommandKey" xml:space="preserve">
|
||||
<value>{0} panel de intercambio</value>
|
||||
<value>Intercambiar panel hacia {0}</value>
|
||||
<comment>{0} will be replaced with one of the four directions "DirectionLeft", "DirectionRight", "DirectionUp", "DirectionDown"</comment>
|
||||
</data>
|
||||
<data name="SwapPaneToLastUsedPane" xml:space="preserve">
|
||||
@@ -307,7 +307,7 @@
|
||||
<value>Identificar ventanas</value>
|
||||
</data>
|
||||
<data name="NextTabCommandKey" xml:space="preserve">
|
||||
<value>Siguiente pestaña</value>
|
||||
<value>Pestaña siguiente</value>
|
||||
</data>
|
||||
<data name="OpenBothSettingsFilesCommandKey" xml:space="preserve">
|
||||
<value>Abrir archivos de configuración y configuración predeterminada (JSON)</value>
|
||||
@@ -357,24 +357,24 @@
|
||||
<comment>{0} will be replaced with one of the four directions "DirectionLeft", "DirectionRight", "DirectionUp", or "DirectionDown"</comment>
|
||||
</data>
|
||||
<data name="ScrollDownCommandKey" xml:space="preserve">
|
||||
<value>Desplazar hacia abajo</value>
|
||||
<value>Desplazarse hacia abajo</value>
|
||||
</data>
|
||||
<data name="ScrollDownSeveralRowsCommandKey" xml:space="preserve">
|
||||
<value>Desplazar hacia abajo {0} línea(s)</value>
|
||||
<value>Desplazarse hacia abajo {0} línea(s)</value>
|
||||
<comment>{0} will be replaced with the number of lines to scroll"</comment>
|
||||
</data>
|
||||
<data name="ScrollDownPageCommandKey" xml:space="preserve">
|
||||
<value>Desplácese hacia abajo una página</value>
|
||||
<value>Desplazarse hacia abajo una página</value>
|
||||
</data>
|
||||
<data name="ScrollUpCommandKey" xml:space="preserve">
|
||||
<value>Desplazar hacia arriba</value>
|
||||
<value>Desplazarse hacia arriba</value>
|
||||
</data>
|
||||
<data name="ScrollUpSeveralRowsCommandKey" xml:space="preserve">
|
||||
<value>Desplazar hacia arriba {0} línea(s)</value>
|
||||
<value>Desplazarse hacia arriba {0} línea(s)</value>
|
||||
<comment>{0} will be replaced with the number of lines to scroll"</comment>
|
||||
</data>
|
||||
<data name="ScrollUpPageCommandKey" xml:space="preserve">
|
||||
<value>Desplácese hacia arriba una página</value>
|
||||
<value>Desplazarse hacia arriba una página</value>
|
||||
</data>
|
||||
<data name="ScrollToTopCommandKey" xml:space="preserve">
|
||||
<value>Desplazarse a la parte superior del historial</value>
|
||||
@@ -429,7 +429,7 @@
|
||||
<value>Mover el panel a una nueva ventana</value>
|
||||
</data>
|
||||
<data name="SplitPaneCommandKey" xml:space="preserve">
|
||||
<value>Panel dividido</value>
|
||||
<value>Dividir panel</value>
|
||||
</data>
|
||||
<data name="SplitVerticalCommandKey" xml:space="preserve">
|
||||
<value>Dividir el panel verticalmente</value>
|
||||
@@ -487,7 +487,7 @@
|
||||
<value>Alternar la orientación de la división del panel</value>
|
||||
</data>
|
||||
<data name="TogglePaneZoomCommandKey" xml:space="preserve">
|
||||
<value>Zoom de panel de alternancia</value>
|
||||
<value>Alternar zoom del panel</value>
|
||||
</data>
|
||||
<data name="TogglePaneReadOnlyCommandKey" xml:space="preserve">
|
||||
<value>Activar o desactivar el modo de solo lectura del panel</value>
|
||||
@@ -524,7 +524,7 @@
|
||||
<value>Mostrar u ocultar la ventana de terminal</value>
|
||||
</data>
|
||||
<data name="QuakeModeCommandKey" xml:space="preserve">
|
||||
<value>Mostrar u ocultar ventana Desastre</value>
|
||||
<value>Mostrar u ocultar ventana Quake</value>
|
||||
<comment>Quake is a well-known computer game and isn't related to earthquakes, etc. See https://en.wikipedia.org/wiki/Quake_(video_game)</comment>
|
||||
</data>
|
||||
<data name="FocusPaneCommandKey" xml:space="preserve">
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace ControlUnitTests
|
||||
|
||||
TEST_METHOD(TestSelectCommandSimple);
|
||||
TEST_METHOD(TestSelectOutputSimple);
|
||||
TEST_METHOD(TestCommandContext);
|
||||
TEST_METHOD(TestSelectOutputScrolling);
|
||||
TEST_METHOD(TestSelectOutputExactWrap);
|
||||
|
||||
@@ -509,6 +510,56 @@ namespace ControlUnitTests
|
||||
VERIFY_ARE_EQUAL(expectedEnd, end);
|
||||
}
|
||||
}
|
||||
void ControlCoreTests::TestCommandContext()
|
||||
{
|
||||
auto [settings, conn] = _createSettingsAndConnection();
|
||||
Log::Comment(L"Create ControlCore object");
|
||||
auto core = createCore(*settings, *conn);
|
||||
VERIFY_IS_NOT_NULL(core);
|
||||
_standardInit(core);
|
||||
|
||||
Log::Comment(L"Print some text");
|
||||
|
||||
_writePrompt(conn, L"C:\\Windows");
|
||||
conn->WriteInput(L"Foo-bar");
|
||||
conn->WriteInput(L"\x1b]133;C\x7");
|
||||
|
||||
conn->WriteInput(L"\r\n");
|
||||
conn->WriteInput(L"This is some text \r\n");
|
||||
conn->WriteInput(L"with varying amounts \r\n");
|
||||
conn->WriteInput(L"of whitespace \r\n");
|
||||
|
||||
_writePrompt(conn, L"C:\\Windows");
|
||||
|
||||
Log::Comment(L"Check the command context");
|
||||
|
||||
const WEX::TestExecution::DisableVerifyExceptions disableExceptionsScope;
|
||||
{
|
||||
auto historyContext{ core->CommandHistory() };
|
||||
VERIFY_ARE_EQUAL(1u, historyContext.History().Size());
|
||||
VERIFY_ARE_EQUAL(L"", historyContext.CurrentCommandline());
|
||||
}
|
||||
|
||||
Log::Comment(L"Write 'Bar' to the command...");
|
||||
conn->WriteInput(L"Bar");
|
||||
{
|
||||
auto historyContext{ core->CommandHistory() };
|
||||
// Bar shouldn't be in the history, it should be the current command
|
||||
VERIFY_ARE_EQUAL(1u, historyContext.History().Size());
|
||||
VERIFY_ARE_EQUAL(L"Bar", historyContext.CurrentCommandline());
|
||||
}
|
||||
|
||||
Log::Comment(L"then delete it");
|
||||
conn->WriteInput(L"\b \b");
|
||||
conn->WriteInput(L"\b \b");
|
||||
conn->WriteInput(L"\b \b");
|
||||
{
|
||||
auto historyContext{ core->CommandHistory() };
|
||||
VERIFY_ARE_EQUAL(1u, historyContext.History().Size());
|
||||
// The current commandline is now empty
|
||||
VERIFY_ARE_EQUAL(L"", historyContext.CurrentCommandline());
|
||||
}
|
||||
}
|
||||
|
||||
void ControlCoreTests::TestSelectOutputScrolling()
|
||||
{
|
||||
|
||||
@@ -51,6 +51,8 @@ class TerminalCoreUnitTests::TerminalBufferTests final
|
||||
|
||||
TEST_METHOD(TestGetReverseTab);
|
||||
|
||||
TEST_METHOD(TestURLPatternDetection);
|
||||
|
||||
TEST_METHOD_SETUP(MethodSetup)
|
||||
{
|
||||
// STEP 1: Set up the Terminal
|
||||
@@ -594,3 +596,34 @@ void TerminalBufferTests::TestGetReverseTab()
|
||||
L"Cursor adjusted to last item in the sample list from position beyond end.");
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalBufferTests::TestURLPatternDetection()
|
||||
{
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
constexpr auto BeforeStr = L"<Before>"sv;
|
||||
constexpr auto UrlStr = L"https://www.contoso.com"sv;
|
||||
constexpr auto AfterStr = L"<After>"sv;
|
||||
constexpr auto urlStartX = BeforeStr.size();
|
||||
constexpr auto urlEndX = BeforeStr.size() + UrlStr.size() - 1;
|
||||
|
||||
auto& termSm = *term->_stateMachine;
|
||||
termSm.ProcessString(fmt::format(FMT_COMPILE(L"{}{}{}"), BeforeStr, UrlStr, AfterStr));
|
||||
term->UpdatePatternsUnderLock();
|
||||
|
||||
std::wstring result;
|
||||
|
||||
result = term->GetHyperlinkAtBufferPosition(til::point{ urlStartX - 1, 0 });
|
||||
VERIFY_IS_TRUE(result.empty(), L"URL is not detected before the actual URL.");
|
||||
|
||||
result = term->GetHyperlinkAtBufferPosition(til::point{ urlStartX, 0 });
|
||||
VERIFY_IS_TRUE(!result.empty(), L"A URL is detected at the start position.");
|
||||
VERIFY_ARE_EQUAL(result, UrlStr, L"Detected URL matches the given URL.");
|
||||
|
||||
result = term->GetHyperlinkAtBufferPosition(til::point{ urlEndX, 0 });
|
||||
VERIFY_IS_TRUE(!result.empty(), L"A URL is detected at the end position.");
|
||||
VERIFY_ARE_EQUAL(result, UrlStr, L"Detected URL matches the given URL.");
|
||||
|
||||
result = term->GetHyperlinkAtBufferPosition(til::point{ urlEndX + 1, 0 });
|
||||
VERIFY_IS_TRUE(result.empty(), L"URL is not detected after the actual URL.");
|
||||
}
|
||||
|
||||
@@ -1184,13 +1184,16 @@ winrt::fire_and_forget AppHost::_QuitRequested(const winrt::Windows::Foundation:
|
||||
co_await wil::resume_foreground(_windowLogic.GetRoot().Dispatcher());
|
||||
|
||||
const auto strongThis = weakThis.lock();
|
||||
// GH #16235: If we don't have a window logic, we're already refrigerating, and won't have our _window either.
|
||||
if (!strongThis || _windowLogic == nullptr)
|
||||
if (!strongThis)
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
|
||||
_windowLogic.Quit();
|
||||
if (_appLogic && _windowLogic && _appLogic.ShouldUsePersistedLayout())
|
||||
{
|
||||
_windowLogic.PersistState();
|
||||
}
|
||||
|
||||
PostQuitMessage(0);
|
||||
}
|
||||
|
||||
|
||||
@@ -348,6 +348,14 @@ void WindowEmperor::_becomeMonarch()
|
||||
|
||||
_revokers.WindowCreated = _manager.WindowCreated(winrt::auto_revoke, { this, &WindowEmperor::_numberOfWindowsChanged });
|
||||
_revokers.WindowClosed = _manager.WindowClosed(winrt::auto_revoke, { this, &WindowEmperor::_numberOfWindowsChanged });
|
||||
|
||||
// If a previous session of Windows Terminal stored buffer_*.txt files, then we need to clean all those up on exit
|
||||
// that aren't needed anymore, even if the user disabled the ShouldUsePersistedLayout() setting in the meantime.
|
||||
{
|
||||
const auto state = ApplicationState::SharedInstance();
|
||||
const auto layouts = state.PersistedWindowLayouts();
|
||||
_requiresPersistenceCleanupOnExit = layouts && layouts.Size() > 0;
|
||||
}
|
||||
}
|
||||
|
||||
// sender and args are always nullptr
|
||||
@@ -486,7 +494,7 @@ void WindowEmperor::_finalizeSessionPersistence() const
|
||||
// Ensure to write the state.json before we TerminateProcess()
|
||||
state.Flush();
|
||||
|
||||
if (!_app.Logic().ShouldUsePersistedLayout())
|
||||
if (!_requiresPersistenceCleanupOnExit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ private:
|
||||
|
||||
std::unique_ptr<NotificationIcon> _notificationIcon;
|
||||
|
||||
bool _requiresPersistenceCleanupOnExit = false;
|
||||
bool _quitting{ false };
|
||||
|
||||
void _windowStartedHandlerPostXAML(const std::shared_ptr<WindowThread>& sender);
|
||||
|
||||
@@ -636,7 +636,7 @@ void AtlasEngine::_resolveFontMetrics(const FontInfoDesired& fontInfoDesired, Fo
|
||||
const auto& faceName = fontInfoDesired.GetFaceName();
|
||||
const auto requestedFamily = fontInfoDesired.GetFamily();
|
||||
auto requestedWeight = fontInfoDesired.GetWeight();
|
||||
auto fontSize = fontInfoDesired.GetFontSize();
|
||||
auto fontSize = std::clamp(fontInfoDesired.GetFontSize(), 1.0f, 100.0f);
|
||||
auto requestedSize = fontInfoDesired.GetEngineSize();
|
||||
|
||||
if (!requestedSize.height)
|
||||
|
||||
@@ -74,9 +74,8 @@ try
|
||||
_handleSettingsUpdate();
|
||||
}
|
||||
|
||||
if (ATLAS_DEBUG_DISABLE_PARTIAL_INVALIDATION || _hackTriggerRedrawAll)
|
||||
if constexpr (ATLAS_DEBUG_DISABLE_PARTIAL_INVALIDATION)
|
||||
{
|
||||
_hackTriggerRedrawAll = false;
|
||||
_api.invalidatedRows = invalidatedRowsAll;
|
||||
_api.scrollOffset = 0;
|
||||
}
|
||||
@@ -703,8 +702,6 @@ void AtlasEngine::_recreateFontDependentResources()
|
||||
_api.textFormatAxes[i] = { fontAxisValues.data(), fontAxisValues.size() };
|
||||
}
|
||||
}
|
||||
|
||||
_hackWantsBuiltinGlyphs = _p.s->font->builtinGlyphs && !_hackIsBackendD2D;
|
||||
}
|
||||
|
||||
void AtlasEngine::_recreateCellCountDependentResources()
|
||||
@@ -765,18 +762,13 @@ void AtlasEngine::_flushBufferLine()
|
||||
// This would seriously blow us up otherwise.
|
||||
Expects(_api.bufferLineColumn.size() == _api.bufferLine.size() + 1);
|
||||
|
||||
const auto builtinGlyphs = _p.s->font->builtinGlyphs;
|
||||
const auto beg = _api.bufferLine.data();
|
||||
const auto len = _api.bufferLine.size();
|
||||
size_t segmentBeg = 0;
|
||||
size_t segmentEnd = 0;
|
||||
bool custom = false;
|
||||
|
||||
if (!_hackWantsBuiltinGlyphs)
|
||||
{
|
||||
_mapRegularText(0, len);
|
||||
return;
|
||||
}
|
||||
|
||||
while (segmentBeg < len)
|
||||
{
|
||||
segmentEnd = segmentBeg;
|
||||
@@ -789,7 +781,7 @@ void AtlasEngine::_flushBufferLine()
|
||||
codepoint = til::combine_surrogates(codepoint, beg[i++]);
|
||||
}
|
||||
|
||||
const auto c = BuiltinGlyphs::IsBuiltinGlyph(codepoint) || BuiltinGlyphs::IsSoftFontChar(codepoint);
|
||||
const auto c = (builtinGlyphs && BuiltinGlyphs::IsBuiltinGlyph(codepoint)) || BuiltinGlyphs::IsSoftFontChar(codepoint);
|
||||
if (custom != c)
|
||||
{
|
||||
break;
|
||||
|
||||
@@ -127,18 +127,6 @@ namespace Microsoft::Console::Render::Atlas
|
||||
std::unique_ptr<IBackend> _b;
|
||||
RenderingPayload _p;
|
||||
|
||||
// _p.s->font->builtinGlyphs is the setting which decides whether we should map box drawing glyphs to
|
||||
// our own builtin versions. There's just one problem: BackendD2D doesn't have this functionality.
|
||||
// But since AtlasEngine shapes the text before it's handed to the backends, it would need to know
|
||||
// whether BackendD2D is in use, before BackendD2D even exists. These two flags solve the issue
|
||||
// by triggering a complete, immediate redraw whenever the backend type changes.
|
||||
//
|
||||
// The proper solution is to move text shaping into the backends.
|
||||
// Someone just needs to write a generic "TextBuffer to DWRITE_GLYPH_RUN" function.
|
||||
bool _hackIsBackendD2D = false;
|
||||
bool _hackWantsBuiltinGlyphs = true;
|
||||
bool _hackTriggerRedrawAll = false;
|
||||
|
||||
struct ApiState
|
||||
{
|
||||
GenerationalSettings s = DirtyGenerationalSettings();
|
||||
|
||||
@@ -55,7 +55,7 @@ catch (const wil::ResultException& exception)
|
||||
{
|
||||
const auto hr = exception.GetErrorCode();
|
||||
|
||||
if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
|
||||
if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET || hr == D2DERR_RECREATE_TARGET)
|
||||
{
|
||||
_p.dxgi = {};
|
||||
return E_PENDING;
|
||||
@@ -77,7 +77,7 @@ CATCH_RETURN()
|
||||
|
||||
[[nodiscard]] bool AtlasEngine::RequiresContinuousRedraw() noexcept
|
||||
{
|
||||
return ATLAS_DEBUG_CONTINUOUS_REDRAW || (_b && _b->RequiresContinuousRedraw()) || _hackTriggerRedrawAll;
|
||||
return ATLAS_DEBUG_CONTINUOUS_REDRAW || (_b && _b->RequiresContinuousRedraw());
|
||||
}
|
||||
|
||||
void AtlasEngine::WaitUntilCanRender() noexcept
|
||||
@@ -282,21 +282,15 @@ void AtlasEngine::_recreateBackend()
|
||||
{
|
||||
case GraphicsAPI::Direct2D:
|
||||
_b = std::make_unique<BackendD2D>();
|
||||
_hackIsBackendD2D = true;
|
||||
break;
|
||||
default:
|
||||
_b = std::make_unique<BackendD3D>(_p);
|
||||
_hackIsBackendD2D = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// This ensures that the backends redraw their entire viewports whenever a new swap chain is created,
|
||||
// EVEN IF we got called when no actual settings changed (i.e. rendering failure, etc.).
|
||||
_p.MarkAllAsDirty();
|
||||
|
||||
const auto hackWantsBuiltinGlyphs = _p.s->font->builtinGlyphs && !_hackIsBackendD2D;
|
||||
_hackTriggerRedrawAll = _hackWantsBuiltinGlyphs != hackWantsBuiltinGlyphs;
|
||||
_hackWantsBuiltinGlyphs = hackWantsBuiltinGlyphs;
|
||||
}
|
||||
|
||||
void AtlasEngine::_handleSwapChainUpdate()
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include "pch.h"
|
||||
#include "BackendD2D.h"
|
||||
|
||||
#include <til/unicode.h>
|
||||
|
||||
#if ATLAS_DEBUG_SHOW_DIRTY
|
||||
#include "colorbrewer.h"
|
||||
#endif
|
||||
@@ -39,18 +41,28 @@ void BackendD2D::Render(RenderingPayload& p)
|
||||
}
|
||||
|
||||
_renderTarget->BeginDraw();
|
||||
try
|
||||
{
|
||||
#if ATLAS_DEBUG_SHOW_DIRTY || ATLAS_DEBUG_DUMP_RENDER_TARGET
|
||||
// Invalidating the render target helps with spotting Present1() bugs.
|
||||
_renderTarget->Clear();
|
||||
// Invalidating the render target helps with spotting Present1() bugs.
|
||||
_renderTarget->Clear();
|
||||
#endif
|
||||
_drawBackground(p);
|
||||
_drawCursorPart1(p);
|
||||
_drawText(p);
|
||||
_drawCursorPart2(p);
|
||||
_drawSelection(p);
|
||||
_drawBackground(p);
|
||||
_drawCursorPart1(p);
|
||||
_drawText(p);
|
||||
_drawCursorPart2(p);
|
||||
_drawSelection(p);
|
||||
#if ATLAS_DEBUG_SHOW_DIRTY
|
||||
_debugShowDirty(p);
|
||||
_debugShowDirty(p);
|
||||
#endif
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// In case an exception is thrown for some reason between BeginDraw() and EndDraw()
|
||||
// we still technically need to call EndDraw() before releasing any resources.
|
||||
LOG_IF_FAILED(_renderTarget->EndDraw());
|
||||
throw;
|
||||
}
|
||||
THROW_IF_FAILED(_renderTarget->EndDraw());
|
||||
|
||||
#if ATLAS_DEBUG_DUMP_RENDER_TARGET
|
||||
@@ -84,11 +96,15 @@ void BackendD2D::_handleSettingsUpdate(const RenderingPayload& p)
|
||||
.dpiY = static_cast<f32>(p.s->font->dpi),
|
||||
};
|
||||
// ID2D1RenderTarget and ID2D1DeviceContext are the same and I'm tired of pretending they're not.
|
||||
THROW_IF_FAILED(p.d2dFactory->CreateDxgiSurfaceRenderTarget(surface.get(), &props, reinterpret_cast<ID2D1RenderTarget**>(_renderTarget.addressof())));
|
||||
_renderTarget.try_query_to(_renderTarget4.addressof());
|
||||
THROW_IF_FAILED(p.d2dFactory->CreateDxgiSurfaceRenderTarget(surface.get(), &props, reinterpret_cast<ID2D1RenderTarget**>(_renderTarget.put())));
|
||||
|
||||
_renderTarget->SetUnitMode(D2D1_UNIT_MODE_PIXELS);
|
||||
_renderTarget->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
|
||||
|
||||
_renderTarget.try_query_to(_renderTarget4.put());
|
||||
if (_renderTarget4)
|
||||
{
|
||||
THROW_IF_FAILED(_renderTarget4->CreateSpriteBatch(_builtinGlyphBatch.put()));
|
||||
}
|
||||
}
|
||||
{
|
||||
static constexpr D2D1_COLOR_F color{};
|
||||
@@ -98,18 +114,15 @@ void BackendD2D::_handleSettingsUpdate(const RenderingPayload& p)
|
||||
}
|
||||
}
|
||||
|
||||
if (!_dottedStrokeStyle)
|
||||
{
|
||||
static constexpr D2D1_STROKE_STYLE_PROPERTIES props{ .dashStyle = D2D1_DASH_STYLE_CUSTOM };
|
||||
static constexpr FLOAT dashes[2]{ 1, 1 };
|
||||
THROW_IF_FAILED(p.d2dFactory->CreateStrokeStyle(&props, &dashes[0], 2, _dottedStrokeStyle.addressof()));
|
||||
}
|
||||
|
||||
if (renderTargetChanged || fontChanged)
|
||||
{
|
||||
const auto dpi = static_cast<f32>(p.s->font->dpi);
|
||||
_renderTarget->SetDpi(dpi, dpi);
|
||||
_renderTarget->SetTextAntialiasMode(static_cast<D2D1_TEXT_ANTIALIAS_MODE>(p.s->font->antialiasingMode));
|
||||
|
||||
_builtinGlyphsRenderTarget.reset();
|
||||
_builtinGlyphsBitmap.reset();
|
||||
_builtinGlyphsRenderTargetActive = false;
|
||||
}
|
||||
|
||||
if (renderTargetChanged || fontChanged || cellCountChanged)
|
||||
@@ -148,11 +161,11 @@ void BackendD2D::_handleSettingsUpdate(const RenderingPayload& p)
|
||||
_viewportCellCount = p.s->viewportCellCount;
|
||||
}
|
||||
|
||||
void BackendD2D::_drawBackground(const RenderingPayload& p) noexcept
|
||||
void BackendD2D::_drawBackground(const RenderingPayload& p)
|
||||
{
|
||||
if (_backgroundBitmapGeneration != p.colorBitmapGenerations[0])
|
||||
{
|
||||
_backgroundBitmap->CopyFromMemory(nullptr, p.backgroundBitmap.data(), gsl::narrow_cast<UINT32>(p.colorBitmapRowStride * sizeof(u32)));
|
||||
THROW_IF_FAILED(_backgroundBitmap->CopyFromMemory(nullptr, p.backgroundBitmap.data(), gsl::narrow_cast<UINT32>(p.colorBitmapRowStride * sizeof(u32))));
|
||||
_backgroundBitmapGeneration = p.colorBitmapGenerations[0];
|
||||
}
|
||||
|
||||
@@ -189,6 +202,12 @@ void BackendD2D::_drawText(RenderingPayload& p)
|
||||
|
||||
for (const auto& m : row->mappings)
|
||||
{
|
||||
if (!m.fontFace)
|
||||
{
|
||||
baselineX = _drawBuiltinGlyphs(p, row, m, baselineY, baselineX);
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto colorsBegin = row->colors.begin();
|
||||
auto it = colorsBegin + m.glyphsFrom;
|
||||
const auto end = colorsBegin + m.glyphsTo;
|
||||
@@ -218,42 +237,39 @@ void BackendD2D::_drawText(RenderingPayload& p)
|
||||
baselineY,
|
||||
};
|
||||
|
||||
if (glyphRun.fontFace)
|
||||
D2D1_RECT_F bounds = GlyphRunEmptyBounds;
|
||||
wil::com_ptr<IDWriteColorGlyphRunEnumerator1> enumerator;
|
||||
|
||||
if (p.s->font->colorGlyphs)
|
||||
{
|
||||
D2D1_RECT_F bounds = GlyphRunEmptyBounds;
|
||||
wil::com_ptr<IDWriteColorGlyphRunEnumerator1> enumerator;
|
||||
enumerator = TranslateColorGlyphRun(p.dwriteFactory4.get(), baselineOrigin, &glyphRun);
|
||||
}
|
||||
|
||||
if (p.s->font->colorGlyphs)
|
||||
if (enumerator)
|
||||
{
|
||||
while (ColorGlyphRunMoveNext(enumerator.get()))
|
||||
{
|
||||
enumerator = TranslateColorGlyphRun(p.dwriteFactory4.get(), baselineOrigin, &glyphRun);
|
||||
const auto colorGlyphRun = ColorGlyphRunGetCurrentRun(enumerator.get());
|
||||
ColorGlyphRunDraw(_renderTarget4.get(), _emojiBrush.get(), brush, colorGlyphRun);
|
||||
ColorGlyphRunAccumulateBounds(_renderTarget.get(), colorGlyphRun, bounds);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_renderTarget->DrawGlyphRun(baselineOrigin, &glyphRun, brush, DWRITE_MEASURING_MODE_NATURAL);
|
||||
GlyphRunAccumulateBounds(_renderTarget.get(), baselineOrigin, &glyphRun, bounds);
|
||||
}
|
||||
|
||||
if (enumerator)
|
||||
if (bounds.top < bounds.bottom)
|
||||
{
|
||||
// Since we used SetUnitMode(D2D1_UNIT_MODE_PIXELS), bounds.top/bottom is in pixels already and requires no conversion/rounding.
|
||||
if (row->lineRendition != LineRendition::DoubleHeightTop)
|
||||
{
|
||||
while (ColorGlyphRunMoveNext(enumerator.get()))
|
||||
{
|
||||
const auto colorGlyphRun = ColorGlyphRunGetCurrentRun(enumerator.get());
|
||||
ColorGlyphRunDraw(_renderTarget4.get(), _emojiBrush.get(), brush, colorGlyphRun);
|
||||
ColorGlyphRunAccumulateBounds(_renderTarget.get(), colorGlyphRun, bounds);
|
||||
}
|
||||
row->dirtyBottom = std::max(row->dirtyBottom, static_cast<i32>(lrintf(bounds.bottom)));
|
||||
}
|
||||
else
|
||||
if (row->lineRendition != LineRendition::DoubleHeightBottom)
|
||||
{
|
||||
_renderTarget->DrawGlyphRun(baselineOrigin, &glyphRun, brush, DWRITE_MEASURING_MODE_NATURAL);
|
||||
GlyphRunAccumulateBounds(_renderTarget.get(), baselineOrigin, &glyphRun, bounds);
|
||||
}
|
||||
|
||||
if (bounds.top < bounds.bottom)
|
||||
{
|
||||
// Since we used SetUnitMode(D2D1_UNIT_MODE_PIXELS), bounds.top/bottom is in pixels already and requires no conversion/rounding.
|
||||
if (row->lineRendition != LineRendition::DoubleHeightTop)
|
||||
{
|
||||
row->dirtyBottom = std::max(row->dirtyBottom, static_cast<i32>(lrintf(bounds.bottom)));
|
||||
}
|
||||
if (row->lineRendition != LineRendition::DoubleHeightBottom)
|
||||
{
|
||||
row->dirtyTop = std::min(row->dirtyTop, static_cast<i32>(lrintf(bounds.top)));
|
||||
}
|
||||
row->dirtyTop = std::min(row->dirtyTop, static_cast<i32>(lrintf(bounds.top)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,6 +280,8 @@ void BackendD2D::_drawText(RenderingPayload& p)
|
||||
}
|
||||
}
|
||||
|
||||
_flushBuiltinGlyphs();
|
||||
|
||||
if (!row->gridLineRanges.empty())
|
||||
{
|
||||
_drawGridlineRow(p, row, y);
|
||||
@@ -290,6 +308,166 @@ void BackendD2D::_drawText(RenderingPayload& p)
|
||||
}
|
||||
}
|
||||
|
||||
f32 BackendD2D::_drawBuiltinGlyphs(const RenderingPayload& p, const ShapedRow* row, const FontMapping& m, f32 baselineY, f32 baselineX)
|
||||
{
|
||||
const f32 cellTop = baselineY - p.s->font->baseline;
|
||||
const f32 cellBottom = cellTop + p.s->font->cellSize.y;
|
||||
const f32 cellWidth = p.s->font->cellSize.x;
|
||||
|
||||
_prepareBuiltinGlyphRenderTarget(p);
|
||||
|
||||
for (size_t i = m.glyphsFrom; i < m.glyphsTo; ++i)
|
||||
{
|
||||
// This code runs when fontFace == nullptr. This is only the case for builtin glyphs which then use the glyphIndices
|
||||
// to store UTF16 code points. In other words, this doesn't accidentally corrupt any actual glyph indices.
|
||||
u32 ch = row->glyphIndices[i];
|
||||
if (til::is_leading_surrogate(ch))
|
||||
{
|
||||
i += 1;
|
||||
ch = til::combine_surrogates(ch, row->glyphIndices[i]);
|
||||
}
|
||||
|
||||
// If we don't have support for ID2D1SpriteBatch we don't support builtin glyphs.
|
||||
// But we do still need to account for the glyphAdvances, which is why we can't just skip everything.
|
||||
// It's very unlikely for a target device to not support ID2D1SpriteBatch as it's very old at this point.
|
||||
if (_builtinGlyphBatch)
|
||||
{
|
||||
if (const auto off = BuiltinGlyphs::GetBitmapCellIndex(ch); off >= 0)
|
||||
{
|
||||
const D2D1_RECT_F dst{ baselineX, cellTop, baselineX + cellWidth, cellBottom };
|
||||
const auto src = _prepareBuiltinGlyph(p, ch, off);
|
||||
const auto color = colorFromU32(row->colors[i]);
|
||||
THROW_IF_FAILED(_builtinGlyphBatch->AddSprites(1, &dst, &src, &color, nullptr, sizeof(D2D1_RECT_F), sizeof(D2D1_RECT_U), sizeof(D2D1_COLOR_F), sizeof(D2D1_MATRIX_3X2_F)));
|
||||
}
|
||||
}
|
||||
|
||||
baselineX += row->glyphAdvances[i];
|
||||
}
|
||||
|
||||
return baselineX;
|
||||
}
|
||||
|
||||
void BackendD2D::_prepareBuiltinGlyphRenderTarget(const RenderingPayload& p)
|
||||
{
|
||||
// If we don't have support for ID2D1SpriteBatch none of the related members will be initialized or used.
|
||||
// We can just early-return in that case.
|
||||
if (!_builtinGlyphBatch)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// If the render target is already created, all of the below has already been done in a previous frame.
|
||||
// Once the relevant settings change for some reason (primarily the font->cellSize), then _handleSettingsUpdate()
|
||||
// will reset the render target which will cause us to skip this condition and re-initialize it below.
|
||||
if (_builtinGlyphsRenderTarget)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto cellWidth = static_cast<u32>(p.s->font->cellSize.x);
|
||||
const auto cellHeight = static_cast<u32>(p.s->font->cellSize.y);
|
||||
const auto cellArea = cellWidth * cellHeight;
|
||||
const auto area = cellArea * BuiltinGlyphs::TotalCharCount;
|
||||
|
||||
// This block of code calculates the size of a power-of-2 texture that has an area larger than the given `area`.
|
||||
// For instance, for an area of 985x1946 = 1916810 it would result in a u/v of 2048x1024 (area = 2097152).
|
||||
// We throw the "v" in this case away, because we don't really need power-of-2 textures here,
|
||||
// but you can find the complete code over in BackendD3D. If someone deleted it in the meantime:
|
||||
// const auto index = bitness_of_area_minus_1 - std::countl_zero(area - 1); // aka: _BitScanReverse
|
||||
// const auto u = 1u << ((index + 2) / 2);
|
||||
// const auto v = 1u << ((index + 1) / 2);
|
||||
unsigned long index;
|
||||
_BitScanReverse(&index, area - 1);
|
||||
const auto potWidth = 1u << ((index + 2) / 2);
|
||||
|
||||
const auto cellCountU = potWidth / cellWidth;
|
||||
const auto cellCountV = (BuiltinGlyphs::TotalCharCount + cellCountU - 1) / cellCountU;
|
||||
const auto u = cellCountU * cellWidth;
|
||||
const auto v = cellCountV * cellHeight;
|
||||
|
||||
const D2D1_SIZE_F sizeF{ static_cast<f32>(u), static_cast<f32>(v) };
|
||||
const D2D1_SIZE_U sizeU{ gsl::narrow_cast<UINT32>(u), gsl::narrow_cast<UINT32>(v) };
|
||||
static constexpr D2D1_PIXEL_FORMAT format{ DXGI_FORMAT_A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED };
|
||||
wil::com_ptr<ID2D1BitmapRenderTarget> target;
|
||||
THROW_IF_FAILED(_renderTarget->CreateCompatibleRenderTarget(&sizeF, &sizeU, &format, D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE, target.addressof()));
|
||||
|
||||
THROW_IF_FAILED(target->GetBitmap(_builtinGlyphsBitmap.put()));
|
||||
_builtinGlyphsRenderTarget = target.query<ID2D1DeviceContext>();
|
||||
_builtinGlyphsBitmapCellCountU = cellCountU;
|
||||
memset(&_builtinGlyphsReady[0], 0, sizeof(_builtinGlyphsReady));
|
||||
|
||||
_builtinGlyphsRenderTarget->BeginDraw();
|
||||
_builtinGlyphsRenderTargetActive = true;
|
||||
|
||||
// The initial contents of the bitmap are undefined.
|
||||
// -> We need to define them. :)
|
||||
_builtinGlyphsRenderTarget->Clear();
|
||||
}
|
||||
|
||||
D2D1_RECT_U BackendD2D::_prepareBuiltinGlyph(const RenderingPayload& p, char32_t ch, u32 off)
|
||||
{
|
||||
const u32 w = p.s->font->cellSize.x;
|
||||
const u32 h = p.s->font->cellSize.y;
|
||||
const u32 l = (off % _builtinGlyphsBitmapCellCountU) * w;
|
||||
const u32 t = (off / _builtinGlyphsBitmapCellCountU) * h;
|
||||
D2D1_RECT_U rectU{ l, t, l + w, t + h };
|
||||
|
||||
// Check if we previously cached this glyph already.
|
||||
if (_builtinGlyphsReady[off])
|
||||
{
|
||||
return rectU;
|
||||
}
|
||||
|
||||
static constexpr D2D1_COLOR_F shadeColorMap[] = {
|
||||
{ 1, 1, 1, 0.25f }, // Shape_Filled025
|
||||
{ 1, 1, 1, 0.50f }, // Shape_Filled050
|
||||
{ 1, 1, 1, 0.75f }, // Shape_Filled075
|
||||
{ 1, 1, 1, 1.00f }, // Shape_Filled100
|
||||
};
|
||||
|
||||
if (!_builtinGlyphsRenderTargetActive)
|
||||
{
|
||||
_builtinGlyphsRenderTarget->BeginDraw();
|
||||
_builtinGlyphsRenderTargetActive = true;
|
||||
}
|
||||
|
||||
const auto brush = _brushWithColor(0xffffffff);
|
||||
const D2D1_RECT_F rectF{
|
||||
static_cast<f32>(rectU.left),
|
||||
static_cast<f32>(rectU.top),
|
||||
static_cast<f32>(rectU.right),
|
||||
static_cast<f32>(rectU.bottom),
|
||||
};
|
||||
BuiltinGlyphs::DrawBuiltinGlyph(p.d2dFactory.get(), _builtinGlyphsRenderTarget.get(), brush, shadeColorMap, rectF, ch);
|
||||
|
||||
_builtinGlyphsReady[off] = true;
|
||||
return rectU;
|
||||
}
|
||||
|
||||
void BackendD2D::_flushBuiltinGlyphs()
|
||||
{
|
||||
// If we don't have support for ID2D1SpriteBatch none of the related members will be initialized or used.
|
||||
// We can just early-return in that case.
|
||||
if (!_builtinGlyphBatch)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_builtinGlyphsRenderTargetActive)
|
||||
{
|
||||
THROW_IF_FAILED(_builtinGlyphsRenderTarget->EndDraw());
|
||||
_builtinGlyphsRenderTargetActive = false;
|
||||
}
|
||||
|
||||
if (const auto count = _builtinGlyphBatch->GetSpriteCount(); count > 0)
|
||||
{
|
||||
_renderTarget4->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
|
||||
_renderTarget4->DrawSpriteBatch(_builtinGlyphBatch.get(), 0, count, _builtinGlyphsBitmap.get(), D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR, D2D1_SPRITE_OPTIONS_NONE);
|
||||
_renderTarget4->SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE);
|
||||
_builtinGlyphBatch->Clear();
|
||||
}
|
||||
}
|
||||
|
||||
f32 BackendD2D::_drawTextPrepareLineRendition(const RenderingPayload& p, const ShapedRow* row, f32 baselineY) const noexcept
|
||||
{
|
||||
const auto lineRendition = row->lineRendition;
|
||||
@@ -356,7 +534,7 @@ f32r BackendD2D::_getGlyphRunDesignBounds(const DWRITE_GLYPH_RUN& glyphRun, f32
|
||||
_glyphMetrics = Buffer<DWRITE_GLYPH_METRICS>{ size };
|
||||
}
|
||||
|
||||
glyphRun.fontFace->GetDesignGlyphMetrics(glyphRun.glyphIndices, glyphRun.glyphCount, _glyphMetrics.data(), false);
|
||||
THROW_IF_FAILED(glyphRun.fontFace->GetDesignGlyphMetrics(glyphRun.glyphIndices, glyphRun.glyphCount, _glyphMetrics.data(), false));
|
||||
|
||||
const f32 fontScale = glyphRun.fontEmSize / fontMetrics.designUnitsPerEm;
|
||||
f32r accumulatedBounds{
|
||||
@@ -400,44 +578,118 @@ f32r BackendD2D::_getGlyphRunDesignBounds(const DWRITE_GLYPH_RUN& glyphRun, f32
|
||||
|
||||
void BackendD2D::_drawGridlineRow(const RenderingPayload& p, const ShapedRow* row, u16 y)
|
||||
{
|
||||
const auto widthShift = gsl::narrow_cast<u8>(row->lineRendition != LineRendition::SingleWidth);
|
||||
const auto cellSize = p.s->font->cellSize;
|
||||
const auto rowTop = gsl::narrow_cast<i16>(cellSize.y * y);
|
||||
const auto rowBottom = gsl::narrow_cast<i16>(rowTop + cellSize.y);
|
||||
const auto textCellCenter = row->lineRendition == LineRendition::DoubleHeightTop ? rowBottom : rowTop;
|
||||
const auto cellWidth = static_cast<f32>(p.s->font->cellSize.x);
|
||||
const auto cellHeight = static_cast<f32>(p.s->font->cellSize.y);
|
||||
const auto rowTop = cellHeight * y;
|
||||
const auto rowBottom = rowTop + cellHeight;
|
||||
const auto cellCenter = row->lineRendition == LineRendition::DoubleHeightTop ? rowBottom : rowTop;
|
||||
const auto scaleHorizontal = row->lineRendition != LineRendition::SingleWidth ? 0.5f : 1.0f;
|
||||
const auto scaledCellWidth = cellWidth * scaleHorizontal;
|
||||
|
||||
const auto appendVerticalLines = [&](const GridLineRange& r, FontDecorationPosition pos) {
|
||||
const auto from = r.from >> widthShift;
|
||||
const auto to = r.to >> widthShift;
|
||||
const auto from = r.from * scaledCellWidth;
|
||||
const auto to = r.to * scaledCellWidth;
|
||||
auto x = from + pos.position;
|
||||
|
||||
auto posX = from * cellSize.x + pos.position;
|
||||
const auto end = to * cellSize.x;
|
||||
|
||||
D2D1_POINT_2F point0{ 0, static_cast<f32>(textCellCenter) };
|
||||
D2D1_POINT_2F point1{ 0, static_cast<f32>(textCellCenter + cellSize.y) };
|
||||
D2D1_POINT_2F point0{ 0, cellCenter };
|
||||
D2D1_POINT_2F point1{ 0, cellCenter + cellHeight };
|
||||
const auto brush = _brushWithColor(r.gridlineColor);
|
||||
const f32 w = pos.height;
|
||||
const f32 hw = w * 0.5f;
|
||||
|
||||
for (; posX < end; posX += cellSize.x)
|
||||
for (; x < to; x += cellWidth)
|
||||
{
|
||||
const auto centerX = posX + hw;
|
||||
const auto centerX = x + hw;
|
||||
point0.x = centerX;
|
||||
point1.x = centerX;
|
||||
_renderTarget->DrawLine(point0, point1, brush, w, nullptr);
|
||||
}
|
||||
};
|
||||
const auto appendHorizontalLine = [&](const GridLineRange& r, FontDecorationPosition pos, ID2D1StrokeStyle* strokeStyle, const u32 color) {
|
||||
const auto from = r.from >> widthShift;
|
||||
const auto to = r.to >> widthShift;
|
||||
const auto from = r.from * scaledCellWidth;
|
||||
const auto to = r.to * scaledCellWidth;
|
||||
|
||||
const auto brush = _brushWithColor(color);
|
||||
const f32 w = pos.height;
|
||||
const f32 centerY = textCellCenter + pos.position + w * 0.5f;
|
||||
const D2D1_POINT_2F point0{ static_cast<f32>(from * cellSize.x), centerY };
|
||||
const D2D1_POINT_2F point1{ static_cast<f32>(to * cellSize.x), centerY };
|
||||
const f32 centerY = cellCenter + pos.position + w * 0.5f;
|
||||
const D2D1_POINT_2F point0{ from, centerY };
|
||||
const D2D1_POINT_2F point1{ to, centerY };
|
||||
_renderTarget->DrawLine(point0, point1, brush, w, strokeStyle);
|
||||
};
|
||||
const auto appendCurlyLine = [&](const GridLineRange& r) {
|
||||
const auto& font = *p.s->font;
|
||||
|
||||
const auto duTop = static_cast<f32>(font.doubleUnderline[0].position);
|
||||
const auto duBottom = static_cast<f32>(font.doubleUnderline[1].position);
|
||||
// The double-underline height is also our target line width.
|
||||
const auto duHeight = static_cast<f32>(font.doubleUnderline[0].height);
|
||||
|
||||
// This gives it the same position and height as our double-underline. There's no particular reason for that, apart from
|
||||
// it being simple to implement and robust against more peculiar fonts with unusually large/small descenders, etc.
|
||||
// We still need to ensure though that it doesn't clip out of the cellHeight at the bottom, which is why `position` has a min().
|
||||
const auto height = std::max(3.0f, duBottom + duHeight - duTop);
|
||||
const auto position = std::min(duTop, cellHeight - height - duHeight);
|
||||
|
||||
// The amplitude of the wave needs to account for the stroke width, so that the final height including
|
||||
// antialiasing isn't larger than our target `height`. That's why we calculate `(height - duHeight)`.
|
||||
//
|
||||
// In other words, Direct2D draws strokes centered on the path. This also means that (for instance)
|
||||
// for a line width of 1px, we need to ensure that the amplitude passes through the center of a pixel.
|
||||
// Because once the path gets stroked, it'll occupy half a pixel on either side of the path.
|
||||
// This results in a "crisp" look. That's why we do `round(amp + half) - half`.
|
||||
const auto halfLineWidth = 0.5f * duHeight;
|
||||
const auto amplitude = roundf((height - duHeight) * 0.5f + halfLineWidth) - halfLineWidth;
|
||||
// While the amplitude needs to account for the stroke width, the vertical center of the wave needs
|
||||
// to be at an integer pixel position of course. Otherwise, the wave won't be vertically symmetric.
|
||||
const auto center = cellCenter + position + amplitude + halfLineWidth;
|
||||
|
||||
const auto top = center - 2.0f * amplitude;
|
||||
const auto bottom = center + 2.0f * amplitude;
|
||||
const auto step = 0.5f * height;
|
||||
const auto period = 4.0f * step;
|
||||
|
||||
const auto from = r.from * scaledCellWidth;
|
||||
const auto to = r.to * scaledCellWidth;
|
||||
// Align the start of the wave to the nearest preceding period boundary.
|
||||
// This ensures that the wave is continuous across color and cell changes.
|
||||
auto x = floorf(from / period) * period;
|
||||
|
||||
wil::com_ptr<ID2D1PathGeometry> geometry;
|
||||
THROW_IF_FAILED(p.d2dFactory->CreatePathGeometry(geometry.addressof()));
|
||||
|
||||
wil::com_ptr<ID2D1GeometrySink> sink;
|
||||
THROW_IF_FAILED(geometry->Open(sink.addressof()));
|
||||
|
||||
// This adds complete periods of the wave until we reach the end of the range.
|
||||
sink->BeginFigure({ x, center }, D2D1_FIGURE_BEGIN_HOLLOW);
|
||||
for (D2D1_QUADRATIC_BEZIER_SEGMENT segment; x < to;)
|
||||
{
|
||||
x += step;
|
||||
segment.point1.x = x;
|
||||
segment.point1.y = top;
|
||||
x += step;
|
||||
segment.point2.x = x;
|
||||
segment.point2.y = center;
|
||||
sink->AddQuadraticBezier(&segment);
|
||||
|
||||
x += step;
|
||||
segment.point1.x = x;
|
||||
segment.point1.y = bottom;
|
||||
x += step;
|
||||
segment.point2.x = x;
|
||||
segment.point2.y = center;
|
||||
sink->AddQuadraticBezier(&segment);
|
||||
}
|
||||
sink->EndFigure(D2D1_FIGURE_END_OPEN);
|
||||
|
||||
THROW_IF_FAILED(sink->Close());
|
||||
|
||||
const auto brush = _brushWithColor(r.underlineColor);
|
||||
const D2D1_RECT_F clipRect{ from, rowTop, to, rowBottom };
|
||||
_renderTarget->PushAxisAlignedClip(&clipRect, D2D1_ANTIALIAS_MODE_ALIASED);
|
||||
_renderTarget->DrawGeometry(geometry.get(), brush, duHeight, nullptr);
|
||||
_renderTarget->PopAxisAlignedClip();
|
||||
};
|
||||
|
||||
for (const auto& r : row->gridLineRanges)
|
||||
{
|
||||
@@ -471,8 +723,28 @@ void BackendD2D::_drawGridlineRow(const RenderingPayload& p, const ShapedRow* ro
|
||||
}
|
||||
else if (r.lines.any(GridLines::DottedUnderline, GridLines::HyperlinkUnderline))
|
||||
{
|
||||
if (!_dottedStrokeStyle)
|
||||
{
|
||||
static constexpr D2D1_STROKE_STYLE_PROPERTIES props{ .dashStyle = D2D1_DASH_STYLE_CUSTOM };
|
||||
static constexpr FLOAT dashes[2]{ 1, 1 };
|
||||
THROW_IF_FAILED(p.d2dFactory->CreateStrokeStyle(&props, &dashes[0], 2, _dottedStrokeStyle.addressof()));
|
||||
}
|
||||
appendHorizontalLine(r, p.s->font->underline, _dottedStrokeStyle.get(), r.underlineColor);
|
||||
}
|
||||
else if (r.lines.test(GridLines::DashedUnderline))
|
||||
{
|
||||
if (!_dashedStrokeStyle)
|
||||
{
|
||||
static constexpr D2D1_STROKE_STYLE_PROPERTIES props{ .dashStyle = D2D1_DASH_STYLE_CUSTOM };
|
||||
static constexpr FLOAT dashes[2]{ 2, 2 };
|
||||
THROW_IF_FAILED(p.d2dFactory->CreateStrokeStyle(&props, &dashes[0], 2, _dashedStrokeStyle.addressof()));
|
||||
}
|
||||
appendHorizontalLine(r, p.s->font->underline, _dashedStrokeStyle.get(), r.underlineColor);
|
||||
}
|
||||
else if (r.lines.test(GridLines::CurlyUnderline))
|
||||
{
|
||||
appendCurlyLine(r);
|
||||
}
|
||||
else if (r.lines.test(GridLines::DoubleUnderline))
|
||||
{
|
||||
for (const auto pos : p.s->font->doubleUnderline)
|
||||
@@ -541,7 +813,8 @@ void BackendD2D::_resizeCursorBitmap(const RenderingPayload& p, const til::size
|
||||
const D2D1_SIZE_F sizeF{ static_cast<f32>(newSizeInPx.width), static_cast<f32>(newSizeInPx.height) };
|
||||
const D2D1_SIZE_U sizeU{ gsl::narrow_cast<UINT32>(newSizeInPx.width), gsl::narrow_cast<UINT32>(newSizeInPx.height) };
|
||||
wil::com_ptr<ID2D1BitmapRenderTarget> cursorRenderTarget;
|
||||
_renderTarget->CreateCompatibleRenderTarget(&sizeF, &sizeU, nullptr, D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE, cursorRenderTarget.addressof());
|
||||
THROW_IF_FAILED(_renderTarget->CreateCompatibleRenderTarget(&sizeF, &sizeU, nullptr, D2D1_COMPATIBLE_RENDER_TARGET_OPTIONS_NONE, cursorRenderTarget.addressof()));
|
||||
|
||||
cursorRenderTarget->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED);
|
||||
|
||||
cursorRenderTarget->BeginDraw();
|
||||
@@ -553,7 +826,7 @@ void BackendD2D::_resizeCursorBitmap(const RenderingPayload& p, const til::size
|
||||
}
|
||||
THROW_IF_FAILED(cursorRenderTarget->EndDraw());
|
||||
|
||||
cursorRenderTarget->GetBitmap(_cursorBitmap.put());
|
||||
THROW_IF_FAILED(cursorRenderTarget->GetBitmap(_cursorBitmap.put()));
|
||||
_cursorBitmapSize = newSize;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <til/flat_set.h>
|
||||
|
||||
#include "Backend.h"
|
||||
#include "BuiltinGlyphs.h"
|
||||
|
||||
namespace Microsoft::Console::Render::Atlas
|
||||
{
|
||||
@@ -17,8 +16,12 @@ namespace Microsoft::Console::Render::Atlas
|
||||
|
||||
private:
|
||||
ATLAS_ATTR_COLD void _handleSettingsUpdate(const RenderingPayload& p);
|
||||
void _drawBackground(const RenderingPayload& p) noexcept;
|
||||
void _drawBackground(const RenderingPayload& p);
|
||||
void _drawText(RenderingPayload& p);
|
||||
ATLAS_ATTR_COLD f32 _drawBuiltinGlyphs(const RenderingPayload& p, const ShapedRow* row, const FontMapping& m, f32 baselineY, f32 baselineX);
|
||||
void _prepareBuiltinGlyphRenderTarget(const RenderingPayload& p);
|
||||
D2D1_RECT_U _prepareBuiltinGlyph(const RenderingPayload& p, char32_t ch, u32 off);
|
||||
void _flushBuiltinGlyphs();
|
||||
ATLAS_ATTR_COLD f32 _drawTextPrepareLineRendition(const RenderingPayload& p, const ShapedRow* row, f32 baselineY) const noexcept;
|
||||
ATLAS_ATTR_COLD void _drawTextResetLineRendition(const ShapedRow* row) const noexcept;
|
||||
ATLAS_ATTR_COLD f32r _getGlyphRunDesignBounds(const DWRITE_GLYPH_RUN& glyphRun, f32 baselineX, f32 baselineY);
|
||||
@@ -37,10 +40,18 @@ namespace Microsoft::Console::Render::Atlas
|
||||
wil::com_ptr<ID2D1DeviceContext> _renderTarget;
|
||||
wil::com_ptr<ID2D1DeviceContext4> _renderTarget4; // Optional. Supported since Windows 10 14393.
|
||||
wil::com_ptr<ID2D1StrokeStyle> _dottedStrokeStyle;
|
||||
wil::com_ptr<ID2D1StrokeStyle> _dashedStrokeStyle;
|
||||
wil::com_ptr<ID2D1Bitmap> _backgroundBitmap;
|
||||
wil::com_ptr<ID2D1BitmapBrush> _backgroundBrush;
|
||||
til::generation_t _backgroundBitmapGeneration;
|
||||
|
||||
wil::com_ptr<ID2D1DeviceContext> _builtinGlyphsRenderTarget;
|
||||
wil::com_ptr<ID2D1Bitmap> _builtinGlyphsBitmap;
|
||||
wil::com_ptr<ID2D1SpriteBatch> _builtinGlyphBatch;
|
||||
u32 _builtinGlyphsBitmapCellCountU = 0;
|
||||
bool _builtinGlyphsRenderTargetActive = false;
|
||||
bool _builtinGlyphsReady[BuiltinGlyphs::TotalCharCount]{};
|
||||
|
||||
wil::com_ptr<ID2D1Bitmap> _cursorBitmap;
|
||||
til::size _cursorBitmapSize; // in columns/rows
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ TIL_FAST_MATH_BEGIN
|
||||
#pragma warning(disable : 26459) // You called an STL function '...' with a raw pointer parameter at position '...' that may be unsafe [...].
|
||||
#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).
|
||||
#pragma warning(disable : 26490) // Don't use reinterpret_cast (type.1).
|
||||
|
||||
// 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)")
|
||||
@@ -184,6 +185,18 @@ BackendD3D::BackendD3D(const RenderingPayload& p)
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma warning(suppress : 26432) // If you define or delete any default operation in the type '...', define or delete them all (c.21).
|
||||
BackendD3D::~BackendD3D()
|
||||
{
|
||||
// In case an exception is thrown for some reason between BeginDraw() and EndDraw()
|
||||
// we still technically need to call EndDraw() before releasing any resources.
|
||||
if (_d2dBeganDrawing)
|
||||
{
|
||||
#pragma warning(suppress : 26447) // The function is declared 'noexcept' but calls function '...' which may throw exceptions (f.6).
|
||||
LOG_IF_FAILED(_d2dRenderTarget->EndDraw());
|
||||
}
|
||||
}
|
||||
|
||||
void BackendD3D::ReleaseResources() noexcept
|
||||
{
|
||||
_renderTargetView.reset();
|
||||
@@ -283,20 +296,20 @@ void BackendD3D::_updateFontDependents(const RenderingPayload& p)
|
||||
// baseline of curlyline is at the middle of singly underline. When there's
|
||||
// limited space to draw a curlyline, we apply a limit on the peak height.
|
||||
{
|
||||
const auto cellHeight = static_cast<f32>(font.cellSize.y);
|
||||
const auto duTop = static_cast<f32>(font.doubleUnderline[0].position);
|
||||
const auto duBottom = static_cast<f32>(font.doubleUnderline[1].position);
|
||||
const auto duHeight = static_cast<f32>(font.doubleUnderline[0].height);
|
||||
const int cellHeight = font.cellSize.y;
|
||||
const int duTop = font.doubleUnderline[0].position;
|
||||
const int duBottom = font.doubleUnderline[1].position;
|
||||
const int duHeight = font.doubleUnderline[0].height;
|
||||
|
||||
// This gives it the same position and height as our double-underline. There's no particular reason for that, apart from
|
||||
// it being simple to implement and robust against more peculiar fonts with unusually large/small descenders, etc.
|
||||
// We still need to ensure though that it doesn't clip out of the cellHeight at the bottom.
|
||||
const auto height = std::max(3.0f, duBottom + duHeight - duTop);
|
||||
const auto top = std::min(duTop, floorf(cellHeight - height - duHeight));
|
||||
// We still need to ensure though that it doesn't clip out of the cellHeight at the bottom, which is why `position` has a min().
|
||||
const auto height = std::max(3, duBottom + duHeight - duTop);
|
||||
const auto position = std::min(duTop, cellHeight - height - duHeight);
|
||||
|
||||
_curlyLineHalfHeight = height * 0.5f;
|
||||
_curlyUnderline.position = gsl::narrow_cast<u16>(lrintf(top));
|
||||
_curlyUnderline.height = gsl::narrow_cast<u16>(lrintf(height));
|
||||
_curlyUnderline.position = gsl::narrow_cast<u16>(position);
|
||||
_curlyUnderline.height = gsl::narrow_cast<u16>(height);
|
||||
}
|
||||
|
||||
DWrite_GetRenderParams(p.dwriteFactory.get(), &_gamma, &_cleartypeEnhancedContrast, &_grayscaleEnhancedContrast, _textRenderingParams.put());
|
||||
@@ -785,6 +798,13 @@ void BackendD3D::_resetGlyphAtlas(const RenderingPayload& p)
|
||||
|
||||
void BackendD3D::_resizeGlyphAtlas(const RenderingPayload& p, const u16 u, const u16 v)
|
||||
{
|
||||
#if defined(_M_X64) || defined(_M_IX86)
|
||||
static const auto faultyMacTypeVersion = _checkMacTypeVersion(p);
|
||||
#else
|
||||
// The affected versions of MacType are unavailable on ARM.
|
||||
static constexpr auto faultyMacTypeVersion = false;
|
||||
#endif
|
||||
|
||||
_d2dRenderTarget.reset();
|
||||
_d2dRenderTarget4.reset();
|
||||
_glyphAtlas.reset();
|
||||
@@ -830,9 +850,13 @@ void BackendD3D::_resizeGlyphAtlas(const RenderingPayload& p, const u16 u, const
|
||||
_d2dRenderTarget4->GetDevice(device.addressof());
|
||||
|
||||
device->SetMaximumTextureMemory(0);
|
||||
if (const auto device4 = device.try_query<ID2D1Device4>())
|
||||
|
||||
if (!faultyMacTypeVersion)
|
||||
{
|
||||
device4->SetMaximumColorGlyphCacheMemory(0);
|
||||
if (const auto device4 = device.try_query<ID2D1Device4>())
|
||||
{
|
||||
device4->SetMaximumColorGlyphCacheMemory(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -847,6 +871,62 @@ void BackendD3D::_resizeGlyphAtlas(const RenderingPayload& p, const u16 u, const
|
||||
_rectPackerData = Buffer<stbrp_node>{ u };
|
||||
}
|
||||
|
||||
// MacType is a popular 3rd party system to give the font rendering on Windows a softer look.
|
||||
// It's particularly popular in China. Unfortunately, it hooks ID2D1Device4 incorrectly:
|
||||
// https://github.com/snowie2000/mactype/pull/938
|
||||
// This results in crashes. Not a lot of them, but enough to constantly show up.
|
||||
// The issue was fixed in the MacType v1.2023.5.31 release, the only one in 2023.
|
||||
//
|
||||
// Please feel free to remove this check in a few years.
|
||||
bool BackendD3D::_checkMacTypeVersion(const RenderingPayload& p)
|
||||
{
|
||||
#ifdef _WIN64
|
||||
static constexpr auto name = L"MacType64.Core.dll";
|
||||
#else
|
||||
static constexpr auto name = L"MacType.Core.dll";
|
||||
#endif
|
||||
|
||||
wil::unique_hmodule handle;
|
||||
if (!GetModuleHandleExW(0, name, handle.addressof()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto resource = FindResourceW(handle.get(), MAKEINTRESOURCE(VS_VERSION_INFO), RT_VERSION);
|
||||
if (!resource)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto dataHandle = LoadResource(handle.get(), resource);
|
||||
if (!dataHandle)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto data = LockResource(dataHandle);
|
||||
if (!data)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
VS_FIXEDFILEINFO* info;
|
||||
UINT varLen = 0;
|
||||
if (!VerQueryValueW(data, L"\\", reinterpret_cast<void**>(&info), &varLen))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto faulty = info->dwFileVersionMS < (1 << 16 | 2023);
|
||||
|
||||
if (faulty && p.warningCallback)
|
||||
{
|
||||
p.warningCallback(ATLAS_ENGINE_ERROR_MAC_TYPE, {});
|
||||
}
|
||||
|
||||
return faulty;
|
||||
}
|
||||
|
||||
BackendD3D::QuadInstance& BackendD3D::_getLastQuad() noexcept
|
||||
{
|
||||
assert(_instancesCount != 0);
|
||||
@@ -1497,7 +1577,19 @@ BackendD3D::AtlasGlyphEntry* BackendD3D::_drawBuiltinGlyph(const RenderingPayloa
|
||||
}
|
||||
else
|
||||
{
|
||||
BuiltinGlyphs::DrawBuiltinGlyph(p.d2dFactory.get(), _d2dRenderTarget.get(), _brush.get(), r, glyphIndex);
|
||||
// This code works in tandem with SHADING_TYPE_TEXT_BUILTIN_GLYPH in our pixel shader.
|
||||
// Unless someone removed it, it should have a lengthy comment visually explaining
|
||||
// what each of the 3 RGB components do. The short version is:
|
||||
// R: stretch the checkerboard pattern (Shape_Filled050) horizontally
|
||||
// G: invert the pixels
|
||||
// B: overrides the above and fills it
|
||||
static constexpr D2D1_COLOR_F shadeColorMap[] = {
|
||||
{ 1, 0, 0, 1 }, // Shape_Filled025
|
||||
{ 0, 0, 0, 1 }, // Shape_Filled050
|
||||
{ 1, 1, 0, 1 }, // Shape_Filled075
|
||||
{ 1, 1, 1, 1 }, // Shape_Filled100
|
||||
};
|
||||
BuiltinGlyphs::DrawBuiltinGlyph(p.d2dFactory.get(), _d2dRenderTarget.get(), _brush.get(), shadeColorMap, r, glyphIndex);
|
||||
shadingType = ShadingType::TextBuiltinGlyph;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace Microsoft::Console::Render::Atlas
|
||||
struct BackendD3D : IBackend
|
||||
{
|
||||
BackendD3D(const RenderingPayload& p);
|
||||
~BackendD3D() override;
|
||||
|
||||
void ReleaseResources() noexcept override;
|
||||
void Render(RenderingPayload& payload) override;
|
||||
@@ -203,6 +204,7 @@ namespace Microsoft::Console::Render::Atlas
|
||||
void _d2dEndDrawing();
|
||||
ATLAS_ATTR_COLD void _resetGlyphAtlas(const RenderingPayload& p);
|
||||
ATLAS_ATTR_COLD void _resizeGlyphAtlas(const RenderingPayload& p, u16 u, u16 v);
|
||||
static bool _checkMacTypeVersion(const RenderingPayload& p);
|
||||
QuadInstance& _getLastQuad() noexcept;
|
||||
QuadInstance& _appendQuad();
|
||||
ATLAS_ATTR_COLD void _bumpInstancesSize();
|
||||
|
||||
@@ -135,8 +135,6 @@ inline constexpr f32 Pos_Lut[][2] = {
|
||||
/* Pos_11_12 */ { 11.0f / 12.0f, 0.0f },
|
||||
};
|
||||
|
||||
static constexpr char32_t BoxDrawing_FirstChar = 0x2500;
|
||||
static constexpr u32 BoxDrawing_CharCount = 0xA0;
|
||||
static constexpr Instruction BoxDrawing[BoxDrawing_CharCount][InstructionsPerGlyph] = {
|
||||
// U+2500 ─ BOX DRAWINGS LIGHT HORIZONTAL
|
||||
{
|
||||
@@ -964,8 +962,6 @@ static constexpr Instruction BoxDrawing[BoxDrawing_CharCount][InstructionsPerGly
|
||||
},
|
||||
};
|
||||
|
||||
static constexpr char32_t Powerline_FirstChar = 0xE0B0;
|
||||
static constexpr u32 Powerline_CharCount = 0x10;
|
||||
static constexpr Instruction Powerline[Powerline_CharCount][InstructionsPerGlyph] = {
|
||||
// U+E0B0 Right triangle solid
|
||||
{
|
||||
@@ -1071,7 +1067,20 @@ static const Instruction* GetInstructions(char32_t codepoint) noexcept
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void BuiltinGlyphs::DrawBuiltinGlyph(ID2D1Factory* factory, ID2D1DeviceContext* renderTarget, ID2D1SolidColorBrush* brush, const D2D1_RECT_F& rect, char32_t codepoint)
|
||||
i32 BuiltinGlyphs::GetBitmapCellIndex(char32_t codepoint) noexcept
|
||||
{
|
||||
if (BoxDrawing_IsMapped(codepoint))
|
||||
{
|
||||
return codepoint - BoxDrawing_FirstChar;
|
||||
}
|
||||
if (Powerline_IsMapped(codepoint))
|
||||
{
|
||||
return codepoint - Powerline_FirstChar + BoxDrawing_CharCount;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void BuiltinGlyphs::DrawBuiltinGlyph(ID2D1Factory* factory, ID2D1DeviceContext* renderTarget, ID2D1SolidColorBrush* brush, const D2D1_COLOR_F (&shadeColorMap)[4], const D2D1_RECT_F& rect, char32_t codepoint)
|
||||
{
|
||||
renderTarget->PushAxisAlignedClip(&rect, D2D1_ANTIALIAS_MODE_ALIASED);
|
||||
const auto restoreD2D = wil::scope_exit([&]() {
|
||||
@@ -1122,15 +1131,18 @@ void BuiltinGlyphs::DrawBuiltinGlyph(ID2D1Factory* factory, ID2D1DeviceContext*
|
||||
const auto lineOffsetX = isHollowRect || isLineX ? lineWidthHalf : 0.0f;
|
||||
const auto lineOffsetY = isHollowRect || isLineY ? lineWidthHalf : 0.0f;
|
||||
|
||||
begX = roundf(begX - lineOffsetX) + lineOffsetX;
|
||||
begY = roundf(begY - lineOffsetY) + lineOffsetY;
|
||||
endX = roundf(endX + lineOffsetX) - lineOffsetX;
|
||||
endY = roundf(endY + lineOffsetY) - lineOffsetY;
|
||||
|
||||
const auto begXabs = begX + rectX;
|
||||
const auto begYabs = begY + rectY;
|
||||
const auto endXabs = endX + rectX;
|
||||
const auto endYabs = endY + rectY;
|
||||
// Direct2D draws strokes centered on the path. In order to make them pixel-perfect we need to round the
|
||||
// coordinates to whole pixels, but offset by half the stroke width (= the radius of the stroke).
|
||||
//
|
||||
// All floats up to this point will be highly "consistent" between different `rect`s of identical size and
|
||||
// different shapes, because the above calculations work with only a small set of constant floats.
|
||||
// However, the addition of a potentially fractional begX/Y with a highly variable `rect` position is different.
|
||||
// Rounding beg/endX/Y first ensures that we continue to get a consistent behavior between calls.
|
||||
// This is particularly noticeable at smaller font sizes, where the line width is just a pixel or two.
|
||||
const auto begXabs = rectX + roundf(begX - lineOffsetX) + lineOffsetX;
|
||||
const auto begYabs = rectY + roundf(begY - lineOffsetY) + lineOffsetY;
|
||||
const auto endXabs = rectX + roundf(endX + lineOffsetX) - lineOffsetX;
|
||||
const auto endYabs = rectY + roundf(endY + lineOffsetY) - lineOffsetY;
|
||||
|
||||
switch (shape)
|
||||
{
|
||||
@@ -1139,21 +1151,8 @@ void BuiltinGlyphs::DrawBuiltinGlyph(ID2D1Factory* factory, ID2D1DeviceContext*
|
||||
case Shape_Filled075:
|
||||
case Shape_Filled100:
|
||||
{
|
||||
// This code works in tandem with SHADING_TYPE_TEXT_BUILTIN_GLYPH in our pixel shader.
|
||||
// Unless someone removed it, it should have a lengthy comment visually explaining
|
||||
// what each of the 3 RGB components do. The short version is:
|
||||
// R: stretch the checkerboard pattern (Shape_Filled050) horizontally
|
||||
// G: invert the pixels
|
||||
// B: overrides the above and fills it
|
||||
static constexpr D2D1_COLOR_F colors[] = {
|
||||
{ 1, 0, 0, 1 }, // Shape_Filled025
|
||||
{ 0, 0, 0, 1 }, // Shape_Filled050
|
||||
{ 1, 1, 0, 1 }, // Shape_Filled075
|
||||
{ 1, 1, 1, 1 }, // Shape_Filled100
|
||||
};
|
||||
|
||||
const auto brushColor = brush->GetColor();
|
||||
brush->SetColor(&colors[shape]);
|
||||
brush->SetColor(&shadeColorMap[shape]);
|
||||
|
||||
const D2D1_RECT_F r{ begXabs, begYabs, endXabs, endYabs };
|
||||
renderTarget->FillRectangle(&r, brush);
|
||||
@@ -1183,13 +1182,13 @@ void BuiltinGlyphs::DrawBuiltinGlyph(ID2D1Factory* factory, ID2D1DeviceContext*
|
||||
}
|
||||
case Shape_FilledEllipsis:
|
||||
{
|
||||
const D2D1_ELLIPSE e{ { begXabs, begYabs }, endX, endY };
|
||||
const D2D1_ELLIPSE e{ { rectX + begX, rectY + begY }, endX, endY };
|
||||
renderTarget->FillEllipse(&e, brush);
|
||||
break;
|
||||
}
|
||||
case Shape_EmptyEllipsis:
|
||||
{
|
||||
const D2D1_ELLIPSE e{ { begXabs, begYabs }, endX, endY };
|
||||
const D2D1_ELLIPSE e{ { rectX + begX, rectY + begY }, endX, endY };
|
||||
renderTarget->DrawEllipse(&e, brush, lineWidth, nullptr);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,17 @@
|
||||
namespace Microsoft::Console::Render::Atlas::BuiltinGlyphs
|
||||
{
|
||||
bool IsBuiltinGlyph(char32_t codepoint) noexcept;
|
||||
void DrawBuiltinGlyph(ID2D1Factory* factory, ID2D1DeviceContext* renderTarget, ID2D1SolidColorBrush* brush, const D2D1_RECT_F& rect, char32_t codepoint);
|
||||
void DrawBuiltinGlyph(ID2D1Factory* factory, ID2D1DeviceContext* renderTarget, ID2D1SolidColorBrush* brush, const D2D1_COLOR_F (&shadeColorMap)[4], const D2D1_RECT_F& rect, char32_t codepoint);
|
||||
|
||||
inline constexpr char32_t BoxDrawing_FirstChar = 0x2500;
|
||||
inline constexpr u32 BoxDrawing_CharCount = 0xA0;
|
||||
|
||||
inline constexpr char32_t Powerline_FirstChar = 0xE0B0;
|
||||
inline constexpr u32 Powerline_CharCount = 0x10;
|
||||
|
||||
inline constexpr u32 TotalCharCount = BoxDrawing_CharCount + Powerline_CharCount;
|
||||
|
||||
i32 GetBitmapCellIndex(char32_t codepoint) noexcept;
|
||||
|
||||
// This is just an extra. It's not actually implemented as part of BuiltinGlyphs.cpp.
|
||||
constexpr bool IsSoftFontChar(char32_t ch) noexcept
|
||||
|
||||
@@ -53,6 +53,8 @@ namespace Microsoft::Console::Render::Atlas
|
||||
// My best effort of replicating __attribute__((cold)) from gcc/clang.
|
||||
#define ATLAS_ATTR_COLD __declspec(noinline)
|
||||
|
||||
#define ATLAS_ENGINE_ERROR_MAC_TYPE MAKE_HRESULT(SEVERITY_SUCCESS, FACILITY_ITF, 'MT')
|
||||
|
||||
template<typename T>
|
||||
struct vec2
|
||||
{
|
||||
@@ -404,9 +406,9 @@ namespace Microsoft::Console::Render::Atlas
|
||||
til::generational<CursorSettings> cursor;
|
||||
til::generational<MiscellaneousSettings> misc;
|
||||
// Size of the viewport / swap chain in pixel.
|
||||
u16x2 targetSize{ 1, 1 };
|
||||
u16x2 targetSize{ 0, 0 };
|
||||
// Size of the portion of the text buffer that we're drawing on the screen.
|
||||
u16x2 viewportCellCount{ 1, 1 };
|
||||
u16x2 viewportCellCount{ 0, 0 };
|
||||
// The position of the viewport inside the text buffer (in cells).
|
||||
u16x2 viewportOffset{ 0, 0 };
|
||||
};
|
||||
|
||||
@@ -37,7 +37,20 @@ XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe,
|
||||
// the pipe.
|
||||
[[nodiscard]] HRESULT XtermEngine::StartPaint() noexcept
|
||||
{
|
||||
RETURN_IF_FAILED(VtEngine::StartPaint());
|
||||
const auto hr = VtEngine::StartPaint();
|
||||
if (hr != S_OK)
|
||||
{
|
||||
// If _noFlushOnEnd was set, and we didn't return early, it would usually
|
||||
// have been reset in the EndPaint call. But since that's not going to
|
||||
// happen now, we need to reset it here, otherwise we may mistakenly skip
|
||||
// the flush on the next frame.
|
||||
if (!_noFlushOnEnd)
|
||||
{
|
||||
_Flush();
|
||||
}
|
||||
_noFlushOnEnd = false;
|
||||
return hr;
|
||||
}
|
||||
|
||||
_trace.TraceLastText(_lastText);
|
||||
|
||||
@@ -83,15 +96,6 @@ XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe,
|
||||
}
|
||||
}
|
||||
|
||||
if (!_quickReturn)
|
||||
{
|
||||
if (_WillWriteSingleChar())
|
||||
{
|
||||
// Don't re-enable the cursor.
|
||||
_quickReturn = true;
|
||||
}
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
@@ -520,9 +524,10 @@ CATCH_RETURN();
|
||||
// proper utf-8 string, depending on our mode.
|
||||
// Arguments:
|
||||
// - wstr - wstring of text to be written
|
||||
// - flush - set to true if the string should be flushed immediately
|
||||
// Return Value:
|
||||
// - S_OK or suitable HRESULT error from either conversion or writing pipe.
|
||||
[[nodiscard]] HRESULT XtermEngine::WriteTerminalW(const std::wstring_view wstr) noexcept
|
||||
[[nodiscard]] HRESULT XtermEngine::WriteTerminalW(const std::wstring_view wstr, const bool flush) noexcept
|
||||
{
|
||||
RETURN_IF_FAILED(_fUseAsciiOnly ?
|
||||
VtEngine::_WriteTerminalAscii(wstr) :
|
||||
@@ -535,8 +540,11 @@ CATCH_RETURN();
|
||||
// cause flickering (where we've buffered some state but not the whole
|
||||
// "frame" as specified by the app). We'll just immediately buffer this
|
||||
// sequence, and flush it when the render thread comes around to paint the
|
||||
// frame normally.
|
||||
|
||||
// frame normally, unless a flush has been explicitly requested.
|
||||
if (flush)
|
||||
{
|
||||
_flushImpl();
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace Microsoft::Console::Render
|
||||
|
||||
[[nodiscard]] HRESULT InvalidateScroll(const til::point* const pcoordDelta) noexcept override;
|
||||
|
||||
[[nodiscard]] HRESULT WriteTerminalW(const std::wstring_view str) noexcept override;
|
||||
[[nodiscard]] HRESULT WriteTerminalW(const std::wstring_view str, const bool flush) noexcept override;
|
||||
|
||||
[[nodiscard]] HRESULT SetWindowVisibility(const bool showOrHide) noexcept override;
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ CATCH_RETURN();
|
||||
}
|
||||
_skipCursor = false;
|
||||
|
||||
_cursorMoved = true;
|
||||
_cursorMoved = psrRegion->origin() != _lastText;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -52,38 +52,3 @@ void VtEngine::_OrRect(_Inout_ til::inclusive_rect* const pRectExisting, const t
|
||||
pRectExisting->right = std::max(pRectExisting->right, pRectToOr->right);
|
||||
pRectExisting->bottom = std::max(pRectExisting->bottom, pRectToOr->bottom);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Returns true if the invalidated region indicates that we only need to
|
||||
// simply print text from the current cursor position. This will prevent us
|
||||
// from sending extra VT set-up/tear down sequences (?12h/l) when all we
|
||||
// need to do is print more text at the current cursor position.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - true iff only the next character is invalid
|
||||
bool VtEngine::_WillWriteSingleChar() const
|
||||
{
|
||||
// If there is no scroll delta, return false.
|
||||
if (til::point{ 0, 0 } != _scrollDelta)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If there is more than one invalid char, return false.
|
||||
if (!_invalidMap.one())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the single point at which things are invalid.
|
||||
const auto invalidPoint = _invalidMap.runs().front().origin();
|
||||
|
||||
// Either the next character to the right or the immediately previous
|
||||
// character should follow this code path
|
||||
// (The immediate previous character would suggest a backspace)
|
||||
auto invalidIsNext = invalidPoint == _lastText;
|
||||
auto invalidIsLast = invalidPoint == til::point{ _lastText.x - 1, _lastText.y };
|
||||
|
||||
return invalidIsNext || invalidIsLast;
|
||||
}
|
||||
|
||||
@@ -20,30 +20,30 @@ using namespace Microsoft::Console::Types;
|
||||
// HRESULT error code if painting didn't start successfully.
|
||||
[[nodiscard]] HRESULT VtEngine::StartPaint() noexcept
|
||||
{
|
||||
// When unit testing, there may be no pipe, but we still need to paint.
|
||||
if (!_hFile)
|
||||
{
|
||||
return S_FALSE;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// If we're using line renditions, and this is a full screen paint, we can
|
||||
// potentially stop using them at the end of this frame.
|
||||
_stopUsingLineRenditions = _usingLineRenditions && _AllIsInvalid();
|
||||
|
||||
// If there's nothing to do, quick return
|
||||
// If there's nothing to do, we won't need to paint.
|
||||
auto somethingToDo = _invalidMap.any() ||
|
||||
_scrollDelta != til::point{ 0, 0 } ||
|
||||
_cursorMoved ||
|
||||
_titleChanged;
|
||||
|
||||
_quickReturn = !somethingToDo;
|
||||
_trace.TraceStartPaint(_quickReturn,
|
||||
_trace.TraceStartPaint(!somethingToDo,
|
||||
_invalidMap,
|
||||
_lastViewport.ToExclusive(),
|
||||
_scrollDelta,
|
||||
_cursorMoved,
|
||||
_wrappedRow);
|
||||
|
||||
return _quickReturn ? S_FALSE : S_OK;
|
||||
return somethingToDo ? S_OK : S_FALSE;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
@@ -142,9 +142,9 @@ using namespace Microsoft::Console::Types;
|
||||
_usingLineRenditions = true;
|
||||
}
|
||||
// One simple optimization is that we can skip sending the line attributes
|
||||
// when _quickReturn is true. That indicates that we're writing out a single
|
||||
// character, which should preclude there being a rendition switch.
|
||||
if (_usingLineRenditions && !_quickReturn)
|
||||
// when we're writing out a single character, which should preclude there
|
||||
// being a rendition switch.
|
||||
if (_usingLineRenditions && !_invalidMap.one())
|
||||
{
|
||||
RETURN_IF_FAILED(_MoveCursor({ _lastText.x, targetRow }));
|
||||
switch (lineRendition)
|
||||
|
||||
@@ -37,7 +37,6 @@ VtEngine::VtEngine(_In_ wil::unique_hfile pipe,
|
||||
_pool(til::pmr::get_default_resource()),
|
||||
_invalidMap(initialViewport.Dimensions(), false, &_pool),
|
||||
_scrollDelta(0, 0),
|
||||
_quickReturn(false),
|
||||
_clearedAllThisFrame(false),
|
||||
_cursorMoved(false),
|
||||
_resized(false),
|
||||
|
||||
@@ -78,7 +78,7 @@ namespace Microsoft::Console::Render
|
||||
[[nodiscard]] HRESULT RequestCursor() noexcept;
|
||||
[[nodiscard]] HRESULT InheritCursor(const til::point coordCursor) noexcept;
|
||||
[[nodiscard]] HRESULT WriteTerminalUtf8(const std::string_view str) noexcept;
|
||||
[[nodiscard]] virtual HRESULT WriteTerminalW(const std::wstring_view str) noexcept = 0;
|
||||
[[nodiscard]] virtual HRESULT WriteTerminalW(const std::wstring_view str, const bool flush = false) noexcept = 0;
|
||||
void SetTerminalOwner(Microsoft::Console::VirtualTerminal::VtIo* const terminalOwner);
|
||||
void SetResizeQuirk(const bool resizeQuirk);
|
||||
void SetLookingForDSRCallback(std::function<void(bool)> pfnLooking) noexcept;
|
||||
@@ -113,7 +113,6 @@ namespace Microsoft::Console::Render
|
||||
til::point _lastText;
|
||||
til::point _scrollDelta;
|
||||
|
||||
bool _quickReturn;
|
||||
bool _clearedAllThisFrame;
|
||||
bool _cursorMoved;
|
||||
bool _resized;
|
||||
@@ -214,8 +213,6 @@ namespace Microsoft::Console::Render
|
||||
[[nodiscard]] HRESULT _RgbUpdateDrawingBrushes(const TextAttribute& textAttributes) noexcept;
|
||||
[[nodiscard]] HRESULT _16ColorUpdateDrawingBrushes(const TextAttribute& textAttributes) noexcept;
|
||||
|
||||
bool _WillWriteSingleChar() const;
|
||||
|
||||
// buffer space for these two functions to build their lines
|
||||
// so they don't have to alloc/free in a tight loop
|
||||
std::wstring _bufferLine;
|
||||
|
||||
@@ -4882,7 +4882,7 @@ ITermDispatch::StringHandler AdaptDispatch::_CreatePassthroughHandler()
|
||||
{
|
||||
buffer += L'\\';
|
||||
}
|
||||
engine.ActionPassThroughString(buffer);
|
||||
engine.ActionPassThroughString(buffer, true);
|
||||
buffer.clear();
|
||||
}
|
||||
return !endOfString;
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
virtual bool ActionPrint(const wchar_t wch) = 0;
|
||||
virtual bool ActionPrintString(const std::wstring_view string) = 0;
|
||||
|
||||
virtual bool ActionPassThroughString(const std::wstring_view string) = 0;
|
||||
virtual bool ActionPassThroughString(const std::wstring_view string, const bool flush = false) = 0;
|
||||
|
||||
virtual bool ActionEscDispatch(const VTID id) = 0;
|
||||
virtual bool ActionVt52EscDispatch(const VTID id, const VTParameters parameters) = 0;
|
||||
|
||||
@@ -276,9 +276,10 @@ bool InputStateMachineEngine::ActionPrintString(const std::wstring_view string)
|
||||
// string of characters given.
|
||||
// Arguments:
|
||||
// - string - string to dispatch.
|
||||
// - flush - not applicable to the input state machine.
|
||||
// Return Value:
|
||||
// - true iff we successfully dispatched the sequence.
|
||||
bool InputStateMachineEngine::ActionPassThroughString(const std::wstring_view string)
|
||||
bool InputStateMachineEngine::ActionPassThroughString(const std::wstring_view string, const bool /*flush*/)
|
||||
{
|
||||
if (_pDispatch->IsVtInputEnabled())
|
||||
{
|
||||
|
||||
@@ -142,7 +142,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
|
||||
bool ActionPrintString(const std::wstring_view string) override;
|
||||
|
||||
bool ActionPassThroughString(const std::wstring_view string) override;
|
||||
bool ActionPassThroughString(const std::wstring_view string, const bool flush) override;
|
||||
|
||||
bool ActionEscDispatch(const VTID id) override;
|
||||
|
||||
|
||||
@@ -180,14 +180,15 @@ bool OutputStateMachineEngine::ActionPrintString(const std::wstring_view string)
|
||||
// we don't know what to do with it)
|
||||
// Arguments:
|
||||
// - string - string to dispatch.
|
||||
// - flush - set to true if the string should be flushed immediately.
|
||||
// Return Value:
|
||||
// - true iff we successfully dispatched the sequence.
|
||||
bool OutputStateMachineEngine::ActionPassThroughString(const std::wstring_view string)
|
||||
bool OutputStateMachineEngine::ActionPassThroughString(const std::wstring_view string, const bool flush)
|
||||
{
|
||||
auto success = true;
|
||||
if (_pTtyConnection != nullptr)
|
||||
{
|
||||
const auto hr = _pTtyConnection->WriteTerminalW(string);
|
||||
const auto hr = _pTtyConnection->WriteTerminalW(string, flush);
|
||||
LOG_IF_FAILED(hr);
|
||||
success = SUCCEEDED(hr);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
|
||||
bool ActionPrintString(const std::wstring_view string) override;
|
||||
|
||||
bool ActionPassThroughString(const std::wstring_view string) override;
|
||||
bool ActionPassThroughString(const std::wstring_view string, const bool flush) override;
|
||||
|
||||
bool ActionEscDispatch(const VTID id) override;
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ public:
|
||||
return true;
|
||||
};
|
||||
|
||||
bool ActionPassThroughString(const std::wstring_view string) override
|
||||
bool ActionPassThroughString(const std::wstring_view string, const bool /*flush*/) override
|
||||
{
|
||||
passedThrough += string;
|
||||
return true;
|
||||
|
||||
@@ -108,15 +108,15 @@ static void printfUTF16(_In_z_ _Printf_format_string_ wchar_t const* const forma
|
||||
|
||||
static void wait()
|
||||
{
|
||||
printUTF16(L"\x1B[9999;1HPress any key to continue...");
|
||||
printUTF16(L"\x1b[9999;1HPress any key to continue...");
|
||||
_getch();
|
||||
}
|
||||
|
||||
static void clear()
|
||||
{
|
||||
printUTF16(
|
||||
L"\x1B[H" // move cursor to 0,0
|
||||
L"\x1B[2J" // clear screen
|
||||
L"\x1b[H" // move cursor to 0,0
|
||||
L"\x1b[2J" // clear screen
|
||||
);
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ int main()
|
||||
for (const auto& t : consoleAttributeTests)
|
||||
{
|
||||
const auto length = static_cast<DWORD>(wcslen(t.text));
|
||||
printfUTF16(L"\x1B[%d;5H%s", row + 1, t.text);
|
||||
printfUTF16(L"\x1b[%d;5H%s", row + 1, t.text);
|
||||
|
||||
WORD attributes[32];
|
||||
std::fill_n(&attributes[0], length, static_cast<WORD>(FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | t.attribute));
|
||||
@@ -190,16 +190,16 @@ int main()
|
||||
{ L"overlined", 53 },
|
||||
};
|
||||
|
||||
printfUTF16(L"\x1B[3;39HANSI escape SGR:");
|
||||
printfUTF16(L"\x1b[3;39HANSI escape SGR:");
|
||||
|
||||
int row = 5;
|
||||
for (const auto& t : basicSGR)
|
||||
{
|
||||
printfUTF16(L"\x1B[%d;39H\x1b[%dm%s\x1b[m", row, t.attribute, t.text);
|
||||
printfUTF16(L"\x1b[%d;39H\x1b[%dm%s\x1b[m", row, t.attribute, t.text);
|
||||
row += 2;
|
||||
}
|
||||
|
||||
printfUTF16(L"\x1B[%d;39H\x1b]8;;https://example.com\x1b\\hyperlink\x1b]8;;\x1b\\", row);
|
||||
printfUTF16(L"\x1b[%d;39H\x1b]8;;https://example.com\x1b\\hyperlink\x1b]8;;\x1b\\", row);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -211,18 +211,18 @@ int main()
|
||||
{ L"dashed", 5 },
|
||||
};
|
||||
|
||||
printfUTF16(L"\x1B[3;63HStyled Underlines:");
|
||||
printfUTF16(L"\x1b[3;63HStyled Underlines:");
|
||||
|
||||
int row = 5;
|
||||
for (const auto& t : styledUnderlines)
|
||||
{
|
||||
printfUTF16(L"\x1B[%d;63H\x1b[4:%dm", row, t.attribute);
|
||||
printfUTF16(L"\x1b[%d;63H\x1b[4:%dm", row, t.attribute);
|
||||
|
||||
const auto len = wcslen(t.text);
|
||||
for (size_t i = 0; i < len; ++i)
|
||||
{
|
||||
const auto color = colorbrewer::pastel1[i % std::size(colorbrewer::pastel1)];
|
||||
printfUTF16(L"\x1B[58:2::%d:%d:%dm%c", (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff, t.text[i]);
|
||||
printfUTF16(L"\x1b[58:2::%d:%d:%dm%c", (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff, t.text[i]);
|
||||
}
|
||||
|
||||
printfUTF16(L"\x1b[m");
|
||||
@@ -236,19 +236,19 @@ int main()
|
||||
|
||||
{
|
||||
printUTF16(
|
||||
L"\x1B[3;5HDECDWL Double Width \U0001FAE0 \x1B[45;92mA\u0353\u0353\x1B[m B\u036F\u036F"
|
||||
L"\x1B[4;3H\x1b#6DECDWL Double Width \U0001FAE0 \x1B[45;92mA\u0353\u0353\x1B[m B\u036F\u036F"
|
||||
L"\x1B[7;5HDECDHL Double Height \U0001F952\U0001F6C1 A\u0353\u0353 \x1B[45;92mB\u036F\u036F\x1B[m \x1B[45;92mX\u0353\u0353\x1B[m Y\u036F\u036F"
|
||||
L"\x1B[8;3H\x1b#3DECDHL Double Height Top \U0001F952 A\u0353\u0353 \x1B[45;92mB\u036F\u036F\x1B[m"
|
||||
L"\x1B[9;3H\x1b#4DECDHL Double Height Bottom \U0001F6C1 \x1B[45;92mX\u0353\u0353\x1B[m Y\u036F\u036F"
|
||||
L"\x1B[13;5H\x1b]8;;https://example.com\x1b\\DECDxL\x1b]8;;\x1b\\ <\x1B[45;92m!\x1B[m-- \x1B[3mitalic\x1b[m \x1b[4munderline\x1b[m \x1b[7mreverse\x1b[m"
|
||||
L"\x1B[15;5H\x1b]8;;https://example.com\x1b\\DECDxL\x1b]8;;\x1b\\ <\x1B[45;92m!\x1B[m-- \x1b[9mstrikethrough\x1b[m \x1b[21mdouble underline\x1b[m \x1b[53moverlined\x1b[m"
|
||||
L"\x1B[17;3H\x1b#6\x1b]8;;https://vt100.net/docs/vt510-rm/DECDWL.html\x1b\\DECDWL\x1b]8;;\x1b\\ <\x1B[45;92m!\x1B[m-- \x1B[3mitalic\x1b[m \x1b[4munderline\x1b[m \x1b[7mreverse\x1b[m"
|
||||
L"\x1B[19;3H\x1b#6\x1b]8;;https://vt100.net/docs/vt510-rm/DECDWL.html\x1b\\DECDWL\x1b]8;;\x1b\\ <\x1B[45;92m!\x1B[m-- \x1b[9mstrikethrough\x1b[m \x1b[21mdouble underline\x1b[m \x1b[53moverlined\x1b[m"
|
||||
L"\x1B[21;3H\x1b#3\x1b]8;;https://vt100.net/docs/vt510-rm/DECDHL.html\x1b\\DECDHL\x1b]8;;\x1b\\ <\x1B[45;92m!\x1B[m-- \x1B[3mitalic\x1b[m \x1b[4munderline\x1b[m \x1b[7mreverse\x1b[m"
|
||||
L"\x1B[22;3H\x1b#4\x1b]8;;https://vt100.net/docs/vt510-rm/DECDHL.html\x1b\\DECDHL\x1b]8;;\x1b\\ <\x1B[45;92m!\x1B[m-- \x1B[3mitalic\x1b[m \x1b[4munderline\x1b[m \x1b[7mreverse\x1b[m"
|
||||
L"\x1B[24;3H\x1b#3\x1b]8;;https://vt100.net/docs/vt510-rm/DECDHL.html\x1b\\DECDHL\x1b]8;;\x1b\\ <\x1B[45;92m!\x1B[m-- \x1b[9mstrikethrough\x1b[m \x1b[21mdouble underline\x1b[m \x1b[53moverlined\x1b[m"
|
||||
L"\x1B[25;3H\x1b#4\x1b]8;;https://vt100.net/docs/vt510-rm/DECDHL.html\x1b\\DECDHL\x1b]8;;\x1b\\ <\x1B[45;92m!\x1B[m-- \x1b[9mstrikethrough\x1b[m \x1b[21mdouble underline\x1b[m \x1b[53moverlined\x1b[m");
|
||||
L"\x1b[3;5HDECDWL Double Width \U0001FAE0 \x1b[45;92mA\u0353\u0353\x1b[m B\u036F\u036F"
|
||||
L"\x1b[4;3H\x1b#6DECDWL Double Width \U0001FAE0 \x1b[45;92mA\u0353\u0353\x1b[m B\u036F\u036F"
|
||||
L"\x1b[7;5HDECDHL Double Height \U0001F952\U0001F6C1 A\u0353\u0353 \x1b[45;92mB\u036F\u036F\x1b[m \x1b[45;92mX\u0353\u0353\x1b[m Y\u036F\u036F"
|
||||
L"\x1b[8;3H\x1b#3DECDHL Double Height Top \U0001F952 A\u0353\u0353 \x1b[45;92mB\u036F\u036F\x1b[m"
|
||||
L"\x1b[9;3H\x1b#4DECDHL Double Height Bottom \U0001F6C1 \x1b[45;92mX\u0353\u0353\x1b[m Y\u036F\u036F"
|
||||
L"\x1b[12;5H\x1b]8;;https://example.com\x1b\\DECDxL\x1b]8;;\x1b\\ <\x1b[45;92m!\x1b[m-- \x1b[3;4:3;58:2::255:0:0mita\x1b[58:2::0:255:0mlic\x1b[m \x1b[4munderline\x1b[m \x1b[7mreverse\x1b[m"
|
||||
L"\x1b[14;5H\x1b]8;;https://example.com\x1b\\DECDxL\x1b]8;;\x1b\\ <\x1b[45;92m!\x1b[m-- \x1b[9mstrikethrough\x1b[m \x1b[21mdouble underline\x1b[m \x1b[53moverlined\x1b[m"
|
||||
L"\x1b[16;3H\x1b#6\x1b]8;;https://vt100.net/docs/vt510-rm/DECDWL.html\x1b\\DECDWL\x1b]8;;\x1b\\ <\x1b[45;92m!\x1b[m-- \x1b[3;4:3;58:2::255:0:0mita\x1b[58:2::0:255:0mlic\x1b[m \x1b[4munderline\x1b[m \x1b[7mreverse\x1b[m"
|
||||
L"\x1b[18;3H\x1b#6\x1b]8;;https://vt100.net/docs/vt510-rm/DECDWL.html\x1b\\DECDWL\x1b]8;;\x1b\\ <\x1b[45;92m!\x1b[m-- \x1b[9mstrikethrough\x1b[m \x1b[21mdouble underline\x1b[m \x1b[53moverlined\x1b[m"
|
||||
L"\x1b[20;3H\x1b#3\x1b]8;;https://vt100.net/docs/vt510-rm/DECDHL.html\x1b\\DECDHL\x1b]8;;\x1b\\ <\x1b[45;92m!\x1b[m-- \x1b[3;4:3;58:2::255:0:0mita\x1b[58:2::0:255:0mlic\x1b[m \x1b[4munderline\x1b[m \x1b[7mreverse\x1b[m"
|
||||
L"\x1b[21;3H\x1b#4\x1b]8;;https://vt100.net/docs/vt510-rm/DECDHL.html\x1b\\DECDHL\x1b]8;;\x1b\\ <\x1b[45;92m!\x1b[m-- \x1b[3;4:3;58:2::255:0:0mita\x1b[58:2::0:255:0mlic\x1b[m \x1b[4munderline\x1b[m \x1b[7mreverse\x1b[m"
|
||||
L"\x1b[23;3H\x1b#3\x1b]8;;https://vt100.net/docs/vt510-rm/DECDHL.html\x1b\\DECDHL\x1b]8;;\x1b\\ <\x1b[45;92m!\x1b[m-- \x1b[9mstrikethrough\x1b[m \x1b[21mdouble underline\x1b[m \x1b[53moverlined\x1b[m"
|
||||
L"\x1b[24;3H\x1b#4\x1b]8;;https://vt100.net/docs/vt510-rm/DECDHL.html\x1b\\DECDHL\x1b]8;;\x1b\\ <\x1b[45;92m!\x1b[m-- \x1b[9mstrikethrough\x1b[m \x1b[21mdouble underline\x1b[m \x1b[53moverlined\x1b[m");
|
||||
|
||||
static constexpr WORD attributes[]{
|
||||
FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | COMMON_LVB_GRID_HORIZONTAL,
|
||||
@@ -264,7 +264,7 @@ int main()
|
||||
DWORD numberOfAttrsWritten;
|
||||
DWORD offset = 0;
|
||||
|
||||
for (const auto r : { 12, 14, 16, 18, 20, 21, 23, 24 })
|
||||
for (const auto r : { 11, 13, 15, 17, 19, 20, 22, 23 })
|
||||
{
|
||||
COORD coord;
|
||||
coord.X = r > 14 ? 2 : 4;
|
||||
@@ -338,14 +338,14 @@ int main()
|
||||
|
||||
#define DRCS_SEQUENCE L"\x1b( @#\x1b(A"
|
||||
printUTF16(
|
||||
L"\x1B[3;5HDECDLD and DRCS test - it should show \"WT\" in a single cell"
|
||||
L"\x1B[5;5HRegular: " DRCS_SEQUENCE L""
|
||||
L"\x1B[7;3H\x1b#6DECDWL: " DRCS_SEQUENCE L""
|
||||
L"\x1B[9;3H\x1b#3DECDHL: " DRCS_SEQUENCE L""
|
||||
L"\x1B[10;3H\x1b#4DECDHL: " DRCS_SEQUENCE L""
|
||||
L"\x1b[3;5HDECDLD and DRCS test - it should show \"WT\" in a single cell"
|
||||
L"\x1b[5;5HRegular: " DRCS_SEQUENCE L""
|
||||
L"\x1b[7;3H\x1b#6DECDWL: " DRCS_SEQUENCE L""
|
||||
L"\x1b[9;3H\x1b#3DECDHL: " DRCS_SEQUENCE L""
|
||||
L"\x1b[10;3H\x1b#4DECDHL: " DRCS_SEQUENCE L""
|
||||
// We map soft fonts into the private use area starting at U+EF20. This test ensures
|
||||
// that we correctly map actual fallback glyphs mixed into the DRCS glyphs.
|
||||
L"\x1B[12;5HUnicode Fallback: \uE000\uE001" DRCS_SEQUENCE L"\uE003\uE004");
|
||||
L"\x1b[12;5HUnicode Fallback: \uE000\uE001" DRCS_SEQUENCE L"\uE003\uE004");
|
||||
#undef DRCS_SEQUENCE
|
||||
|
||||
wait();
|
||||
|
||||
@@ -22,10 +22,15 @@ try
|
||||
RETURN_HR_IF_NULL(E_INVALIDARG, pProvider);
|
||||
RETURN_HR_IF_NULL(E_INVALIDARG, pData);
|
||||
|
||||
pData->LockConsole();
|
||||
const auto unlock = wil::scope_exit([&]() noexcept {
|
||||
pData->UnlockConsole();
|
||||
});
|
||||
|
||||
_pProvider = pProvider;
|
||||
_pData = pData;
|
||||
_start = pData->GetViewport().Origin();
|
||||
_end = pData->GetViewport().Origin();
|
||||
_end = _start;
|
||||
_blockRange = false;
|
||||
_wordDelimiters = wordDelimiters;
|
||||
|
||||
@@ -41,9 +46,16 @@ HRESULT UiaTextRangeBase::RuntimeClassInitialize(_In_ Render::IRenderData* pData
|
||||
_In_ std::wstring_view wordDelimiters) noexcept
|
||||
try
|
||||
{
|
||||
RETURN_HR_IF_NULL(E_INVALIDARG, pProvider);
|
||||
RETURN_HR_IF_NULL(E_INVALIDARG, pData);
|
||||
RETURN_IF_FAILED(RuntimeClassInitialize(pData, pProvider, wordDelimiters));
|
||||
|
||||
pData->LockConsole();
|
||||
const auto unlock = wil::scope_exit([&]() noexcept {
|
||||
pData->UnlockConsole();
|
||||
});
|
||||
|
||||
_pProvider = pProvider;
|
||||
_pData = pData;
|
||||
// GH#8730: The cursor position may be in a delayed state, resulting in it being out of bounds.
|
||||
// If that's the case, clamp it to be within bounds.
|
||||
// TODO GH#12440: We should be able to just check some fields off of the Cursor object,
|
||||
@@ -51,6 +63,8 @@ try
|
||||
_start = cursor.GetPosition();
|
||||
pData->GetTextBuffer().GetSize().Clamp(_start);
|
||||
_end = _start;
|
||||
_blockRange = false;
|
||||
_wordDelimiters = wordDelimiters;
|
||||
|
||||
UiaTracing::TextRange::Constructor(*this);
|
||||
return S_OK;
|
||||
@@ -66,15 +80,15 @@ HRESULT UiaTextRangeBase::RuntimeClassInitialize(_In_ Render::IRenderData* pData
|
||||
_In_ std::wstring_view wordDelimiters) noexcept
|
||||
try
|
||||
{
|
||||
RETURN_IF_FAILED(RuntimeClassInitialize(pData, pProvider, wordDelimiters));
|
||||
RETURN_HR_IF_NULL(E_INVALIDARG, pProvider);
|
||||
RETURN_HR_IF_NULL(E_INVALIDARG, pData);
|
||||
|
||||
// start must be before or equal to end
|
||||
_pProvider = pProvider;
|
||||
_pData = pData;
|
||||
_start = std::min(start, end);
|
||||
_end = std::max(start, end);
|
||||
|
||||
// This should be the only way to set if we are a blockRange
|
||||
// This is used for blockSelection
|
||||
_blockRange = blockRange;
|
||||
_wordDelimiters = wordDelimiters;
|
||||
|
||||
UiaTracing::TextRange::Constructor(*this);
|
||||
return S_OK;
|
||||
@@ -148,9 +162,6 @@ til::point UiaTextRangeBase::GetEndpoint(TextPatternRangeEndpoint endpoint) cons
|
||||
// - true if range is degenerate, false otherwise.
|
||||
bool UiaTextRangeBase::SetEndpoint(TextPatternRangeEndpoint endpoint, const til::point val) noexcept
|
||||
{
|
||||
// GH#6402: Get the actual buffer size here, instead of the one
|
||||
// constrained by the virtual bottom.
|
||||
const auto bufferSize = _pData->GetTextBuffer().GetSize();
|
||||
switch (endpoint)
|
||||
{
|
||||
case TextPatternRangeEndpoint_End:
|
||||
|
||||
@@ -89,6 +89,20 @@ $Cards = Get-GithubProjectCard -ColumnId $ToPickColumn.id
|
||||
|
||||
& git fetch --all 2>&1 | Out-Null
|
||||
|
||||
$Branch = & git rev-parse --abbrev-ref HEAD
|
||||
$RemoteForCurrentBranch = & git config "branch.$Branch.remote"
|
||||
$CommitsBehind = [int](& git rev-list --count "$RemoteForCurrentBranch/$Branch" "^$Branch")
|
||||
|
||||
If ($LASTEXITCODE -Ne 0) {
|
||||
Write-Error "Failed to determine branch divergence"
|
||||
Exit 1
|
||||
}
|
||||
|
||||
If ($CommitsBehind -Gt 0) {
|
||||
Write-Error "Local branch $Branch is out of date with $RemoteForCurrentBranch; consider git pull"
|
||||
Exit 1
|
||||
}
|
||||
|
||||
$Entries = @(& git log $SourceBranch --first-parent --grep "(#\($($Cards.Number -Join "\|")\))" "--pretty=format:%H%x1C%s" |
|
||||
ConvertFrom-CSV -Delimiter "`u{001C}" -Header CommitID,Subject)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user