You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@beam.apache.org by pa...@apache.org on 2022/11/15 16:04:08 UTC

[beam] branch master updated: Configure flutter_code_editor options with Hugo shortcode (#23926) (#24031)

This is an automated email from the ASF dual-hosted git repository.

pabloem pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/beam.git


The following commit(s) were added to refs/heads/master by this push:
     new f349f41010c Configure flutter_code_editor options with Hugo shortcode (#23926) (#24031)
f349f41010c is described below

commit f349f41010c5b238ff6020f7de718f938eef3c5e
Author: alexeyinkin <al...@akvelon.com>
AuthorDate: Tue Nov 15 20:04:01 2022 +0400

    Configure flutter_code_editor options with Hugo shortcode (#23926) (#24031)
    
    * Configure flutter_code_editor options with Hugo shortcode (#23926)
    
    * Minor fixes (#23926)
    
    * Refactor after review (#23926)
---
 learning/tour-of-beam/frontend/pubspec.lock        |   2 +-
 .../example_list/example_item_actions.dart         |   4 +-
 .../examples_loading_descriptor_factory.dart       |  35 +++-
 .../examples/models/example_token_type.dart        |   7 +
 .../lib/playground_components.dart                 |   2 +
 .../example_loaders/empty_example_loader.dart      |   8 +-
 .../example_loaders/examples_loader.dart           |   2 +
 ...xample_loader.dart => http_example_loader.dart} |  35 ++--
 .../example_loaders/standard_example_loader.dart   |   4 +-
 .../lib/src/controllers/playground_controller.dart |   1 +
 .../controllers/snippet_editing_controller.dart    |  44 +++-
 .../lib/src/enums/complexity.dart                  |   9 +-
 .../lib/src/models/example.dart                    |  44 ++--
 .../lib/src/models/example_base.dart               |  28 +--
 .../content_example_loading_descriptor.dart        |  11 +-
 .../example_loading_descriptor.dart                |   8 +-
 ...r.dart => http_example_loading_descriptor.dart} |  18 +-
 .../standard_example_loading_descriptor.dart       |   6 +-
 .../user_shared_example_loading_descriptor.dart    |   6 +-
 .../lib/src/models/example_view_options.dart       |  72 +++++++
 .../repositories/complexity_grpc_extension.dart    |   4 +-
 .../repositories/models/get_snippet_response.dart  |   8 +-
 .../string.dart}                                   |  10 +-
 .../lib/src/widgets/editor_textarea.dart           |  13 +-
 .../frontend/playground_components/pubspec.yaml    |   3 +-
 .../example_loaders/http_example_loader_test.dart  |  58 ++++++
 playground/frontend/pubspec.lock                   |   4 +-
 .../examples_loading_descriptor_factory_test.dart  | 224 +++++++++++++++++++++
 .../examples/models/example_token_type_test.dart   |  48 +++++
 .../www/site/layouts/shortcodes/playground.html    |  33 ++-
 .../layouts/shortcodes/playground_snippet.html     |  36 +++-
 31 files changed, 660 insertions(+), 127 deletions(-)

diff --git a/learning/tour-of-beam/frontend/pubspec.lock b/learning/tour-of-beam/frontend/pubspec.lock
index 9983b9bb530..e1ed198ef56 100644
--- a/learning/tour-of-beam/frontend/pubspec.lock
+++ b/learning/tour-of-beam/frontend/pubspec.lock
@@ -278,7 +278,7 @@ packages:
       name: flutter_code_editor
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.1.1"
+    version: "0.1.4"
   flutter_driver:
     dependency: transitive
     description: flutter
diff --git a/playground/frontend/lib/modules/examples/components/example_list/example_item_actions.dart b/playground/frontend/lib/modules/examples/components/example_list/example_item_actions.dart
index d02e27eec61..30b67fa0edd 100644
--- a/playground/frontend/lib/modules/examples/components/example_list/example_item_actions.dart
+++ b/playground/frontend/lib/modules/examples/components/example_list/example_item_actions.dart
@@ -36,8 +36,8 @@ class ExampleItemActions extends StatelessWidget {
     return Row(
       children: [
         if (example.isMultiFile) multifilePopover,
-        if (example.complexity != Complexity.unspecified)
-          ComplexityWidget(complexity: example.complexity),
+        if (example.complexity != null)
+          ComplexityWidget(complexity: example.complexity!),
         descriptionPopover,
       ],
     );
diff --git a/playground/frontend/lib/modules/examples/models/example_loading_descriptors/examples_loading_descriptor_factory.dart b/playground/frontend/lib/modules/examples/models/example_loading_descriptors/examples_loading_descriptor_factory.dart
index 7b3854d6f31..a73b16d7cfd 100644
--- a/playground/frontend/lib/modules/examples/models/example_loading_descriptors/examples_loading_descriptor_factory.dart
+++ b/playground/frontend/lib/modules/examples/models/example_loading_descriptors/examples_loading_descriptor_factory.dart
@@ -18,6 +18,7 @@
 
 import 'dart:convert';
 
+import 'package:flutter/foundation.dart';
 import 'package:playground/constants/params.dart';
 import 'package:playground/modules/examples/models/example_token_type.dart';
 import 'package:playground_components/playground_components.dart';
@@ -115,7 +116,7 @@ class ExamplesLoadingDescriptorFactory {
       return null;
     }
 
-    return _parseSingleExample(token);
+    return _parseSingleExample(token, params);
   }
 
   static ExamplesLoadingDescriptor? _tryParseOfCatalogDefaultExamples(
@@ -135,15 +136,32 @@ class ExamplesLoadingDescriptorFactory {
     );
   }
 
-  static ExampleLoadingDescriptor _parseSingleExample(String token) {
+  static ExampleLoadingDescriptor _parseSingleExample(
+    String token,
+    Map<String, dynamic> params,
+  ) {
+    final viewOptions = ExampleViewOptions.fromShortMap(params);
     final tokenType = ExampleTokenType.fromToken(token);
 
     switch (tokenType) {
+      case ExampleTokenType.http:
+        return HttpExampleLoadingDescriptor(
+          sdk: Sdk.parseOrCreate(params['sdk']),
+          uri: Uri.parse(token),
+          viewOptions: viewOptions,
+        );
+
       case ExampleTokenType.standard:
-        return StandardExampleLoadingDescriptor(path: token);
+        return StandardExampleLoadingDescriptor(
+          path: token,
+          viewOptions: viewOptions,
+        );
 
       case ExampleTokenType.userShared:
-        return UserSharedExampleLoadingDescriptor(snippetId: token);
+        return UserSharedExampleLoadingDescriptor(
+          snippetId: token,
+          viewOptions: viewOptions,
+        );
     }
   }
 
@@ -166,22 +184,23 @@ class ExamplesLoadingDescriptorFactory {
       return _emptyLazyLoadDescriptors;
     }
 
-    return _defaultLazyLoadDescriptors;
+    return defaultLazyLoadDescriptors;
   }
 
   static Map<Sdk, List<ExampleLoadingDescriptor>>
       get _emptyLazyLoadDescriptors {
     return {
       for (final sdk in Sdk.known)
-        sdk: [EmptyExampleLoadingDescriptor(sdk: sdk)]
+        sdk: [EmptyExampleLoadingDescriptor(sdk: sdk)],
     };
   }
 
+  @visibleForTesting
   static Map<Sdk, List<ExampleLoadingDescriptor>>
-      get _defaultLazyLoadDescriptors {
+      get defaultLazyLoadDescriptors {
     return {
       for (final sdk in Sdk.known)
-        sdk: [CatalogDefaultExampleLoadingDescriptor(sdk: sdk)]
+        sdk: [CatalogDefaultExampleLoadingDescriptor(sdk: sdk)],
     };
   }
 }
diff --git a/playground/frontend/lib/modules/examples/models/example_token_type.dart b/playground/frontend/lib/modules/examples/models/example_token_type.dart
index 48c7c580635..ee274563fae 100644
--- a/playground/frontend/lib/modules/examples/models/example_token_type.dart
+++ b/playground/frontend/lib/modules/examples/models/example_token_type.dart
@@ -19,11 +19,18 @@
 import 'package:playground_components/playground_components.dart';
 
 enum ExampleTokenType {
+  /// A URI to load the plain content from.
+  http,
+
   standard,
   userShared,
   ;
 
   static ExampleTokenType fromToken(String token) {
+    if (token.startsWith(RegExp('http(s)?://'))) {
+      return http;
+    }
+
     final sdk = Sdk.tryParseExamplePath(token);
     if (sdk != null) {
       return standard;
diff --git a/playground/frontend/playground_components/lib/playground_components.dart b/playground/frontend/playground_components/lib/playground_components.dart
index 24837c8fc0b..c738710bde7 100644
--- a/playground/frontend/playground_components/lib/playground_components.dart
+++ b/playground/frontend/playground_components/lib/playground_components.dart
@@ -36,8 +36,10 @@ export 'src/models/example_loading_descriptors/content_example_loading_descripto
 export 'src/models/example_loading_descriptors/empty_example_loading_descriptor.dart';
 export 'src/models/example_loading_descriptors/example_loading_descriptor.dart';
 export 'src/models/example_loading_descriptors/examples_loading_descriptor.dart';
+export 'src/models/example_loading_descriptors/http_example_loading_descriptor.dart';
 export 'src/models/example_loading_descriptors/standard_example_loading_descriptor.dart';
 export 'src/models/example_loading_descriptors/user_shared_example_loading_descriptor.dart';
+export 'src/models/example_view_options.dart';
 export 'src/models/intents.dart';
 export 'src/models/outputs.dart';
 export 'src/models/sdk.dart';
diff --git a/playground/frontend/playground_components/lib/src/controllers/example_loaders/empty_example_loader.dart b/playground/frontend/playground_components/lib/src/controllers/example_loaders/empty_example_loader.dart
index faef4c0098f..a2d178c5fcd 100644
--- a/playground/frontend/playground_components/lib/src/controllers/example_loaders/empty_example_loader.dart
+++ b/playground/frontend/playground_components/lib/src/controllers/example_loaders/empty_example_loader.dart
@@ -17,7 +17,6 @@
  */
 
 import '../../cache/example_cache.dart';
-import '../../enums/complexity.dart';
 import '../../models/example.dart';
 import '../../models/example_base.dart';
 import '../../models/example_loading_descriptors/empty_example_loading_descriptor.dart';
@@ -34,14 +33,11 @@ class EmptyExampleLoader extends ExampleLoader {
 
   @override
   Future<Example> get future async => Example(
-        sdk: descriptor.sdk,
         name: 'Embedded_Example',
         path: '',
-        description: '',
+        sdk: descriptor.sdk,
+        source: '',
         tags: [],
         type: ExampleType.example,
-        source: '',
-        pipelineOptions: '',
-        complexity: Complexity.unspecified,
       );
 }
diff --git a/playground/frontend/playground_components/lib/src/controllers/example_loaders/examples_loader.dart b/playground/frontend/playground_components/lib/src/controllers/example_loaders/examples_loader.dart
index 6d872e67751..d2b1799f02b 100644
--- a/playground/frontend/playground_components/lib/src/controllers/example_loaders/examples_loader.dart
+++ b/playground/frontend/playground_components/lib/src/controllers/example_loaders/examples_loader.dart
@@ -26,6 +26,7 @@ import 'catalog_default_example_loader.dart';
 import 'content_example_loader.dart';
 import 'empty_example_loader.dart';
 import 'example_loader_factory.dart';
+import 'http_example_loader.dart';
 import 'standard_example_loader.dart';
 import 'user_shared_example_loader.dart';
 
@@ -38,6 +39,7 @@ class ExamplesLoader {
     defaultFactory.add(CatalogDefaultExampleLoader.new);
     defaultFactory.add(ContentExampleLoader.new);
     defaultFactory.add(EmptyExampleLoader.new);
+    defaultFactory.add(HttpExampleLoader.new);
     defaultFactory.add(StandardExampleLoader.new);
     defaultFactory.add(UserSharedExampleLoader.new);
   }
diff --git a/playground/frontend/playground_components/lib/src/controllers/example_loaders/empty_example_loader.dart b/playground/frontend/playground_components/lib/src/controllers/example_loaders/http_example_loader.dart
similarity index 63%
copy from playground/frontend/playground_components/lib/src/controllers/example_loaders/empty_example_loader.dart
copy to playground/frontend/playground_components/lib/src/controllers/example_loaders/http_example_loader.dart
index faef4c0098f..b36fa2ef99d 100644
--- a/playground/frontend/playground_components/lib/src/controllers/example_loaders/empty_example_loader.dart
+++ b/playground/frontend/playground_components/lib/src/controllers/example_loaders/http_example_loader.dart
@@ -16,32 +16,35 @@
  * limitations under the License.
  */
 
+import 'package:collection/collection.dart';
+import 'package:http/http.dart' as http;
+
 import '../../cache/example_cache.dart';
-import '../../enums/complexity.dart';
 import '../../models/example.dart';
 import '../../models/example_base.dart';
-import '../../models/example_loading_descriptors/empty_example_loading_descriptor.dart';
+import '../../models/example_loading_descriptors/http_example_loading_descriptor.dart';
 import 'example_loader.dart';
 
-class EmptyExampleLoader extends ExampleLoader {
-  final EmptyExampleLoadingDescriptor descriptor;
+class HttpExampleLoader extends ExampleLoader {
+  final HttpExampleLoadingDescriptor descriptor;
 
-  const EmptyExampleLoader({
+  const HttpExampleLoader({
     required this.descriptor,
     // TODO(alexeyinkin): Remove when this lands: https://github.com/dart-lang/language/issues/1813
     required ExampleCache exampleCache,
   });
 
   @override
-  Future<Example> get future async => Example(
-        sdk: descriptor.sdk,
-        name: 'Embedded_Example',
-        path: '',
-        description: '',
-        tags: [],
-        type: ExampleType.example,
-        source: '',
-        pipelineOptions: '',
-        complexity: Complexity.unspecified,
-      );
+  Future<Example> get future async {
+    final response = await http.get(descriptor.uri);
+
+    return Example(
+      name: descriptor.uri.path.split('/').lastOrNull ?? 'HTTP Example',
+      path: descriptor.uri.toString(),
+      sdk: descriptor.sdk,
+      source: response.body,
+      type: ExampleType.example,
+      viewOptions: descriptor.viewOptions,
+    );
+  }
 }
diff --git a/playground/frontend/playground_components/lib/src/controllers/example_loaders/standard_example_loader.dart b/playground/frontend/playground_components/lib/src/controllers/example_loaders/standard_example_loader.dart
index 23c429ebab1..83e3c5c46ba 100644
--- a/playground/frontend/playground_components/lib/src/controllers/example_loaders/standard_example_loader.dart
+++ b/playground/frontend/playground_components/lib/src/controllers/example_loaders/standard_example_loader.dart
@@ -27,7 +27,7 @@ import 'example_loader.dart';
 
 /// Loads a given example from the local cache, then adds info from network.
 ///
-/// This loader assumes that [ExampleState] is loading all examples to
+/// This loader assumes that [ExampleCache] is loading all examples to
 /// its cache. So it only completes if this is successful.
 class StandardExampleLoader extends ExampleLoader {
   final StandardExampleLoadingDescriptor descriptor;
@@ -41,7 +41,7 @@ class StandardExampleLoader extends ExampleLoader {
     required this.descriptor,
     required this.exampleCache,
   }) {
-    _load();
+    unawaited(_load());
   }
 
   Future<void> _load() async {
diff --git a/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart b/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart
index f3f1f52ba09..2937c2c5eb3 100644
--- a/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart
+++ b/playground/frontend/playground_components/lib/src/controllers/playground_controller.dart
@@ -175,6 +175,7 @@ class PlaygroundController with ChangeNotifier {
     }
   }
 
+  // TODO(alexeyinkin): Remove, used only in tests, refactor them.
   void setSource(String source) {
     final controller = requireSnippetEditingController();
     controller.setSource(source);
diff --git a/playground/frontend/playground_components/lib/src/controllers/snippet_editing_controller.dart b/playground/frontend/playground_components/lib/src/controllers/snippet_editing_controller.dart
index 8bb285eff42..7ed145e3ac7 100644
--- a/playground/frontend/playground_components/lib/src/controllers/snippet_editing_controller.dart
+++ b/playground/frontend/playground_components/lib/src/controllers/snippet_editing_controller.dart
@@ -19,36 +19,57 @@
 import 'package:flutter/widgets.dart';
 import 'package:flutter_code_editor/flutter_code_editor.dart';
 
-import '../enums/complexity.dart';
 import '../models/example.dart';
 import '../models/example_loading_descriptors/content_example_loading_descriptor.dart';
 import '../models/example_loading_descriptors/example_loading_descriptor.dart';
+import '../models/example_view_options.dart';
 import '../models/sdk.dart';
 
 class SnippetEditingController extends ChangeNotifier {
   final Sdk sdk;
   final CodeController codeController;
   Example? _selectedExample;
-  String _pipelineOptions;
+  String _pipelineOptions = '';
 
   SnippetEditingController({
     required this.sdk,
-    Example? selectedExample,
-    String pipelineOptions = '',
-  })  : codeController = CodeController(
+  }) : codeController = CodeController(
           language: sdk.highlightMode,
+          namedSectionParser: const BracketsStartEndNamedSectionParser(),
           webSpaceFix: false,
-        ),
-        _selectedExample = selectedExample,
-        _pipelineOptions = pipelineOptions;
+        );
 
   set selectedExample(Example? value) {
     _selectedExample = value;
     setSource(_selectedExample?.source ?? '');
+
+    final viewOptions = value?.viewOptions;
+    if (viewOptions != null) {
+      _applyViewOptions(viewOptions);
+    }
+
     _pipelineOptions = _selectedExample?.pipelineOptions ?? '';
     notifyListeners();
   }
 
+  void _applyViewOptions(ExampleViewOptions options) {
+    codeController.readOnlySectionNames = options.readOnlySectionNames.toSet();
+    codeController.visibleSectionNames = options.showSectionNames.toSet();
+
+    if (options.foldCommentAtLineZero) {
+      codeController.foldCommentAtLineZero();
+    }
+
+    if (options.foldImports) {
+      codeController.foldImports();
+    }
+
+    final unfolded = options.unfoldSectionNames;
+    if (unfolded.isNotEmpty) {
+      codeController.foldOutsideSections(unfolded);
+    }
+  }
+
   Example? get selectedExample => _selectedExample;
 
   set pipelineOptions(String value) {
@@ -82,15 +103,18 @@ class SnippetEditingController extends ChangeNotifier {
     //  user-shared examples, and an empty editor,
     //  https://github.com/apache/beam/issues/23252
     return ContentExampleLoadingDescriptor(
+      complexity: _selectedExample?.complexity,
       content: codeController.fullText,
       name: _selectedExample?.name,
-      complexity: _selectedExample?.complexity ?? Complexity.unspecified,
       sdk: sdk,
     );
   }
 
   void setSource(String source) {
-    codeController.text = source;
+    codeController.readOnlySectionNames = const {};
+    codeController.visibleSectionNames = const {};
+
+    codeController.fullText = source;
     codeController.historyController.deleteHistory();
   }
 }
diff --git a/playground/frontend/playground_components/lib/src/enums/complexity.dart b/playground/frontend/playground_components/lib/src/enums/complexity.dart
index e8e675b34ff..59da4941087 100644
--- a/playground/frontend/playground_components/lib/src/enums/complexity.dart
+++ b/playground/frontend/playground_components/lib/src/enums/complexity.dart
@@ -19,8 +19,6 @@
 import 'package:json_annotation/json_annotation.dart';
 
 enum Complexity {
-  @JsonValue('UNSPECIFIED')
-  unspecified,
   @JsonValue('BASIC')
   basic,
   @JsonValue('MEDIUM')
@@ -29,7 +27,7 @@ enum Complexity {
   advanced,
   ;
 
-  static Complexity fromString(String complexity) {
+  static Complexity? fromString(String? complexity) {
     switch (complexity) {
       case 'basic':
         return Complexity.basic;
@@ -37,9 +35,8 @@ enum Complexity {
         return Complexity.medium;
       case 'advanced':
         return Complexity.advanced;
-      case 'unspecified':
-        return Complexity.unspecified;
     }
-    throw Exception('Unknown complexity: $complexity');
+
+    return null;
   }
 }
diff --git a/playground/frontend/playground_components/lib/src/models/example.dart b/playground/frontend/playground_components/lib/src/models/example.dart
index 2654ecb6461..33e2b65a973 100644
--- a/playground/frontend/playground_components/lib/src/models/example.dart
+++ b/playground/frontend/playground_components/lib/src/models/example.dart
@@ -20,46 +20,48 @@ import 'example_base.dart';
 
 /// A [ExampleBase] that also has all large fields fetched.
 class Example extends ExampleBase {
-  final String source;
-  final String? outputs;
-  final String? logs;
   final String? graph;
+  final String? logs;
+  final String? outputs;
+  final String source;
 
   const Example({
+    required this.source,
+    required super.name,
     required super.sdk,
-    required super.tags,
     required super.type,
-    required super.name,
     required super.path,
-    required super.description,
+    this.graph,
+    this.logs,
+    this.outputs,
+    super.complexity,
     super.contextLine,
+    super.description,
     super.isMultiFile,
     super.link,
-    required super.pipelineOptions,
-    required this.source,
-    this.outputs,
-    this.logs,
-    this.graph,
-    required super.complexity,
+    super.pipelineOptions,
+    super.tags,
+    super.viewOptions,
   });
 
   Example.fromBase(
     ExampleBase example, {
-    required this.source,
-    required this.outputs,
     required this.logs,
+    required this.outputs,
+    required this.source,
     this.graph,
   }) : super(
-          sdk: example.sdk,
-          name: example.name,
-          path: example.path,
-          description: example.description,
-          tags: example.tags,
-          type: example.type,
+          complexity: example.complexity,
           contextLine: example.contextLine,
+          description: example.description,
           isMultiFile: example.isMultiFile,
           link: example.link,
+          name: example.name,
+          path: example.path,
           pipelineOptions: example.pipelineOptions,
-          complexity: example.complexity,
+          sdk: example.sdk,
+          tags: example.tags,
+          type: example.type,
+          viewOptions: example.viewOptions,
         );
 }
diff --git a/playground/frontend/playground_components/lib/src/models/example_base.dart b/playground/frontend/playground_components/lib/src/models/example_base.dart
index 62968579b27..dd9b7fcb34c 100644
--- a/playground/frontend/playground_components/lib/src/models/example_base.dart
+++ b/playground/frontend/playground_components/lib/src/models/example_base.dart
@@ -20,6 +20,7 @@ import 'package:equatable/equatable.dart';
 
 import '../enums/complexity.dart';
 import '../repositories/example_repository.dart';
+import 'example_view_options.dart';
 import 'sdk.dart';
 
 enum ExampleType {
@@ -48,32 +49,35 @@ extension ExampleTypeToString on ExampleType {
 /// and other large fields.
 /// These objects are fetched as lists from [ExampleRepository].
 class ExampleBase with Comparable<ExampleBase>, EquatableMixin {
-  final Sdk sdk;
-  final List<String> tags;
-  final ExampleType type;
-  final String name;
-  final String path;
-  final String description;
+  final Complexity? complexity;
   final int contextLine;
+  final String description;
   final bool isMultiFile;
   final String? link;
+  final String name;
+  final String path;
   final String pipelineOptions;
-  final Complexity complexity;
+  final Sdk sdk;
+  final List<String> tags;
+  final ExampleType type;
+  final ExampleViewOptions viewOptions;
 
   const ExampleBase({
-    required this.sdk,
     required this.name,
     required this.path,
-    required this.description,
-    required this.tags,
+    required this.sdk,
     required this.type,
+    this.complexity,
     this.contextLine = 1,
+    this.description = '',
     this.isMultiFile = false,
     this.link,
-    required this.pipelineOptions,
-    required this.complexity,
+    this.pipelineOptions = '',
+    this.tags = const [],
+    this.viewOptions = ExampleViewOptions.empty,
   });
 
+  // TODO(alexeyinkin): Use all fields, https://github.com/apache/beam/issues/23979
   @override
   List<Object> get props => [path];
 
diff --git a/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/content_example_loading_descriptor.dart b/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/content_example_loading_descriptor.dart
index dbc9a7e8567..6cac8861e24 100644
--- a/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/content_example_loading_descriptor.dart
+++ b/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/content_example_loading_descriptor.dart
@@ -27,15 +27,16 @@ class ContentExampleLoadingDescriptor extends ExampleLoadingDescriptor {
   /// The name of the example, if any, to show in the dropdown.
   final String? name;
 
-  final Complexity complexity;
+  final Complexity? complexity;
 
   final Sdk sdk;
 
   const ContentExampleLoadingDescriptor({
     required this.content,
-    required this.name,
     required this.sdk,
-    required this.complexity,
+    this.complexity,
+    this.name,
+    super.viewOptions,
   });
 
   static ContentExampleLoadingDescriptor? tryParse(Map eventData) {
@@ -69,7 +70,7 @@ class ContentExampleLoadingDescriptor extends ExampleLoadingDescriptor {
     return Sdk.tryParse(map['sdk']);
   }
 
-  static Complexity _parseComplexity(Map map) {
+  static Complexity? _parseComplexity(Map map) {
     final complexityString = map['complexity'];
     return Complexity.fromString(complexityString);
   }
@@ -79,9 +80,9 @@ class ContentExampleLoadingDescriptor extends ExampleLoadingDescriptor {
 
   @override
   Map<String, dynamic> toJson() => {
+        'complexity': complexity?.name,
         'content': content,
         'name': name,
         'sdk': sdk.id,
-        'complexity': complexity.name,
       };
 }
diff --git a/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/example_loading_descriptor.dart b/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/example_loading_descriptor.dart
index 35c3cf18100..c78dafff250 100644
--- a/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/example_loading_descriptor.dart
+++ b/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/example_loading_descriptor.dart
@@ -18,8 +18,14 @@
 
 import 'package:equatable/equatable.dart';
 
+import '../example_view_options.dart';
+
 abstract class ExampleLoadingDescriptor with EquatableMixin {
-  const ExampleLoadingDescriptor();
+  const ExampleLoadingDescriptor({
+    this.viewOptions = ExampleViewOptions.empty,
+  });
+
+  final ExampleViewOptions viewOptions;
 
   Map<String, dynamic> toJson() => throw UnimplementedError();
 }
diff --git a/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/standard_example_loading_descriptor.dart b/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/http_example_loading_descriptor.dart
similarity index 73%
copy from playground/frontend/playground_components/lib/src/models/example_loading_descriptors/standard_example_loading_descriptor.dart
copy to playground/frontend/playground_components/lib/src/models/example_loading_descriptors/http_example_loading_descriptor.dart
index 8ac57c624a9..f65534aac93 100644
--- a/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/standard_example_loading_descriptor.dart
+++ b/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/http_example_loading_descriptor.dart
@@ -16,15 +16,23 @@
  * limitations under the License.
  */
 
+import '../sdk.dart';
 import 'example_loading_descriptor.dart';
 
-class StandardExampleLoadingDescriptor extends ExampleLoadingDescriptor {
-  final String path;
+class HttpExampleLoadingDescriptor extends ExampleLoadingDescriptor {
+  final Sdk sdk;
+  final Uri uri;
 
-  const StandardExampleLoadingDescriptor({
-    required this.path,
+  const HttpExampleLoadingDescriptor({
+    required this.sdk,
+    required this.uri,
+    super.viewOptions,
   });
 
   @override
-  List<Object> get props => [path];
+  List<Object> get props => [
+        sdk,
+        uri,
+        viewOptions,
+      ];
 }
diff --git a/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/standard_example_loading_descriptor.dart b/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/standard_example_loading_descriptor.dart
index 8ac57c624a9..62767e2b58d 100644
--- a/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/standard_example_loading_descriptor.dart
+++ b/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/standard_example_loading_descriptor.dart
@@ -23,8 +23,12 @@ class StandardExampleLoadingDescriptor extends ExampleLoadingDescriptor {
 
   const StandardExampleLoadingDescriptor({
     required this.path,
+    super.viewOptions,
   });
 
   @override
-  List<Object> get props => [path];
+  List<Object> get props => [
+        path,
+        viewOptions,
+      ];
 }
diff --git a/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/user_shared_example_loading_descriptor.dart b/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/user_shared_example_loading_descriptor.dart
index 1bcbe0dc60a..fc697bccb75 100644
--- a/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/user_shared_example_loading_descriptor.dart
+++ b/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/user_shared_example_loading_descriptor.dart
@@ -23,8 +23,12 @@ class UserSharedExampleLoadingDescriptor extends ExampleLoadingDescriptor {
 
   const UserSharedExampleLoadingDescriptor({
     required this.snippetId,
+    super.viewOptions,
   });
 
   @override
-  List<Object> get props => [snippetId];
+  List<Object> get props => [
+        snippetId,
+        viewOptions,
+      ];
 }
diff --git a/playground/frontend/playground_components/lib/src/models/example_view_options.dart b/playground/frontend/playground_components/lib/src/models/example_view_options.dart
new file mode 100644
index 00000000000..bc12a66db0a
--- /dev/null
+++ b/playground/frontend/playground_components/lib/src/models/example_view_options.dart
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import 'package:equatable/equatable.dart';
+
+import '../util/string.dart';
+
+class ExampleViewOptions with EquatableMixin {
+  final bool foldCommentAtLineZero;
+  final bool foldImports;
+  final List<String> readOnlySectionNames;
+  final List<String> showSectionNames;
+  final List<String> unfoldSectionNames;
+
+  const ExampleViewOptions({
+    required this.foldCommentAtLineZero,
+    required this.foldImports,
+    required this.readOnlySectionNames,
+    required this.showSectionNames,
+    required this.unfoldSectionNames,
+  });
+
+  factory ExampleViewOptions.fromShortMap(Map<String, dynamic> map) {
+    return ExampleViewOptions(
+      foldCommentAtLineZero: true,
+      foldImports: true,
+      readOnlySectionNames: _split(map['readonly']),
+      showSectionNames: _split(map['show']),
+      unfoldSectionNames: _split(map['unfold']),
+    );
+  }
+
+  static List<String> _split(Object? value) {
+    if (value is! String) {
+      return [];
+    }
+
+    return value.splitNotEmpty(',');
+  }
+
+  static const empty = ExampleViewOptions(
+    foldCommentAtLineZero: true,
+    foldImports: true,
+    readOnlySectionNames: [],
+    showSectionNames: [],
+    unfoldSectionNames: [],
+  );
+
+  @override
+  List<Object> get props => [
+        foldCommentAtLineZero,
+        foldImports,
+        readOnlySectionNames,
+        showSectionNames,
+        unfoldSectionNames,
+      ];
+}
diff --git a/playground/frontend/playground_components/lib/src/repositories/complexity_grpc_extension.dart b/playground/frontend/playground_components/lib/src/repositories/complexity_grpc_extension.dart
index c9edb542983..39c9eed824d 100644
--- a/playground/frontend/playground_components/lib/src/repositories/complexity_grpc_extension.dart
+++ b/playground/frontend/playground_components/lib/src/repositories/complexity_grpc_extension.dart
@@ -20,7 +20,7 @@ import '../api/v1/api.pbgrpc.dart' as g;
 import '../enums/complexity.dart';
 
 extension GrpcComplecity on g.Complexity {
-  Complexity get model {
+  Complexity? get model {
     switch (this) {
       case g.Complexity.COMPLEXITY_BASIC:
         return Complexity.basic;
@@ -29,7 +29,7 @@ extension GrpcComplecity on g.Complexity {
       case g.Complexity.COMPLEXITY_ADVANCED:
         return Complexity.advanced;
       case g.Complexity.COMPLEXITY_UNSPECIFIED:
-        return Complexity.unspecified;
+        return null;
     }
     throw Exception('Unknown complexity: $this');
   }
diff --git a/playground/frontend/playground_components/lib/src/repositories/models/get_snippet_response.dart b/playground/frontend/playground_components/lib/src/repositories/models/get_snippet_response.dart
index 460b215c4f4..c935936898d 100644
--- a/playground/frontend/playground_components/lib/src/repositories/models/get_snippet_response.dart
+++ b/playground/frontend/playground_components/lib/src/repositories/models/get_snippet_response.dart
@@ -21,15 +21,15 @@ import '../../models/sdk.dart';
 import 'shared_file.dart';
 
 class GetSnippetResponse {
+  final Complexity? complexity;
   final List<SharedFile> files;
-  final Sdk sdk;
   final String pipelineOptions;
-  final Complexity complexity;
+  final Sdk sdk;
 
   const GetSnippetResponse({
+    required this.complexity,
     required this.files,
-    required this.sdk,
     required this.pipelineOptions,
-    required this.complexity,
+    required this.sdk,
   });
 }
diff --git a/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/example_loading_descriptor.dart b/playground/frontend/playground_components/lib/src/util/string.dart
similarity index 79%
copy from playground/frontend/playground_components/lib/src/models/example_loading_descriptors/example_loading_descriptor.dart
copy to playground/frontend/playground_components/lib/src/util/string.dart
index 35c3cf18100..924abcde1f2 100644
--- a/playground/frontend/playground_components/lib/src/models/example_loading_descriptors/example_loading_descriptor.dart
+++ b/playground/frontend/playground_components/lib/src/util/string.dart
@@ -16,10 +16,8 @@
  * limitations under the License.
  */
 
-import 'package:equatable/equatable.dart';
-
-abstract class ExampleLoadingDescriptor with EquatableMixin {
-  const ExampleLoadingDescriptor();
-
-  Map<String, dynamic> toJson() => throw UnimplementedError();
+extension StringExtension on String {
+  List<String> splitNotEmpty(Pattern pattern) {
+    return split(pattern).where((s) => s.isNotEmpty).toList(growable: false);
+  }
 }
diff --git a/playground/frontend/playground_components/lib/src/widgets/editor_textarea.dart b/playground/frontend/playground_components/lib/src/widgets/editor_textarea.dart
index 9714177ec94..571415a1e71 100644
--- a/playground/frontend/playground_components/lib/src/widgets/editor_textarea.dart
+++ b/playground/frontend/playground_components/lib/src/widgets/editor_textarea.dart
@@ -149,15 +149,12 @@ class _EditorTextAreaState extends State<EditorTextArea> {
   }
 
   int _getIndexOfContextLine() {
-    int ctxLineNumber = widget.example!.contextLine;
-    String contextLine = widget.codeController.text.split('\n')[ctxLineNumber];
+    final contextLine = widget.example!.contextLine;
+    final code = widget.codeController.code;
+    final fullCharIndex = code.lines.lines[contextLine].textRange.start;
+    final visibleCharIndex = code.hiddenRanges.cutPosition(fullCharIndex);
 
-    while (contextLine == '') {
-      ctxLineNumber -= 1;
-      contextLine = widget.codeController.text.split('\n')[ctxLineNumber];
-    }
-
-    return widget.codeController.text.indexOf(contextLine);
+    return visibleCharIndex;
   }
 
   // This function made for more accuracy in the process of finding an exact line.
diff --git a/playground/frontend/playground_components/pubspec.yaml b/playground/frontend/playground_components/pubspec.yaml
index 7922c2bbec3..9183b6b4490 100644
--- a/playground/frontend/playground_components/pubspec.yaml
+++ b/playground/frontend/playground_components/pubspec.yaml
@@ -32,12 +32,13 @@ dependencies:
   easy_localization_loader: ^1.0.0
   equatable: ^2.0.5
   flutter: { sdk: flutter }
-  flutter_code_editor: ^0.1.3
+  flutter_code_editor: ^0.1.4 # Unlisted package, use direct link: https://pub.dev/packages/flutter_code_editor
   flutter_markdown: ^0.6.12
   flutter_svg: ^1.0.3
   google_fonts: ^3.0.1
   grpc: ^3.0.2
   highlight: ^0.7.0
+  http: ^0.13.5
   json_annotation: ^4.7.0
   meta: ^1.7.0
   protobuf: ^2.1.0
diff --git a/playground/frontend/playground_components/test/src/controllers/example_loaders/http_example_loader_test.dart b/playground/frontend/playground_components/test/src/controllers/example_loaders/http_example_loader_test.dart
new file mode 100644
index 00000000000..5860ff7bb17
--- /dev/null
+++ b/playground/frontend/playground_components/test/src/controllers/example_loaders/http_example_loader_test.dart
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:mockito/annotations.dart';
+import 'package:playground_components/playground_components.dart';
+import 'package:playground_components/src/controllers/example_loaders/http_example_loader.dart';
+
+import 'http_example_loader_test.mocks.dart';
+
+// A random small file at a specific revision.
+const _name = 'section-remote-info.yaml';
+const _path =
+    'https://raw.githubusercontent.com/apache/beam/238356dade3df54ea3d3f84a7424fa5c99bf37a4/learning/katas/go/core_transforms/$_name';
+
+const _contents = '''
+id: 131788
+update_date: Mon, 27 Jul 2020 18:50:54 UTC
+''';
+
+const _sdk = Sdk.go;
+
+@GenerateMocks([ExampleCache])
+void main() {
+  test('HttpExampleLoader', () async {
+    final loader = HttpExampleLoader(
+      descriptor: HttpExampleLoadingDescriptor(
+        sdk: _sdk,
+        uri: Uri.parse(_path),
+      ),
+      exampleCache: MockExampleCache(),
+    );
+
+    final example = await loader.future;
+
+    // TODO(alexeyinkin): Compare whole objects when that gets to include all fields, https://github.com/apache/beam/issues/23979
+    expect(example.name, _name);
+    expect(example.sdk, _sdk);
+    expect(example.source, _contents);
+    expect(example.type, ExampleType.example);
+    expect(example.path, _path);
+  });
+}
diff --git a/playground/frontend/pubspec.lock b/playground/frontend/pubspec.lock
index 6e7da5f916a..bce3fe573ac 100644
--- a/playground/frontend/pubspec.lock
+++ b/playground/frontend/pubspec.lock
@@ -278,7 +278,7 @@ packages:
       name: flutter_code_editor
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.1.3"
+    version: "0.1.4"
   flutter_highlight:
     dependency: transitive
     description:
@@ -391,7 +391,7 @@ packages:
       name: http
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.13.4"
+    version: "0.13.5"
   http2:
     dependency: transitive
     description:
diff --git a/playground/frontend/test/modules/examples/models/example_loading_descriptors/examples_loading_descriptor_factory_test.dart b/playground/frontend/test/modules/examples/models/example_loading_descriptors/examples_loading_descriptor_factory_test.dart
new file mode 100644
index 00000000000..1867ae28d3a
--- /dev/null
+++ b/playground/frontend/test/modules/examples/models/example_loading_descriptors/examples_loading_descriptor_factory_test.dart
@@ -0,0 +1,224 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import 'dart:convert';
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:playground/constants/params.dart';
+import 'package:playground/modules/examples/models/example_loading_descriptors/examples_loading_descriptor_factory.dart';
+import 'package:playground_components/playground_components.dart';
+
+const _viewOptionsMap = {
+  'readonly': 'readonly1,readonly2',
+  'show': 'show1,show2',
+  'unfold': 'unfold1,unfold2',
+};
+
+const _viewOptions = ExampleViewOptions(
+  foldCommentAtLineZero: true,
+  foldImports: true,
+  readOnlySectionNames: ['readonly1', 'readonly2'],
+  showSectionNames: ['show1', 'show2'],
+  unfoldSectionNames: ['unfold1', 'unfold2'],
+);
+
+void main() {
+  final lazy = ExamplesLoadingDescriptorFactory.defaultLazyLoadDescriptors;
+
+  group('ExamplesLoadingDescriptorFactory', () {
+    test('defaultLazyLoadDescriptors', () {
+      final expected = {
+        Sdk.go: [
+          const CatalogDefaultExampleLoadingDescriptor(
+            sdk: Sdk.go,
+          )
+        ],
+        Sdk.java: [
+          const CatalogDefaultExampleLoadingDescriptor(
+            sdk: Sdk.java,
+          )
+        ],
+        Sdk.python: [
+          const CatalogDefaultExampleLoadingDescriptor(
+            sdk: Sdk.python,
+          )
+        ],
+        Sdk.scio: [
+          const CatalogDefaultExampleLoadingDescriptor(
+            sdk: Sdk.scio,
+          )
+        ],
+      };
+
+      expect(lazy, expected);
+    });
+
+    group('fromUriParts', () {
+      void testExamples(Iterable<_Example> examples) {
+        for (final example in examples) {
+          final result = ExamplesLoadingDescriptorFactory.fromUriParts(
+            params: example.params,
+            path: '',
+          );
+
+          expect(result, example.expected);
+        }
+      }
+
+      test('ContentExampleLoadingDescriptor', () {
+        testExamples([
+          _Example(
+            params: {
+              kExamplesParam: jsonEncode(
+                [
+                  {
+                    'sdk': 'go',
+                    'content': 'go_content',
+                  },
+                  {
+                    'sdk': 'python',
+                    'content': 'python_content',
+                    ..._viewOptionsMap,
+                  },
+                ],
+              ),
+            },
+            expected: ExamplesLoadingDescriptor(
+              descriptors: const [
+                ContentExampleLoadingDescriptor(
+                  content: 'go_content',
+                  sdk: Sdk.go,
+                ),
+                ContentExampleLoadingDescriptor(
+                  content: 'python_content',
+                  sdk: Sdk.python,
+                  viewOptions: _viewOptions,
+                ),
+              ],
+              lazyLoadDescriptors: lazy,
+            ),
+          ),
+        ]);
+      });
+
+      test('HttpExampleLoadingDescriptor', () {
+        testExamples([
+          _Example(
+            params: {
+              kExamplesParam: jsonEncode(
+                [
+                  {
+                    'sdk': 'go',
+                    'example': 'http://',
+                  },
+                  {
+                    'sdk': 'python',
+                    'example': 'https://',
+                    ..._viewOptionsMap,
+                  },
+                ],
+              ),
+            },
+            expected: ExamplesLoadingDescriptor(
+              descriptors: [
+                HttpExampleLoadingDescriptor(
+                  sdk: Sdk.go,
+                  uri: Uri.parse('http://'),
+                ),
+                HttpExampleLoadingDescriptor(
+                  sdk: Sdk.python,
+                  uri: Uri.parse('https://'),
+                  viewOptions: _viewOptions,
+                ),
+              ],
+              lazyLoadDescriptors: lazy,
+            ),
+          ),
+        ]);
+      });
+
+      test('StandardExampleLoadingDescriptor', () {
+        testExamples([
+          _Example(
+            params: {
+              kExamplesParam: jsonEncode(
+                [
+                  {'example': 'SDK_GO'},
+                  {'example': 'SDK_PYTHON/something', ..._viewOptionsMap},
+                ],
+              ),
+            },
+            expected: ExamplesLoadingDescriptor(
+              descriptors: const [
+                StandardExampleLoadingDescriptor(
+                  path: 'SDK_GO',
+                ),
+                StandardExampleLoadingDescriptor(
+                  path: 'SDK_PYTHON/something',
+                  viewOptions: _viewOptions,
+                ),
+              ],
+              lazyLoadDescriptors: lazy,
+            ),
+          ),
+        ]);
+      });
+
+      test('UserSharedExampleLoadingDescriptor', () {
+        testExamples([
+          _Example(
+            params: {
+              kExamplesParam: jsonEncode(
+                [
+                  {'example': ''},
+                  {'example': '123'},
+                  {'example': 'abc', ..._viewOptionsMap},
+                ],
+              ),
+            },
+            expected: ExamplesLoadingDescriptor(
+              descriptors: const [
+                UserSharedExampleLoadingDescriptor(
+                  snippetId: '',
+                ),
+                UserSharedExampleLoadingDescriptor(
+                  snippetId: '123',
+                ),
+                UserSharedExampleLoadingDescriptor(
+                  snippetId: 'abc',
+                  viewOptions: _viewOptions,
+                ),
+              ],
+              lazyLoadDescriptors: lazy,
+            ),
+          ),
+        ]);
+      });
+    });
+  });
+}
+
+class _Example {
+  final Map<String, dynamic> params;
+  final ExamplesLoadingDescriptor expected;
+
+  const _Example({
+    required this.params,
+    required this.expected,
+  });
+}
diff --git a/playground/frontend/test/modules/examples/models/example_token_type_test.dart b/playground/frontend/test/modules/examples/models/example_token_type_test.dart
new file mode 100644
index 00000000000..5f1ec8943f7
--- /dev/null
+++ b/playground/frontend/test/modules/examples/models/example_token_type_test.dart
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:playground/modules/examples/models/example_token_type.dart';
+
+void main() {
+  test('ExampleTokenType.fromToken', () {
+    const examples = {
+      //
+      'http://': ExampleTokenType.http,
+      'https://example.com/path': ExampleTokenType.http,
+
+      'SDK_GO': ExampleTokenType.standard,
+      'SDK_JAVA_something': ExampleTokenType.standard,
+      'SDK_PYTHONNNN': ExampleTokenType.standard,
+      'SDK_SCIO/something': ExampleTokenType.standard,
+
+      '': ExampleTokenType.userShared,
+      '123': ExampleTokenType.userShared,
+      'abc': ExampleTokenType.userShared,
+      'SDK_TYPESCRIPT': ExampleTokenType.userShared,
+      'SDK_RUST': ExampleTokenType.userShared,
+    };
+
+    for (final example in examples.entries) {
+      final token = example.key;
+      final result = ExampleTokenType.fromToken(token);
+
+      expect(result, example.value, reason: token);
+    }
+  });
+}
diff --git a/website/www/site/layouts/shortcodes/playground.html b/website/www/site/layouts/shortcodes/playground.html
index 4dc3eaf09e7..7e9aa3dd7d3 100644
--- a/website/www/site/layouts/shortcodes/playground.html
+++ b/website/www/site/layouts/shortcodes/playground.html
@@ -10,6 +10,9 @@ See the License for the specific language governing permissions and
 limitations under the License. See accompanying LICENSE file.
 */}}
 {{/*
+
+Creates an embedded Playground with given examples.
+
 Embedding example:
 
 {{< playground height="700px" >}}
@@ -25,14 +28,32 @@ Embedding example:
         {{ .Inner }}
     </div>
     {{ $snippetsList := slice }}
-    {{ $divMatches := findRE "<div class=\"[^\"]+(playground-snippet)\"(.*)</div>" .Inner }}
+    {{ $divMatches := findRE "<div(\\s+)class=\"[^\"]+(playground-snippet)\"((.|\\s)*?)</div>" .Inner }}
 
     {{ range $divMatches }}
-        {{ $attributeRegex := "data-sdk=\"(?P<sdk>\\w+)\" data-example=\"([^\"]+)\"" }}
-        {{ $sdk := replaceRE ".*data-sdk=\"(\\w+)\".*" "$1" . }}
-        {{ $example := replaceRE ".*example=\"([^\"]+)\".*" "$1" . }}
-        {{ $json := printf "%s%s%s%s%s" "{\"sdk\":\"" $sdk "\",\"example\":\"" $example "\"}" }}
-        {{ $snippetsList = append $json $snippetsList }}
+        {{ $sdk := replaceRE "(.|\\s)*data-sdk=\"(\\w+)\"(.|\\s)*" "$2" . }}
+        {{ $example := replaceRE "(.|\\s)*example=\"([^\"]+)\"(.|\\s)*" "$2" . }}
+        {{ $dict := dict "sdk" $sdk "example" $example }}
+
+        {{ $readonlyRegex := "(.|\\s)*readonly=\"([_,a-zA-Z0-9]+)\"(.|\\s)*" }}
+        {{ if findRE $readonlyRegex . }}
+            {{ $readonly := replaceRE $readonlyRegex "$2" . }}
+            {{ $dict = merge $dict (dict "readonly" $readonly) }}
+        {{ end }}
+
+        {{ $showRegex := "(.|\\s)*show=\"([_a-zA-Z0-9]+)\"(.|\\s)*" }}
+        {{ if findRE $showRegex . }}
+            {{ $show := replaceRE $showRegex "$2" . }}
+            {{ $dict = merge $dict (dict "show" $show) }}
+        {{ end }}
+
+        {{ $unfoldRegex := "(.|\\s)*unfold=\"([_,a-zA-Z0-9]+)\"(.|\\s)*" }}
+        {{ if findRE $unfoldRegex . }}
+            {{ $unfold := replaceRE $unfoldRegex "$2" . }}
+            {{ $dict = merge $dict (dict "unfold" $unfold) }}
+        {{ end }}
+
+        {{ $snippetsList = append (jsonify $dict) $snippetsList }}
     {{ end}}
 
     {{ $snippets := printf "%s%s%s" "[" (delimit $snippetsList ",") "]" }}
diff --git a/website/www/site/layouts/shortcodes/playground_snippet.html b/website/www/site/layouts/shortcodes/playground_snippet.html
index 4c4ce501168..10574844201 100644
--- a/website/www/site/layouts/shortcodes/playground_snippet.html
+++ b/website/www/site/layouts/shortcodes/playground_snippet.html
@@ -9,4 +9,38 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License. See accompanying LICENSE file.
 */}}
-{{ $sdk := .Get "language" }}{{ if eq $sdk "py" }}{{ $sdk = "python" }}{{ end }}<div class="language-{{ .Get "language" }} playground-snippet" data-sdk="{{ $sdk }}" data-example="{{ .Get "example" }}"></div>
+{{/*
+
+Creates an embedded Playground tab with a given example. Must be used within 'playground' shortcode
+because here only a div is created and not an iframe. It is 'playground' that actually creates
+an iframe.
+
+Required parameters:
+- example - the example ID or snippet ID.
+- language - the string to be passed as SDK to playground. 'py' translates to 'python'.
+
+Optional parameters:
+- readonly - comma-separated section names to make read-only.
+- show - section name, hides everything except it, makes the entire code read-only.
+- unfold - comma-separated section names, folds all blocks not overlapping with them.
+
+*/}}
+
+{{ $sdk := .Get "language" }}
+{{ if eq $sdk "py" }}
+    {{ $sdk = "python" }}
+{{ end }}
+<div
+    class="language-{{ .Get "language" }} playground-snippet"
+    data-sdk="{{ $sdk }}"
+    data-example="{{ .Get "example" }}"
+    {{ if .Get "readonly" }}
+        data-readonly="{{ .Get "readonly" }}"
+    {{ end }}
+    {{ if .Get "show" }}
+        data-show="{{ .Get "show" }}"
+    {{ end }}
+    {{ if .Get "unfold" }}
+        data-unfold="{{ .Get "unfold" }}"
+    {{ end }}
+></div>