Version 2.18.0-156.0.dev
Merge commit '83850ac5fa8cfd58a0dcbfe7103b6455a2a1d523' into 'dev'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6195c9e..2eab9df 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -54,6 +54,52 @@
- Add `connectionState` attribute and `connectionstatechange` listener to
`RtcPeerConnection`.
+#### `dart:io`
+
+- **Breaking Change** [#45630][]: The Dart VM no longer automatically restores
+ the initial terminal settings upon exit. Programs that change the `Stdin`
+ settings `lineMode` and `echoMode` are now responsible for restoring the
+ settings upon program exit. E.g. a program disabling `echoMode` will now
+ need to restore the setting itself and handle exiting by the appropriate
+ signals if desired:
+
+ ```dart
+ import 'dart:io';
+ import 'dart:async';
+
+ main() {
+ bool echoWasEnabled = stdin.echoMode;
+ try {
+ late StreamSubscription subscription;
+ subscription = ProcessSignal.sigint.watch().listen((ProcessSignal signal) {
+ stdin.echoMode = echoWasEnabled;
+ subscription.cancel();
+ Process.killPid(pid, signal); /* Die by the signal. */
+ });
+ stdin.echoMode = false;
+ } finally {
+ stdin.echoMode = echoWasEnabled;
+ }
+ }
+ ```
+
+ This change is needed to fix [#36453][] where the dart programs not caring
+ about the terminal settings can inadverently corrupt the terminal settings
+ when e.g. piping into less.
+
+ Furthermore the `echoMode` setting now only controls the `echo` local mode
+ and no longer sets the `echonl` local mode on POSIX systems (which controls
+ whether newline are echoed even if the regular echo mode is disabled). The
+ `echonl` local mode is usually turned off in common shell environments.
+ Programs that wish to control the `echonl` local mode can use the new
+ `echoNewlineMode` setting.
+
+ The Windows console code pages (if not UTF-8) and ANSI escape code support
+ (if disabled) remain restored when the VM exits.
+
+[#45630]: https://github.com/dart-lang/sdk/issues/45630
+[#36453]: https://github.com/dart-lang/sdk/issues/36453
+
#### `dart:js_util`
- Added `dartify` and a number of minor helper functions.
diff --git a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
index 35444e1..a7e9b00 100644
--- a/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
+++ b/pkg/_fe_analyzer_shared/lib/src/messages/codes_generated.dart
@@ -3847,6 +3847,29 @@
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Template<Message Function(String name)>
+ templateExtensionInNullAwareReceiver =
+ const Template<Message Function(String name)>(
+ problemMessageTemplate: r"""The extension '#name' cannot be null.""",
+ correctionMessageTemplate: r"""Try replacing '?.' with '.'""",
+ withArguments: _withArgumentsExtensionInNullAwareReceiver);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Code<Message Function(String name)> codeExtensionInNullAwareReceiver =
+ const Code<Message Function(String name)>("ExtensionInNullAwareReceiver",
+ severity: Severity.warning);
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+Message _withArgumentsExtensionInNullAwareReceiver(String name) {
+ if (name.isEmpty) throw 'No name provided';
+ name = demangleMixinApplicationName(name);
+ return new Message(codeExtensionInNullAwareReceiver,
+ problemMessage: """The extension '${name}' cannot be null.""",
+ correctionMessage: """Try replacing '?.' with '.'""",
+ arguments: {'name': name});
+}
+
+// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
+const Template<Message Function(String name)>
templateExtensionMemberConflictsWithObjectMember =
const Template<Message Function(String name)>(
problemMessageTemplate:
diff --git a/pkg/front_end/lib/src/fasta/kernel/body_builder.dart b/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
index 02535d9..2946592 100644
--- a/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/body_builder.dart
@@ -2947,7 +2947,7 @@
assert(declaration.isStatic || declaration.isTopLevel);
MemberBuilder memberBuilder = declaration as MemberBuilder;
return new StaticAccessGenerator(
- this, token, name, memberBuilder.member, null);
+ this, token, name, memberBuilder.parent, memberBuilder.member, null);
} else if (declaration is PrefixBuilder) {
assert(prefix == null);
return new PrefixUseGenerator(this, token, declaration);
diff --git a/pkg/front_end/lib/src/fasta/kernel/expression_generator.dart b/pkg/front_end/lib/src/fasta/kernel/expression_generator.dart
index 4d0d98c..066a9c7 100644
--- a/pkg/front_end/lib/src/fasta/kernel/expression_generator.dart
+++ b/pkg/front_end/lib/src/fasta/kernel/expression_generator.dart
@@ -20,6 +20,7 @@
import '../builder/declaration_builder.dart';
import '../builder/extension_builder.dart';
import '../builder/invalid_type_declaration_builder.dart';
+import '../builder/library_builder.dart';
import '../builder/member_builder.dart';
import '../builder/named_type_builder.dart';
import '../builder/nullability_builder.dart';
@@ -1386,12 +1387,18 @@
final int? typeOffset;
final bool isNullAware;
+ /// The builder for the parent of [readTarget] and [writeTarget]. This is
+ /// either the builder for the enclosing library, class, or extension.
+ final Builder? parentBuilder;
+
StaticAccessGenerator(ExpressionGeneratorHelper helper, Token token,
- this.targetName, this.readTarget, this.writeTarget,
+ this.targetName, this.parentBuilder, this.readTarget, this.writeTarget,
{this.typeOffset, this.isNullAware: false})
// ignore: unnecessary_null_comparison
: assert(targetName != null),
assert(readTarget != null || writeTarget != null),
+ assert(parentBuilder is DeclarationBuilder ||
+ parentBuilder is LibraryBuilder),
super(helper, token);
factory StaticAccessGenerator.fromBuilder(
@@ -1402,19 +1409,44 @@
MemberBuilder? setterBuilder,
{int? typeOffset,
bool isNullAware: false}) {
- return new StaticAccessGenerator(helper, token, targetName,
- getterBuilder?.readTarget, setterBuilder?.writeTarget,
- typeOffset: typeOffset, isNullAware: isNullAware);
+ // If both [getterBuilder] and [setterBuilder] exist, they must both be
+ // either top level (potentially from different libraries) or from the same
+ // class/extension.
+ assert(getterBuilder == null ||
+ setterBuilder == null ||
+ (getterBuilder.parent is LibraryBuilder &&
+ setterBuilder.parent is LibraryBuilder) ||
+ getterBuilder.parent == setterBuilder.parent);
+ return new StaticAccessGenerator(
+ helper,
+ token,
+ targetName,
+ getterBuilder?.parent ?? setterBuilder?.parent,
+ getterBuilder?.readTarget,
+ setterBuilder?.writeTarget,
+ typeOffset: typeOffset,
+ isNullAware: isNullAware);
}
void _reportNonNullableInNullAwareWarningIfNeeded() {
if (isNullAware && _helper.libraryBuilder.isNonNullableByDefault) {
- String className = (readTarget ?? writeTarget)!.enclosingClass!.name;
- _helper.libraryBuilder.addProblem(
- templateClassInNullAwareReceiver.withArguments(className),
- typeOffset ?? fileOffset,
- typeOffset != null ? className.length : noLength,
- _helper.uri);
+ DeclarationBuilder declarationBuilder =
+ parentBuilder as DeclarationBuilder;
+ if (declarationBuilder.isExtension) {
+ String extensionName = declarationBuilder.name;
+ _helper.libraryBuilder.addProblem(
+ templateExtensionInNullAwareReceiver.withArguments(extensionName),
+ typeOffset ?? fileOffset,
+ typeOffset != null ? extensionName.length : noLength,
+ _helper.uri);
+ } else {
+ String className = declarationBuilder.name;
+ _helper.libraryBuilder.addProblem(
+ templateClassInNullAwareReceiver.withArguments(className),
+ typeOffset ?? fileOffset,
+ typeOffset != null ? className.length : noLength,
+ _helper.uri);
+ }
}
}
diff --git a/pkg/front_end/messages.status b/pkg/front_end/messages.status
index d7c85b5..7c33194 100644
--- a/pkg/front_end/messages.status
+++ b/pkg/front_end/messages.status
@@ -314,6 +314,7 @@
ExtensionDeclaresAbstractMember/example: Fail
ExtensionDeclaresConstructor/example: Fail
ExtensionDeclaresInstanceField/example: Fail
+ExtensionInNullAwareReceiver/analyzerCode: Fail
ExtensionMemberConflictsWithObjectMember/analyzerCode: Fail
ExternalConstructorWithBody/part_wrapped_script1: Fail
ExternalConstructorWithBody/script1: Fail
diff --git a/pkg/front_end/messages.yaml b/pkg/front_end/messages.yaml
index 7691707..0d6ce5d 100644
--- a/pkg/front_end/messages.yaml
+++ b/pkg/front_end/messages.yaml
@@ -5223,6 +5223,19 @@
C?.field;
}
+ExtensionInNullAwareReceiver:
+ problemMessage: "The extension '#name' cannot be null."
+ correctionMessage: "Try replacing '?.' with '.'"
+ severity: WARNING
+ configuration: nnbd-strong
+ script: |
+ extension E on int {
+ static var field;
+ }
+ method() {
+ E?.field;
+ }
+
NonNullableNotAssignedError:
problemMessage: "Non-nullable variable '#name' must be assigned before it can be used."
configuration: nnbd-strong
diff --git a/pkg/front_end/test/fasta/generator_to_string_test.dart b/pkg/front_end/test/fasta/generator_to_string_test.dart
index c35e622..2d4703b 100644
--- a/pkg/front_end/test/fasta/generator_to_string_test.dart
+++ b/pkg/front_end/test/fasta/generator_to_string_test.dart
@@ -187,7 +187,8 @@
"StaticAccessGenerator(offset: 4, targetName: foo,"
" readTarget: $uri::myGetter,"
" writeTarget: $uri::mySetter)",
- new StaticAccessGenerator(helper, token, 'foo', getter, setter));
+ new StaticAccessGenerator(
+ helper, token, 'foo', libraryBuilder, getter, setter));
check(
"LoadLibraryGenerator(offset: 4,"
" builder: Instance of 'LoadLibraryBuilder')",
diff --git a/pkg/front_end/testcases/general/issue49127.dart b/pkg/front_end/testcases/general/issue49127.dart
new file mode 100644
index 0000000..b475828
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue49127.dart
@@ -0,0 +1,11 @@
+// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+extension E on int {
+ static String s = "Lily was here";
+}
+
+test() {
+ E?.s;
+}
diff --git a/pkg/front_end/testcases/general/issue49127.dart.textual_outline.expect b/pkg/front_end/testcases/general/issue49127.dart.textual_outline.expect
new file mode 100644
index 0000000..06deec8
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue49127.dart.textual_outline.expect
@@ -0,0 +1,5 @@
+extension E on int {
+ static String s = "Lily was here";
+}
+
+test() {}
diff --git a/pkg/front_end/testcases/general/issue49127.dart.textual_outline_modelled.expect b/pkg/front_end/testcases/general/issue49127.dart.textual_outline_modelled.expect
new file mode 100644
index 0000000..06deec8
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue49127.dart.textual_outline_modelled.expect
@@ -0,0 +1,5 @@
+extension E on int {
+ static String s = "Lily was here";
+}
+
+test() {}
diff --git a/pkg/front_end/testcases/general/issue49127.dart.weak.expect b/pkg/front_end/testcases/general/issue49127.dart.weak.expect
new file mode 100644
index 0000000..efafa32
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue49127.dart.weak.expect
@@ -0,0 +1,19 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/issue49127.dart:10:3: Warning: The extension 'E' cannot be null.
+// Try replacing '?.' with '.'
+// E?.s;
+// ^
+//
+import self as self;
+import "dart:core" as core;
+
+extension E on core::int {
+ static field s = self::E|s;
+}
+static field core::String E|s = "Lily was here";
+static method test() → dynamic {
+ self::E|s;
+}
diff --git a/pkg/front_end/testcases/general/issue49127.dart.weak.modular.expect b/pkg/front_end/testcases/general/issue49127.dart.weak.modular.expect
new file mode 100644
index 0000000..efafa32
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue49127.dart.weak.modular.expect
@@ -0,0 +1,19 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/issue49127.dart:10:3: Warning: The extension 'E' cannot be null.
+// Try replacing '?.' with '.'
+// E?.s;
+// ^
+//
+import self as self;
+import "dart:core" as core;
+
+extension E on core::int {
+ static field s = self::E|s;
+}
+static field core::String E|s = "Lily was here";
+static method test() → dynamic {
+ self::E|s;
+}
diff --git a/pkg/front_end/testcases/general/issue49127.dart.weak.outline.expect b/pkg/front_end/testcases/general/issue49127.dart.weak.outline.expect
new file mode 100644
index 0000000..ca2e74f
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue49127.dart.weak.outline.expect
@@ -0,0 +1,10 @@
+library /*isNonNullableByDefault*/;
+import self as self;
+import "dart:core" as core;
+
+extension E on core::int {
+ static field s = self::E|s;
+}
+static field core::String E|s;
+static method test() → dynamic
+ ;
diff --git a/pkg/front_end/testcases/general/issue49127.dart.weak.transformed.expect b/pkg/front_end/testcases/general/issue49127.dart.weak.transformed.expect
new file mode 100644
index 0000000..efafa32
--- /dev/null
+++ b/pkg/front_end/testcases/general/issue49127.dart.weak.transformed.expect
@@ -0,0 +1,19 @@
+library /*isNonNullableByDefault*/;
+//
+// Problems in library:
+//
+// pkg/front_end/testcases/general/issue49127.dart:10:3: Warning: The extension 'E' cannot be null.
+// Try replacing '?.' with '.'
+// E?.s;
+// ^
+//
+import self as self;
+import "dart:core" as core;
+
+extension E on core::int {
+ static field s = self::E|s;
+}
+static field core::String E|s = "Lily was here";
+static method test() → dynamic {
+ self::E|s;
+}
diff --git a/runtime/bin/console_posix.cc b/runtime/bin/console_posix.cc
index 17116da..f318ae1 100644
--- a/runtime/bin/console_posix.cc
+++ b/runtime/bin/console_posix.cc
@@ -18,71 +18,10 @@
namespace dart {
namespace bin {
-class PosixConsole {
- public:
- static const tcflag_t kInvalidFlag = -1;
-
- static void Initialize() {
- SaveMode(STDOUT_FILENO, &stdout_initial_c_lflag_);
- SaveMode(STDERR_FILENO, &stderr_initial_c_lflag_);
- SaveMode(STDIN_FILENO, &stdin_initial_c_lflag_);
- }
-
- static void Cleanup() {
- RestoreMode(STDOUT_FILENO, stdout_initial_c_lflag_);
- RestoreMode(STDERR_FILENO, stderr_initial_c_lflag_);
- RestoreMode(STDIN_FILENO, stdin_initial_c_lflag_);
- ClearLFlags();
- }
-
- private:
- static tcflag_t stdout_initial_c_lflag_;
- static tcflag_t stderr_initial_c_lflag_;
- static tcflag_t stdin_initial_c_lflag_;
-
- static void ClearLFlags() {
- stdout_initial_c_lflag_ = kInvalidFlag;
- stderr_initial_c_lflag_ = kInvalidFlag;
- stdin_initial_c_lflag_ = kInvalidFlag;
- }
-
- static void SaveMode(intptr_t fd, tcflag_t* flag) {
- ASSERT(flag != NULL);
- struct termios term;
- int status = TEMP_FAILURE_RETRY(tcgetattr(fd, &term));
- if (status != 0) {
- return;
- }
- *flag = term.c_lflag;
- }
-
- static void RestoreMode(intptr_t fd, tcflag_t flag) {
- if (flag == kInvalidFlag) {
- return;
- }
- struct termios term;
- int status = TEMP_FAILURE_RETRY(tcgetattr(fd, &term));
- if (status != 0) {
- return;
- }
- term.c_lflag = flag;
- VOID_TEMP_FAILURE_RETRY(tcsetattr(fd, TCSANOW, &term));
- }
-
- DISALLOW_ALLOCATION();
- DISALLOW_IMPLICIT_CONSTRUCTORS(PosixConsole);
-};
-
-tcflag_t PosixConsole::stdout_initial_c_lflag_ = PosixConsole::kInvalidFlag;
-tcflag_t PosixConsole::stderr_initial_c_lflag_ = PosixConsole::kInvalidFlag;
-tcflag_t PosixConsole::stdin_initial_c_lflag_ = PosixConsole::kInvalidFlag;
-
void Console::SaveConfig() {
- PosixConsole::Initialize();
}
void Console::RestoreConfig() {
- PosixConsole::Cleanup();
}
} // namespace bin
diff --git a/runtime/bin/console_win.cc b/runtime/bin/console_win.cc
index 59e6603..d714aa5 100644
--- a/runtime/bin/console_win.cc
+++ b/runtime/bin/console_win.cc
@@ -105,6 +105,10 @@
/// to reset the state when we cleanup.
if ((h != INVALID_HANDLE_VALUE) && GetConsoleMode(h, &mode)) {
old_mode = mode;
+ // No reason to restore the mode on exit if it was already desirable.
+ if ((mode & flags) == flags) {
+ return kInvalidFlag;
+ }
if (flags != 0) {
const DWORD request = mode | flags;
SetConsoleMode(h, request);
diff --git a/runtime/bin/io_natives.cc b/runtime/bin/io_natives.cc
index ea51935..002cd2c 100644
--- a/runtime/bin/io_natives.cc
+++ b/runtime/bin/io_natives.cc
@@ -176,6 +176,8 @@
V(Stdin_ReadByte, 1) \
V(Stdin_GetEchoMode, 1) \
V(Stdin_SetEchoMode, 2) \
+ V(Stdin_GetEchoNewlineMode, 1) \
+ V(Stdin_SetEchoNewlineMode, 2) \
V(Stdin_GetLineMode, 1) \
V(Stdin_SetLineMode, 2) \
V(Stdin_AnsiSupported, 1) \
diff --git a/runtime/bin/stdio.cc b/runtime/bin/stdio.cc
index d833ba4..07ea704 100644
--- a/runtime/bin/stdio.cc
+++ b/runtime/bin/stdio.cc
@@ -85,6 +85,39 @@
}
}
+void FUNCTION_NAME(Stdin_GetEchoNewlineMode)(Dart_NativeArguments args) {
+ bool enabled = false;
+ intptr_t fd;
+ if (!GetIntptrArgument(args, 0, &fd)) {
+ return;
+ }
+ if (Stdin::GetEchoNewlineMode(fd, &enabled)) {
+ Dart_SetBooleanReturnValue(args, enabled);
+ } else {
+ Dart_SetReturnValue(args, DartUtils::NewDartOSError());
+ }
+}
+
+void FUNCTION_NAME(Stdin_SetEchoNewlineMode)(Dart_NativeArguments args) {
+ intptr_t fd;
+ if (!GetIntptrArgument(args, 0, &fd)) {
+ return;
+ }
+ bool enabled;
+ Dart_Handle status = Dart_GetNativeBooleanArgument(args, 1, &enabled);
+ if (Dart_IsError(status)) {
+ // The caller is expecting an OSError if something goes wrong.
+ OSError os_error(-1, "Invalid argument", OSError::kUnknown);
+ Dart_SetReturnValue(args, DartUtils::NewDartOSError(&os_error));
+ return;
+ }
+ if (Stdin::SetEchoNewlineMode(fd, enabled)) {
+ Dart_SetReturnValue(args, Dart_True());
+ } else {
+ Dart_SetReturnValue(args, DartUtils::NewDartOSError());
+ }
+}
+
void FUNCTION_NAME(Stdin_GetLineMode)(Dart_NativeArguments args) {
bool enabled = false;
intptr_t fd;
diff --git a/runtime/bin/stdio.h b/runtime/bin/stdio.h
index 3bbc019..e5796d5 100644
--- a/runtime/bin/stdio.h
+++ b/runtime/bin/stdio.h
@@ -20,6 +20,9 @@
static bool GetEchoMode(intptr_t fd, bool* enabled);
static bool SetEchoMode(intptr_t fd, bool enabled);
+ static bool GetEchoNewlineMode(intptr_t fd, bool* enabled);
+ static bool SetEchoNewlineMode(intptr_t fd, bool enabled);
+
static bool GetLineMode(intptr_t fd, bool* enabled);
static bool SetLineMode(intptr_t fd, bool enabled);
diff --git a/runtime/bin/stdio_android.cc b/runtime/bin/stdio_android.cc
index 1afb34f..e620e1f 100644
--- a/runtime/bin/stdio_android.cc
+++ b/runtime/bin/stdio_android.cc
@@ -44,9 +44,34 @@
return false;
}
if (enabled) {
- term.c_lflag |= (ECHO | ECHONL);
+ term.c_lflag |= ECHO;
} else {
- term.c_lflag &= ~(ECHO | ECHONL);
+ term.c_lflag &= ~(ECHO);
+ }
+ status = NO_RETRY_EXPECTED(tcsetattr(fd, TCSANOW, &term));
+ return (status == 0);
+}
+
+bool Stdin::GetEchoNewlineMode(intptr_t fd, bool* enabled) {
+ struct termios term;
+ int status = NO_RETRY_EXPECTED(tcgetattr(fd, &term));
+ if (status != 0) {
+ return false;
+ }
+ *enabled = ((term.c_lflag & ECHONL) != 0);
+ return true;
+}
+
+bool Stdin::SetEchoNewlineMode(intptr_t fd, bool enabled) {
+ struct termios term;
+ int status = NO_RETRY_EXPECTED(tcgetattr(fd, &term));
+ if (status != 0) {
+ return false;
+ }
+ if (enabled) {
+ term.c_lflag |= ECHONL;
+ } else {
+ term.c_lflag &= ~(ECHONL);
}
status = NO_RETRY_EXPECTED(tcsetattr(fd, TCSANOW, &term));
return (status == 0);
diff --git a/runtime/bin/stdio_fuchsia.cc b/runtime/bin/stdio_fuchsia.cc
index 2f91a9c..4d84b48 100644
--- a/runtime/bin/stdio_fuchsia.cc
+++ b/runtime/bin/stdio_fuchsia.cc
@@ -34,6 +34,16 @@
return false;
}
+bool Stdin::GetEchoNewlineMode(intptr_t fd, bool* enabled) {
+ errno = ENOSYS;
+ return false;
+}
+
+bool Stdin::SetEchoNewlineMode(intptr_t fd, bool enabled) {
+ errno = ENOSYS;
+ return false;
+}
+
bool Stdin::GetLineMode(intptr_t fd, bool* enabled) {
errno = ENOSYS;
return false;
diff --git a/runtime/bin/stdio_linux.cc b/runtime/bin/stdio_linux.cc
index 17db9bd..4acddde 100644
--- a/runtime/bin/stdio_linux.cc
+++ b/runtime/bin/stdio_linux.cc
@@ -44,9 +44,34 @@
return false;
}
if (enabled) {
- term.c_lflag |= (ECHO | ECHONL);
+ term.c_lflag |= ECHO;
} else {
- term.c_lflag &= ~(ECHO | ECHONL);
+ term.c_lflag &= ~(ECHO);
+ }
+ status = NO_RETRY_EXPECTED(tcsetattr(fd, TCSANOW, &term));
+ return (status == 0);
+}
+
+bool Stdin::GetEchoNewlineMode(intptr_t fd, bool* enabled) {
+ struct termios term;
+ int status = NO_RETRY_EXPECTED(tcgetattr(fd, &term));
+ if (status != 0) {
+ return false;
+ }
+ *enabled = ((term.c_lflag & ECHONL) != 0);
+ return true;
+}
+
+bool Stdin::SetEchoNewlineMode(intptr_t fd, bool enabled) {
+ struct termios term;
+ int status = NO_RETRY_EXPECTED(tcgetattr(fd, &term));
+ if (status != 0) {
+ return false;
+ }
+ if (enabled) {
+ term.c_lflag |= ECHONL;
+ } else {
+ term.c_lflag &= ~(ECHONL);
}
status = NO_RETRY_EXPECTED(tcsetattr(fd, TCSANOW, &term));
return (status == 0);
diff --git a/runtime/bin/stdio_macos.cc b/runtime/bin/stdio_macos.cc
index 969b75d..202b169 100644
--- a/runtime/bin/stdio_macos.cc
+++ b/runtime/bin/stdio_macos.cc
@@ -44,9 +44,34 @@
return false;
}
if (enabled) {
- term.c_lflag |= (ECHO | ECHONL);
+ term.c_lflag |= ECHO;
} else {
- term.c_lflag &= ~(ECHO | ECHONL);
+ term.c_lflag &= ~(ECHO);
+ }
+ status = NO_RETRY_EXPECTED(tcsetattr(fd, TCSANOW, &term));
+ return (status == 0);
+}
+
+bool Stdin::GetEchoNewlineMode(intptr_t fd, bool* enabled) {
+ struct termios term;
+ int status = NO_RETRY_EXPECTED(tcgetattr(fd, &term));
+ if (status != 0) {
+ return false;
+ }
+ *enabled = ((term.c_lflag & ECHONL) != 0);
+ return true;
+}
+
+bool Stdin::SetEchoNewlineMode(intptr_t fd, bool enabled) {
+ struct termios term;
+ int status = NO_RETRY_EXPECTED(tcgetattr(fd, &term));
+ if (status != 0) {
+ return false;
+ }
+ if (enabled) {
+ term.c_lflag |= ECHONL;
+ } else {
+ term.c_lflag &= ~(ECHONL);
}
status = NO_RETRY_EXPECTED(tcsetattr(fd, TCSANOW, &term));
return (status == 0);
diff --git a/runtime/bin/stdio_win.cc b/runtime/bin/stdio_win.cc
index dc65cba..105b611 100644
--- a/runtime/bin/stdio_win.cc
+++ b/runtime/bin/stdio_win.cc
@@ -56,6 +56,19 @@
return SetConsoleMode(h, mode);
}
+bool Stdin::GetEchoNewlineMode(intptr_t fd, bool* enabled) {
+ *enabled = false;
+ return true;
+}
+
+bool Stdin::SetEchoNewlineMode(intptr_t fd, bool enabled) {
+ if (enabled) {
+ SetLastError(ERROR_NOT_CAPABLE);
+ return false;
+ }
+ return true;
+}
+
bool Stdin::GetLineMode(intptr_t fd, bool* enabled) {
HANDLE h = GetStdHandle(STD_INPUT_HANDLE);
DWORD mode;
diff --git a/sdk/lib/_internal/js_dev_runtime/patch/io_patch.dart b/sdk/lib/_internal/js_dev_runtime/patch/io_patch.dart
index a70800f..3be8a09 100644
--- a/sdk/lib/_internal/js_dev_runtime/patch/io_patch.dart
+++ b/sdk/lib/_internal/js_dev_runtime/patch/io_patch.dart
@@ -688,6 +688,16 @@
}
@patch
+ bool get echoNewlineMode {
+ throw UnsupportedError("Stdin.echoNewlineMode");
+ }
+
+ @patch
+ void set echoNewlineMode(bool enabled) {
+ throw UnsupportedError("Stdin.echoNewlineMode");
+ }
+
+ @patch
bool get lineMode {
throw UnsupportedError("Stdin.lineMode");
}
diff --git a/sdk/lib/_internal/js_runtime/lib/io_patch.dart b/sdk/lib/_internal/js_runtime/lib/io_patch.dart
index e22aae2..f795f7c 100644
--- a/sdk/lib/_internal/js_runtime/lib/io_patch.dart
+++ b/sdk/lib/_internal/js_runtime/lib/io_patch.dart
@@ -688,6 +688,16 @@
}
@patch
+ bool get echoNewlineMode {
+ throw UnsupportedError("Stdin.echoNewlineMode");
+ }
+
+ @patch
+ void set echoNewlineMode(bool enabled) {
+ throw UnsupportedError("Stdin.echoNewlineMode");
+ }
+
+ @patch
bool get lineMode {
throw new UnsupportedError("Stdin.lineMode");
}
diff --git a/sdk/lib/_internal/vm/bin/stdio_patch.dart b/sdk/lib/_internal/vm/bin/stdio_patch.dart
index 6a6bd2c..680bf44 100644
--- a/sdk/lib/_internal/vm/bin/stdio_patch.dart
+++ b/sdk/lib/_internal/vm/bin/stdio_patch.dart
@@ -87,6 +87,29 @@
}
@patch
+ bool get echoNewlineMode {
+ var result = _echoNewlineMode(_fd);
+ if (result is OSError) {
+ throw new StdinException(
+ "Error getting terminal echo newline mode", result);
+ }
+ return result;
+ }
+
+ @patch
+ void set echoNewlineMode(bool enabled) {
+ if (!_EmbedderConfig._maySetEchoNewlineMode) {
+ throw new UnsupportedError(
+ "This embedder disallows setting Stdin.echoNewlineMode");
+ }
+ var result = _setEchoNewlineMode(_fd, enabled);
+ if (result is OSError) {
+ throw new StdinException(
+ "Error setting terminal echo newline mode", result);
+ }
+ }
+
+ @patch
bool get lineMode {
var result = _lineMode(_fd);
if (result is OSError) {
@@ -120,6 +143,10 @@
external static _echoMode(int fd);
@pragma("vm:external-name", "Stdin_SetEchoMode")
external static _setEchoMode(int fd, bool enabled);
+ @pragma("vm:external-name", "Stdin_GetEchoNewlineMode")
+ external static _echoNewlineMode(int fd);
+ @pragma("vm:external-name", "Stdin_SetEchoNewlineMode")
+ external static _setEchoNewlineMode(int fd, bool enabled);
@pragma("vm:external-name", "Stdin_GetLineMode")
external static _lineMode(int fd);
@pragma("vm:external-name", "Stdin_SetLineMode")
diff --git a/sdk/lib/io/embedder_config.dart b/sdk/lib/io/embedder_config.dart
index fd5bb24..9b934a6 100644
--- a/sdk/lib/io/embedder_config.dart
+++ b/sdk/lib/io/embedder_config.dart
@@ -24,6 +24,10 @@
@pragma('vm:entry-point')
static bool _maySetEchoMode = true;
+ // Whether the isolate may set [Stdin.echoNewlineMode].
+ @pragma('vm:entry-point')
+ static bool _maySetEchoNewlineMode = true;
+
// Whether the isolate may set [Stdin.lineMode].
@pragma('vm:entry-point')
static bool _maySetLineMode = true;
diff --git a/sdk/lib/io/stdio.dart b/sdk/lib/io/stdio.dart
index dcaf1d8..3c8e355 100644
--- a/sdk/lib/io/stdio.dart
+++ b/sdk/lib/io/stdio.dart
@@ -112,14 +112,34 @@
/// Whether echo mode is enabled on [stdin].
///
- /// If disabled, input from to console will not be echoed.
+ /// If disabled, input from the console will not be echoed.
///
/// Default depends on the parent process, but is usually enabled.
///
+ /// On POSIX systems this mode is the `echo` local terminal mode. Before
+ /// Dart 2.18, it also controlled the `echonl` mode, which is now controlled
+ /// by [echoNewlineMode].
+ ///
/// On Windows this mode can only be enabled if [lineMode] is enabled as well.
external bool get echoMode;
external set echoMode(bool echoMode);
+ /// Whether echo newline mode is enabled on [stdin].
+ ///
+ /// If enabled, newlines from the terminal will be echoed even if the regular
+ /// [echoMode] is disabled. This mode may require `lineMode` to be turned on
+ /// to have an effect.
+ ///
+ /// Default depends on the parent process, but is usually disabled.
+ ///
+ /// On POSIX systems this mode is the `echonl` local terminal mode.
+ ///
+ /// On Windows this mode cannot be set.
+ @Since("2.18")
+ external bool get echoNewlineMode;
+ @Since("2.18")
+ external set echoNewlineMode(bool echoNewlineMode);
+
/// Whether line mode is enabled on [stdin].
///
/// If enabled, characters are delayed until a newline character is entered.
@@ -127,7 +147,10 @@
///
/// Default depends on the parent process, but is usually enabled.
///
- /// On Windows this mode can only be disabled if [echoMode] is disabled as well.
+ /// On POSIX systems this mode is the `icanon` local terminal mode.
+ ///
+ /// On Windows this mode can only be disabled if [echoMode] is disabled as
+ /// well.
external bool get lineMode;
external set lineMode(bool lineMode);
diff --git a/tests/standalone/io/io_override_test.dart b/tests/standalone/io/io_override_test.dart
index 4fe9c66..7acca81 100644
--- a/tests/standalone/io/io_override_test.dart
+++ b/tests/standalone/io/io_override_test.dart
@@ -177,6 +177,7 @@
class StdinMock extends Stream<List<int>> implements Stdin {
bool echoMode = false;
+ bool echoNewlineMode = false;
bool lineMode = false;
bool get hasTerminal => throw "";
bool get supportsAnsiEscapes => throw "";
diff --git a/tests/standalone_2/io/io_override_test.dart b/tests/standalone_2/io/io_override_test.dart
index e7d9a81..e99f611 100644
--- a/tests/standalone_2/io/io_override_test.dart
+++ b/tests/standalone_2/io/io_override_test.dart
@@ -177,6 +177,7 @@
class StdinMock extends Stream<List<int>> implements Stdin {
bool echoMode = false;
+ bool echoNewlineMode = false;
bool lineMode = false;
bool get hasTerminal => throw "";
bool get supportsAnsiEscapes => throw "";
diff --git a/tools/VERSION b/tools/VERSION
index f0ba236..3ea86a6 100644
--- a/tools/VERSION
+++ b/tools/VERSION
@@ -27,5 +27,5 @@
MAJOR 2
MINOR 18
PATCH 0
-PRERELEASE 155
+PRERELEASE 156
PRERELEASE_PATCH 0
\ No newline at end of file