Repository: akvelon/flutter-code-editor
Pull request #278 (2024-07-26 14:57:18)

Disable smart quotes and smart dashes on iOS
Expose [`smartDashesType`][1] and [`smartQuotesType`][2] on the underlying `TextField`, and disable them by default.

This fixes an issue where typing a `"` on the iOS keyboard would automatically be replaced by a `“`.

[1]: https://api.flutter.dev/flutter/material/TextField/smartDashesType.html
[2]: https://api.flutter.dev/flutter/material/TextField/smartQuotesType.html


Patch:
diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle
index 6ca2344..431faff 100644
--- a/example/android/app/build.gradle
+++ b/example/android/app/build.gradle
@@ -1,3 +1,9 @@
+plugins {
+    id "com.android.application"
+    id "kotlin-android"
+    id "dev.flutter.flutter-gradle-plugin"
+}
+
 def localProperties = new Properties()
 def localPropertiesFile = rootProject.file('local.properties')
 if (localPropertiesFile.exists()) {
@@ -6,11 +12,6 @@ if (localPropertiesFile.exists()) {
     }
 }
 
-def flutterRoot = localProperties.getProperty('flutter.sdk')
-if (flutterRoot == null) {
-    throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
-}
-
 def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
 if (flutterVersionCode == null) {
     flutterVersionCode = '1'
@@ -21,12 +22,20 @@ if (flutterVersionName == null) {
     flutterVersionName = '1.0'
 }
 
-apply plugin: 'com.android.application'
-apply plugin: 'kotlin-android'
-apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
-
 android {
-    compileSdkVersion 30
+    namespace "com.example.example"
+    defaultConfig {
+        compileSdk flutter.compileSdkVersion
+    }
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    kotlinOptions {
+        jvmTarget = '1.8'
+    }
 
     sourceSets {
         main.java.srcDirs += 'src/main/kotlin'
@@ -35,8 +44,8 @@ android {
     defaultConfig {
         // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
         applicationId "com.example.example"
-        minSdkVersion 16
-        targetSdkVersion 30
+        minSdkVersion flutter.minSdkVersion
+        targetSdkVersion flutter.targetSdkVersion
         versionCode flutterVersionCode.toInteger()
         versionName flutterVersionName
     }
@@ -54,6 +63,4 @@ flutter {
     source '../..'
 }
 
-dependencies {
-    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
-}
+dependencies {}
diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml
index c208884..f880684 100644
--- a/example/android/app/src/debug/AndroidManifest.xml
+++ b/example/android/app/src/debug/AndroidManifest.xml
@@ -1,5 +1,4 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.example.example">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
     <!-- Flutter needs it to communicate with the running application
          to allow setting breakpoints, to provide hot reload, etc.
     -->
diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml
index 34dd77e..3ff9f2a 100644
--- a/example/android/app/src/main/AndroidManifest.xml
+++ b/example/android/app/src/main/AndroidManifest.xml
@@ -1,10 +1,10 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.example.example">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application
         android:label="example"
         android:icon="@mipmap/ic_launcher">
         <activity
             android:name=".MainActivity"
+            android:exported="true"
             android:launchMode="singleTop"
             android:theme="@style/LaunchTheme"
             android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml
index c208884..f880684 100644
--- a/example/android/app/src/profile/AndroidManifest.xml
+++ b/example/android/app/src/profile/AndroidManifest.xml
@@ -1,5 +1,4 @@
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.example.example">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
     <!-- Flutter needs it to communicate with the running application
          to allow setting breakpoints, to provide hot reload, etc.
     -->
diff --git a/example/android/build.gradle b/example/android/build.gradle
index c505a86..6c4d142 100644
--- a/example/android/build.gradle
+++ b/example/android/build.gradle
@@ -1,12 +1,12 @@
 buildscript {
-    ext.kotlin_version = '1.3.50'
+    ext.kotlin_version = '1.7.10'
     repositories {
         google()
         jcenter()
     }
 
     dependencies {
-        classpath 'com.android.tools.build:gradle:4.1.0'
+        classpath 'com.android.tools.build:gradle:8.3.0'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
     }
 }
@@ -14,7 +14,7 @@ buildscript {
 allprojects {
     repositories {
         google()
-        jcenter()
+        mavenCentral()
     }
 }
 
@@ -26,6 +26,6 @@ subprojects {
     project.evaluationDependsOn(':app')
 }
 
-task clean(type: Delete) {
+tasks.register("clean", Delete) {
     delete rootProject.buildDir
 }
diff --git a/example/android/gradle.properties b/example/android/gradle.properties
index 94adc3a..b9a9a24 100644
--- a/example/android/gradle.properties
+++ b/example/android/gradle.properties
@@ -1,3 +1,6 @@
 org.gradle.jvmargs=-Xmx1536M
 android.useAndroidX=true
 android.enableJetifier=true
+android.defaults.buildfeatures.buildconfig=true
+android.nonTransitiveRClass=false
+android.nonFinalResIds=false
diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties
index bc6a58a..fb5eb59 100644
--- a/example/android/gradle/wrapper/gradle-wrapper.properties
+++ b/example/android/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip
diff --git a/example/android/settings.gradle b/example/android/settings.gradle
index 44e62bc..7dee7dc 100644
--- a/example/android/settings.gradle
+++ b/example/android/settings.gradle
@@ -1,11 +1,24 @@
-include ':app'
+pluginManagement {
+    def flutterSdkPath = {
+        def properties = new Properties()
+        file("local.properties").withInputStream { properties.load(it) }
+        def flutterSdkPath = properties.getProperty("flutter.sdk")
+        assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+        return flutterSdkPath
+    }
+    settings.ext.flutterSdkPath = flutterSdkPath()
 
-def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
-def properties = new Properties()
+    includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
 
-assert localPropertiesFile.exists()
-localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
+    plugins {
+        id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false
+    }
+}
 
-def flutterSdkPath = properties.getProperty("flutter.sdk")
-assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
-apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
+plugins {
+    id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0'
+}
+
+include ":app"
+
+apply from: "${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle/app_plugin_loader.gradle"
diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist
index 9367d48..7c56964 100644
--- a/example/ios/Flutter/AppFrameworkInfo.plist
+++ b/example/ios/Flutter/AppFrameworkInfo.plist
@@ -21,6 +21,6 @@
   <key>CFBundleVersion</key>
   <string>1.0</string>
   <key>MinimumOSVersion</key>
-  <string>8.0</string>
+  <string>12.0</string>
 </dict>
 </plist>
diff --git a/example/ios/Podfile b/example/ios/Podfile
index 1e8c3c9..279576f 100644
--- a/example/ios/Podfile
+++ b/example/ios/Podfile
@@ -1,5 +1,5 @@
 # Uncomment this line to define a global platform for your project
-# platform :ios, '9.0'
+# platform :ios, '12.0'
 
 # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
 ENV['COCOAPODS_DISABLE_STATS'] = 'true'
diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj
index c6759a6..b98c35c 100644
--- a/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/example/ios/Runner.xcodeproj/project.pbxproj
@@ -3,7 +3,7 @@
 	archiveVersion = 1;
 	classes = {
 	};
-	objectVersion = 46;
+	objectVersion = 54;
 	objects = {
 
 /* Begin PBXBuildFile section */
@@ -13,6 +13,7 @@
 		97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
 		97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
 		97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
+		E2C0241AD294C9276D444091 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = ADFA5823957E0B9AE77E1DF2 /* Pods_Runner.framework */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXCopyFilesBuildPhase section */
@@ -35,6 +36,7 @@
 		74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
 		74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
 		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
+		931C3FAE062D5D73D5988872 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
 		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
 		9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
 		97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -42,6 +44,9 @@
 		97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
 		97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
 		97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		ADFA5823957E0B9AE77E1DF2 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+		D36A88157482154467148A97 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
+		E8DC42FE1BD81E62F5E1DC48 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -49,12 +54,21 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				E2C0241AD294C9276D444091 /* Pods_Runner.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
 /* End PBXFrameworksBuildPhase section */
 
 /* Begin PBXGroup section */
+		5128DAE7524B727EDE717CC5 /* Frameworks */ = {
+			isa = PBXGroup;
+			children = (
+				ADFA5823957E0B9AE77E1DF2 /* Pods_Runner.framework */,
+			);
+			name = Frameworks;
+			sourceTree = "<group>";
+		};
 		9740EEB11CF90186004384FC /* Flutter */ = {
 			isa = PBXGroup;
 			children = (
@@ -72,6 +86,8 @@
 				9740EEB11CF90186004384FC /* Flutter */,
 				97C146F01CF9000F007C117D /* Runner */,
 				97C146EF1CF9000F007C117D /* Products */,
+				9BD710DD45D470F2F87BB2CB /* Pods */,
+				5128DAE7524B727EDE717CC5 /* Frameworks */,
 			);
 			sourceTree = "<group>";
 		};
@@ -98,6 +114,17 @@
 			path = Runner;
 			sourceTree = "<group>";
 		};
+		9BD710DD45D470F2F87BB2CB /* Pods */ = {
+			isa = PBXGroup;
+			children = (
+				E8DC42FE1BD81E62F5E1DC48 /* Pods-Runner.debug.xcconfig */,
+				D36A88157482154467148A97 /* Pods-Runner.release.xcconfig */,
+				931C3FAE062D5D73D5988872 /* Pods-Runner.profile.xcconfig */,
+			);
+			name = Pods;
+			path = Pods;
+			sourceTree = "<group>";
+		};
 /* End PBXGroup section */
 
 /* Begin PBXNativeTarget section */
@@ -105,12 +132,14 @@
 			isa = PBXNativeTarget;
 			buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
 			buildPhases = (
+				7DEB565E75F0B55755C8E03B /* [CP] Check Pods Manifest.lock */,
 				9740EEB61CF901F6004384FC /* Run Script */,
 				97C146EA1CF9000F007C117D /* Sources */,
 				97C146EB1CF9000F007C117D /* Frameworks */,
 				97C146EC1CF9000F007C117D /* Resources */,
 				9705A1C41CF9048500538489 /* Embed Frameworks */,
 				3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+				59B4E2D0E7983859023B38DC /* [CP] Embed Pods Frameworks */,
 			);
 			buildRules = (
 			);
@@ -127,7 +156,7 @@
 		97C146E61CF9000F007C117D /* Project object */ = {
 			isa = PBXProject;
 			attributes = {
-				LastUpgradeCheck = 1020;
+				LastUpgradeCheck = 1510;
 				ORGANIZATIONNAME = "";
 				TargetAttributes = {
 					97C146ED1CF9000F007C117D = {
@@ -171,10 +200,12 @@
 /* Begin PBXShellScriptBuildPhase section */
 		3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
 			isa = PBXShellScriptBuildPhase;
+			alwaysOutOfDate = 1;
 			buildActionMask = 2147483647;
 			files = (
 			);
 			inputPaths = (
+				"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
 			);
 			name = "Thin Binary";
 			outputPaths = (
@@ -183,8 +214,48 @@
 			shellPath = /bin/sh;
 			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
 		};
+		59B4E2D0E7983859023B38DC /* [CP] Embed Pods Frameworks */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+				"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
+			);
+			name = "[CP] Embed Pods Frameworks";
+			outputFileListPaths = (
+				"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
+			showEnvVarsInLog = 0;
+		};
+		7DEB565E75F0B55755C8E03B /* [CP] Check Pods Manifest.lock */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+			);
+			inputPaths = (
+				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+				"${PODS_ROOT}/Manifest.lock",
+			);
+			name = "[CP] Check Pods Manifest.lock";
+			outputFileListPaths = (
+			);
+			outputPaths = (
+				"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+			showEnvVarsInLog = 0;
+		};
 		9740EEB61CF901F6004384FC /* Run Script */ = {
 			isa = PBXShellScriptBuildPhase;
+			alwaysOutOfDate = 1;
 			buildActionMask = 2147483647;
 			files = (
 			);
@@ -272,7 +343,7 @@
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+				IPHONEOS_DEPLOYMENT_TARGET = 12.0;
 				MTL_ENABLE_DEBUG_INFO = NO;
 				SDKROOT = iphoneos;
 				SUPPORTED_PLATFORMS = iphoneos;
@@ -290,7 +361,10 @@
 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
 				ENABLE_BITCODE = NO;
 				INFOPLIST_FILE = Runner/Info.plist;
-				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
 				PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -346,7 +420,7 @@
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+				IPHONEOS_DEPLOYMENT_TARGET = 12.0;
 				MTL_ENABLE_DEBUG_INFO = YES;
 				ONLY_ACTIVE_ARCH = YES;
 				SDKROOT = iphoneos;
@@ -395,7 +469,7 @@
 				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
 				GCC_WARN_UNUSED_FUNCTION = YES;
 				GCC_WARN_UNUSED_VARIABLE = YES;
-				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+				IPHONEOS_DEPLOYMENT_TARGET = 12.0;
 				MTL_ENABLE_DEBUG_INFO = NO;
 				SDKROOT = iphoneos;
 				SUPPORTED_PLATFORMS = iphoneos;
@@ -414,7 +488,10 @@
 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
 				ENABLE_BITCODE = NO;
 				INFOPLIST_FILE = Runner/Info.plist;
-				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
 				PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -433,7 +510,10 @@
 				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
 				ENABLE_BITCODE = NO;
 				INFOPLIST_FILE = Runner/Info.plist;
-				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+				LD_RUNPATH_SEARCH_PATHS = (
+					"$(inherited)",
+					"@executable_path/Frameworks",
+				);
 				PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index a28140c..e67b280 100644
--- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <Scheme
-   LastUpgradeVersion = "1020"
+   LastUpgradeVersion = "1510"
    version = "1.3">
    <BuildAction
       parallelizeBuildables = "YES"
diff --git a/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcworkspace/contents.xcworkspacedata
index 1d526a1..21a3cc1 100644
--- a/example/ios/Runner.xcworkspace/contents.xcworkspacedata
+++ b/example/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -4,4 +4,7 @@
    <FileRef
       location = "group:Runner.xcodeproj">
    </FileRef>
+   <FileRef
+      location = "group:Pods/Pods.xcodeproj">
+   </FileRef>
 </Workspace>
diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist
index a060db6..4f68a2c 100644
--- a/example/ios/Runner/Info.plist
+++ b/example/ios/Runner/Info.plist
@@ -41,5 +41,9 @@
 	</array>
 	<key>UIViewControllerBasedStatusBarAppearance</key>
 	<false/>
+	<key>CADisableMinimumFrameDurationOnPhone</key>
+	<true/>
+	<key>UIApplicationSupportsIndirectInputEvents</key>
+	<true/>
 </dict>
 </plist>
diff --git a/example/lib/03.change_language_theme/constants.dart b/example/lib/03.change_language_theme/constants.dart
index 9b0379f..ff091ca 100644
--- a/example/lib/03.change_language_theme/constants.dart
+++ b/example/lib/03.change_language_theme/constants.dart
@@ -4,6 +4,7 @@ import 'package:highlight/languages/java.dart';
 import 'package:highlight/languages/php.dart';
 import 'package:highlight/languages/python.dart';
 import 'package:highlight/languages/scala.dart';
+import 'package:highlight/languages/yaml.dart';
 
 final builtinLanguages = {
   'dart': dart,
@@ -12,6 +13,7 @@ final builtinLanguages = {
   'php': php,
   'python': python,
   'scala': scala,
+  'yaml': yaml,
 };
 
 const languageList = <String>[
@@ -21,6 +23,7 @@ const languageList = <String>[
   'php',
   'python',
   'scala',
+  'yaml',
 ];
 
 const themeList = <String>[
diff --git a/lib/src/code_field/actions/tab.dart b/lib/src/code_field/actions/tab.dart
new file mode 100644
index 0000000..aa991d0
--- /dev/null
+++ b/lib/src/code_field/actions/tab.dart
@@ -0,0 +1,21 @@
+import 'package:flutter/material.dart';
+
+import '../code_controller.dart';
+
+class TabKeyIntent extends Intent {
+  const TabKeyIntent();
+}
+
+class TabKeyAction extends Action<TabKeyIntent> {
+  final CodeController controller;
+
+  TabKeyAction({
+    required this.controller,
+  });
+
+  @override
+  Object? invoke(TabKeyIntent intent) {
+    controller.onTabKeyAction();
+    return null;
+  }
+}
diff --git a/lib/src/code_field/code_controller.dart b/lib/src/code_field/code_controller.dart
index 5a4a5d9..a63ef50 100644
--- a/lib/src/code_field/code_controller.dart
+++ b/lib/src/code_field/code_controller.dart
@@ -12,6 +12,7 @@ import '../../flutter_code_editor.dart';
 import '../autocomplete/autocompleter.dart';
 import '../code/code_edit_result.dart';
 import '../code/key_event.dart';
+import '../code_modifiers/insertion.dart';
 import '../history/code_history_controller.dart';
 import '../history/code_history_record.dart';
 import '../search/controller.dart';
@@ -19,6 +20,7 @@ import '../search/result.dart';
 import '../search/search_navigation_controller.dart';
 import '../search/settings_controller.dart';
 import '../single_line_comments/parser/single_line_comments.dart';
+import '../util/string_util.dart';
 import '../wip/autocomplete/popup_controller.dart';
 import 'actions/comment_uncomment.dart';
 import 'actions/copy.dart';
@@ -28,6 +30,7 @@ import 'actions/indent.dart';
 import 'actions/outdent.dart';
 import 'actions/redo.dart';
 import 'actions/search.dart';
+import 'actions/tab.dart';
 import 'actions/undo.dart';
 import 'search_result_highlighted_builder.dart';
 import 'span_builder.dart';
@@ -50,6 +53,7 @@ class CodeController extends TextEditingController {
   /// Calls [AbstractAnalyzer.analyze] after change with 500ms debounce.
   AbstractAnalyzer get analyzer => _analyzer;
   AbstractAnalyzer _analyzer;
+
   set analyzer(AbstractAnalyzer analyzer) {
     if (_analyzer == analyzer) {
       return;
@@ -107,6 +111,7 @@ class CodeController extends TextEditingController {
 
   SearchSettingsController get _searchSettingsController =>
       searchController.settingsController;
+
   SearchNavigationController get _searchNavigationController =>
       searchController.navigationController;
 
@@ -130,8 +135,21 @@ class CodeController extends TextEditingController {
     SearchIntent: SearchAction(controller: this),
     DismissIntent: CustomDismissAction(controller: this),
     EnterKeyIntent: EnterKeyAction(controller: this),
+    TabKeyIntent: TabKeyAction(controller: this),
   };
 
+  static const defaultCodeModifiers = [
+    IndentModifier(),
+    CloseBlockModifier(),
+    TabModifier(),
+    InsertionCodeModifier.backticks,
+    InsertionCodeModifier.braces,
+    InsertionCodeModifier.brackets,
+    InsertionCodeModifier.doubleQuotes,
+    InsertionCodeModifier.parentheses,
+    InsertionCodeModifier.singleQuotes,
+  ];
+
   CodeController({
     String? text,
     Mode? language,
@@ -143,11 +161,7 @@ class CodeController extends TextEditingController {
     this.patternMap,
     this.readOnly = false,
     this.params = const EditorParams(),
-    this.modifiers = const [
-      IndentModifier(),
-      CloseBlockModifier(),
-      TabModifier(),
-    ],
+    this.modifiers = defaultCodeModifiers,
   })  : _analyzer = analyzer,
         _readOnlySectionNames = readOnlySectionNames,
         _code = Code.empty,
@@ -347,30 +361,60 @@ class CodeController extends TextEditingController {
     insertStr('\n');
   }
 
+  void onTabKeyAction() {
+    if (popupController.shouldShow) {
+      insertSelectedWord();
+      return;
+    }
+
+    insertStr(' ' * params.tabSpaces);
+  }
+
   /// Inserts the word selected from the list of completions
   void insertSelectedWord() {
     final previousSelection = selection;
     final selectedWord = popupController.getSelectedWord();
     final startPosition = value.wordAtCursorStart;
+    final currentWord = value.wordAtCursor;
 
-    if (startPosition != null) {
-      final replacedText = text.replaceRange(
-        startPosition,
-        selection.baseOffset,
-        selectedWord,
-      );
+    if (startPosition == null || currentWord == null) {
+      popupController.hide();
+      return;
+    }
 
-      final adjustedSelection = previousSelection.copyWith(
-        baseOffset: startPosition + selectedWord.length,
-        extentOffset: startPosition + selectedWord.length,
-      );
+    final endReplacingPosition = startPosition + currentWord.length;
+    final endSelectionPosition = startPosition + selectedWord.length;
 
-      value = TextEditingValue(
-        text: replacedText,
-        selection: adjustedSelection,
-      );
+    var additionalSpaceIfEnd = '';
+    var offsetIfEndsWithSpace = 1;
+    if (text.length < endReplacingPosition + 1) {
+      additionalSpaceIfEnd = ' ';
+    } else {
+      final charAfterText = text[endReplacingPosition];
+      if (charAfterText != ' ' &&
+          !StringUtil.isDigit(charAfterText) &&
+          !StringUtil.isLetterEng(charAfterText)) {
+        // ex. case ';' or other finalizer, or symbol
+        offsetIfEndsWithSpace = 0;
+      }
     }
 
+    final replacedText = text.replaceRange(
+      startPosition,
+      endReplacingPosition,
+      '$selectedWord$additionalSpaceIfEnd',
+    );
+
+    final adjustedSelection = previousSelection.copyWith(
+      baseOffset: endSelectionPosition + offsetIfEndsWithSpace,
+      extentOffset: endSelectionPosition + offsetIfEndsWithSpace,
+    );
+
+    value = TextEditingValue(
+      text: replacedText,
+      selection: adjustedSelection,
+    );
+
     popupController.hide();
   }
 
diff --git a/lib/src/code_field/code_field.dart b/lib/src/code_field/code_field.dart
index 5ea622d..ab2a38f 100644
--- a/lib/src/code_field/code_field.dart
+++ b/lib/src/code_field/code_field.dart
@@ -15,6 +15,7 @@ import 'actions/enter_key.dart';
 import 'actions/indent.dart';
 import 'actions/outdent.dart';
 import 'actions/search.dart';
+import 'actions/tab.dart';
 import 'code_controller.dart';
 import 'default_styles.dart';
 import 'js_workarounds/js_workarounds.dart';
@@ -110,6 +111,11 @@ final _shortcuts = <ShortcutActivator, Intent>{
   LogicalKeySet(
     LogicalKeyboardKey.enter,
   ): const EnterKeyIntent(),
+
+  // TabKey
+  LogicalKeySet(
+    LogicalKeyboardKey.tab,
+  ): const TabKeyIntent(),
 };
 
 class CodeField extends StatefulWidget {
@@ -139,6 +145,12 @@ class CodeField extends StatefulWidget {
   /// {@macro flutter.widgets.textField.textStyle}
   final TextStyle? textStyle;
 
+  /// {@macro flutter.widgets.textField.smartDashesType}
+  final SmartDashesType smartDashesType;
+
+  /// {@macro flutter.widgets.textField.smartQuotesType}
+  final SmartQuotesType smartQuotesType;
+
   /// A way to replace specific line numbers by a custom TextSpan
   final TextSpan Function(int, TextStyle?)? lineNumberBuilder;
 
@@ -175,6 +187,8 @@ class CodeField extends StatefulWidget {
     this.background,
     this.decoration,
     this.textStyle,
+    this.smartDashesType = SmartDashesType.disabled,
+    this.smartQuotesType = SmartQuotesType.disabled,
     this.padding = EdgeInsets.zero,
     GutterStyle? gutterStyle,
     this.enabled,
@@ -400,6 +414,8 @@ class _CodeFieldState extends State<CodeField> {
       focusNode: _focusNode,
       scrollPadding: widget.padding,
       style: textStyle,
+      smartDashesType: widget.smartDashesType,
+      smartQuotesType: widget.smartQuotesType,
       controller: widget.controller,
       minLines: widget.minLines,
       maxLines: widget.maxLines,
diff --git a/lib/src/code_modifiers/close_block_code_modifier.dart b/lib/src/code_modifiers/close_block_code_modifier.dart
index 7a71940..b40d22d 100644
--- a/lib/src/code_modifiers/close_block_code_modifier.dart
+++ b/lib/src/code_modifiers/close_block_code_modifier.dart
@@ -5,6 +5,8 @@ import 'package:flutter/widgets.dart';
 import '../code_field/editor_params.dart';
 import 'code_modifier.dart';
 
+/// [CloseBlockModifier] is an implementation of [CodeModifier]
+/// that remove spaces before the closing bracket, if required.
 class CloseBlockModifier extends CodeModifier {
   const CloseBlockModifier() : super('}');
 
diff --git a/lib/src/code_modifiers/code_modifier.dart b/lib/src/code_modifiers/code_modifier.dart
index b2a4d53..c681385 100644
--- a/lib/src/code_modifiers/code_modifier.dart
+++ b/lib/src/code_modifiers/code_modifier.dart
@@ -7,7 +7,7 @@ abstract class CodeModifier {
 
   const CodeModifier(this.char);
 
-  // Helper to insert [str] in [text] between [start] and [end]
+  /// Helper to insert [str] in [text] between [start] and [end]
   TextEditingValue replace(String text, int start, int end, String str) {
     final len = str.length;
     return TextEditingValue(
diff --git a/lib/src/code_modifiers/insertion.dart b/lib/src/code_modifiers/insertion.dart
new file mode 100644
index 0000000..ef967b9
--- /dev/null
+++ b/lib/src/code_modifiers/insertion.dart
@@ -0,0 +1,47 @@
+import 'package:flutter/services.dart';
+
+import '../code_field/editor_params.dart';
+import 'code_modifier.dart';
+
+class InsertionCodeModifier extends CodeModifier {
+  final String openChar;
+  final String closeString;
+
+  const InsertionCodeModifier({
+    required this.openChar,
+    required this.closeString,
+  }) : super(openChar);
+
+  static const backticks =
+      InsertionCodeModifier(openChar: '`', closeString: '`');
+
+  static const braces = InsertionCodeModifier(openChar: '{', closeString: '}');
+
+  static const brackets =
+      InsertionCodeModifier(openChar: '[', closeString: ']');
+
+  static const doubleQuotes =
+      InsertionCodeModifier(openChar: '"', closeString: '"');
+
+  static const parentheses =
+      InsertionCodeModifier(openChar: '(', closeString: ')');
+
+  static const singleQuotes =
+      InsertionCodeModifier(openChar: '\'', closeString: '\'');
+
+  @override
+  TextEditingValue? updateString(
+    String text,
+    TextSelection sel,
+    EditorParams params,
+  ) {
+    final replaced = replace(text, sel.start, sel.end, '$openChar$closeString');
+
+    return replaced.copyWith(
+      selection: TextSelection(
+        baseOffset: replaced.selection.baseOffset - closeString.length,
+        extentOffset: replaced.selection.extentOffset - closeString.length,
+      ),
+    );
+  }
+}
diff --git a/lib/src/folding/parsers/parser_factory.dart b/lib/src/folding/parsers/parser_factory.dart
index 6174775..586efce 100644
--- a/lib/src/folding/parsers/parser_factory.dart
+++ b/lib/src/folding/parsers/parser_factory.dart
@@ -1,9 +1,11 @@
 import 'package:highlight/highlight_core.dart';
 import 'package:highlight/languages/java.dart';
 import 'package:highlight/languages/python.dart';
+import 'package:highlight/languages/yaml.dart';
 
 import 'abstract.dart';
 import 'highlight.dart';
+import 'indent.dart';
 import 'java.dart';
 import 'python.dart';
 
@@ -15,6 +17,11 @@ class FoldableBlockParserFactory {
     if (mode == java) {
       return JavaFoldableBlockParser();
     }
+
+    if (mode == yaml) {
+      return IndentFoldableBlockParser();
+    }
+
     return HighlightFoldableBlockParser();
   }
 }
diff --git a/lib/src/single_line_comments/parser/single_line_comments.dart b/lib/src/single_line_comments/parser/single_line_comments.dart
index e23a10e..fba5c1e 100644
--- a/lib/src/single_line_comments/parser/single_line_comments.dart
+++ b/lib/src/single_line_comments/parser/single_line_comments.dart
@@ -2,9 +2,11 @@ import 'package:highlight/highlight_core.dart';
 import 'package:highlight/languages/dart.dart';
 import 'package:highlight/languages/go.dart';
 import 'package:highlight/languages/java.dart';
+import 'package:highlight/languages/javascript.dart';
 import 'package:highlight/languages/php.dart';
 import 'package:highlight/languages/python.dart';
 import 'package:highlight/languages/scala.dart';
+import 'package:highlight/languages/typescript.dart';
 import 'package:highlight/languages/vhdl.dart';
 
 class SingleLineComments {
@@ -18,6 +20,8 @@ class SingleLineComments {
     python: [_hash],
     scala: [_slashes],
     vhdl: [_hyphenMinuses],
+    typescript: [_slashes],
+    javascript: [_slashes],
   };
 
   static const _slashes = '//';
diff --git a/lib/src/util/string_util.dart b/lib/src/util/string_util.dart
new file mode 100644
index 0000000..63212cf
--- /dev/null
+++ b/lib/src/util/string_util.dart
@@ -0,0 +1,14 @@
+class StringUtil {
+  static bool isDigit(String char) {
+    if (char.isEmpty || char.length > 1) return false;
+    final codeUnit = char.codeUnitAt(0);
+    return codeUnit >= 48 && codeUnit <= 57; // Unicode range for '0' to '9'
+  }
+
+  static bool isLetterEng(String char) {
+    if (char.isEmpty || char.length > 1) return false;
+    final codeUnit = char.codeUnitAt(0);
+    return (codeUnit >= 65 && codeUnit <= 90) || // A-Z
+        (codeUnit >= 97 && codeUnit <= 122); // a-z
+  }
+}
diff --git a/lib/src/wip/autocomplete/popup.dart b/lib/src/wip/autocomplete/popup.dart
index 90e2894..a102409 100644
--- a/lib/src/wip/autocomplete/popup.dart
+++ b/lib/src/wip/autocomplete/popup.dart
@@ -45,6 +45,7 @@ class Popup extends StatefulWidget {
 
 class PopupState extends State<Popup> {
   final pageStorageBucket = PageStorageBucket();
+
   @override
   void initState() {
     widget.controller.addListener(rebuild);
@@ -68,6 +69,13 @@ class PopupState extends State<Popup> {
             (widget.editorOffset?.dx ?? 0) -
             100;
 
+    // Fixes assertion error when ISC isn't attached but _attach method
+    // of ISC instance are being called
+    ItemScrollController? isc;
+    if (widget.controller.itemScrollController.isAttached) {
+      isc = widget.controller.itemScrollController;
+    }
+
     return PageStorage(
       bucket: pageStorageBucket,
       child: Positioned(
@@ -97,7 +105,7 @@ class PopupState extends State<Popup> {
             child: ScrollablePositionedList.builder(
               shrinkWrap: true,
               physics: const ClampingScrollPhysics(),
-              itemScrollController: widget.controller.itemScrollController,
+              itemScrollController: isc,
               itemPositionsListener: widget.controller.itemPositionsListener,
               itemCount: widget.controller.suggestions.length,
               itemBuilder: (context, index) {
diff --git a/lib/src/wip/autocomplete/popup_controller.dart b/lib/src/wip/autocomplete/popup_controller.dart
index bbbc388..de081b4 100644
--- a/lib/src/wip/autocomplete/popup_controller.dart
+++ b/lib/src/wip/autocomplete/popup_controller.dart
@@ -24,7 +24,7 @@ class PopupController extends ChangeNotifier {
   int get selectedIndex => _selectedIndex;
 
   void show(List<String> suggestions) {
-    if (enabled == false) {
+    if (!enabled) {
       return;
     }
 


Test patch:
diff --git a/test/src/code/code_read_only_test.dart b/test/src/code/code_read_only_test.dart
index 26e63df..01aadd0 100644
--- a/test/src/code/code_read_only_test.dart
+++ b/test/src/code/code_read_only_test.dart
@@ -3,6 +3,8 @@ import 'package:flutter_code_editor/src/named_sections/parsers/brackets_start_en
 import 'package:flutter_test/flutter_test.dart';
 import 'package:highlight/languages/angelscript.dart';
 import 'package:highlight/languages/java.dart';
+import 'package:highlight/languages/javascript.dart';
+import 'package:highlight/languages/typescript.dart';
 
 final _language = java;
 
@@ -102,5 +104,55 @@ public class MyClass {
         expected,
       );
     });
+
+    test('Lines in read-only sections are read-only for JS/TS Language', () {
+      const text = '''
+export class MyTypeScriptClass {
+  run() { // [START section1]
+  }
+  // [END section1]
+  // [START section2]
+  method() {
+  }
+  // [END section2]
+}
+''';
+      const expected = [
+        false,
+        true,
+        true,
+        true,
+        false,
+        false,
+        false,
+        false,
+        false,
+        false,
+      ];
+
+      final codeTypescript = Code(
+        text: text,
+        namedSectionParser: const BracketsStartEndNamedSectionParser(),
+        readOnlySectionNames: {'section1', 'nonexistent'},
+        language: typescript,
+      );
+
+      final codeJavascript = Code(
+        text: text,
+        namedSectionParser: const BracketsStartEndNamedSectionParser(),
+        readOnlySectionNames: {'section1', 'nonexistent'},
+        language: javascript,
+      );
+
+      expect(
+        codeTypescript.lines.lines.map((line) => line.isReadOnly),
+        expected,
+      );
+
+      expect(
+        codeJavascript.lines.lines.map((line) => line.isReadOnly),
+        expected,
+      );
+    });
   });
 }
diff --git a/test/src/code_modifiers/controller_insertion_test.dart b/test/src/code_modifiers/controller_insertion_test.dart
new file mode 100644
index 0000000..9019e70
--- /dev/null
+++ b/test/src/code_modifiers/controller_insertion_test.dart
@@ -0,0 +1,149 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_code_editor/flutter_code_editor.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+  test('Insertion modifier test', () {
+    const examples = [
+      //
+      _Example(
+        'Add backticks',
+        initialValue: TextEditingValue(
+          text: 'dict',
+          //     \ cursor
+          selection: TextSelection.collapsed(offset: 0),
+        ),
+        expected: TextEditingValue(
+          text: '``dict',
+          //      \ cursor
+          selection: TextSelection.collapsed(offset: 1),
+        ),
+        inputChar: '`',
+      ),
+
+      _Example(
+        'Add char at the start of the string (braces)',
+        initialValue: TextEditingValue(
+          text: 'dict',
+          //     \ cursor
+          selection: TextSelection.collapsed(offset: 0),
+        ),
+        expected: TextEditingValue(
+          text: '{}dict',
+          //      \ cursor
+          selection: TextSelection.collapsed(offset: 1),
+        ),
+        inputChar: '{',
+      ),
+
+      _Example(
+        'Add char in the middle of the string (parentheses)',
+        initialValue: TextEditingValue(
+          text: 'print',
+          //        \ cursor
+          selection: TextSelection.collapsed(offset: 3),
+        ),
+        expected: TextEditingValue(
+          text: 'pri()nt',
+          //         \ cursor
+          selection: TextSelection.collapsed(offset: 4),
+        ),
+        inputChar: '(',
+      ),
+
+      _Example(
+        'Add char at the end of the string (brackets)',
+        initialValue: TextEditingValue(
+          text: 'print',
+          //          \ cursor
+          selection: TextSelection.collapsed(offset: 5),
+        ),
+        expected: TextEditingValue(
+          text: 'print[]',
+          //           \ cursor
+          selection: TextSelection.collapsed(offset: 6),
+        ),
+        inputChar: '[',
+      ),
+
+      _Example(
+        'Add close char before same close char (double quotes)',
+        initialValue: TextEditingValue(
+          text: 'string"',
+          //           \ cursor
+          selection: TextSelection.collapsed(offset: 6),
+        ),
+        expected: TextEditingValue(
+          text: 'string"""',
+          //            \ cursor
+          selection: TextSelection.collapsed(offset: 7),
+        ),
+        inputChar: '"',
+      ),
+
+      _Example(
+        'Empty initial string (single quotes)',
+        initialValue: TextEditingValue(
+          // ignore: avoid_redundant_argument_values
+          text: '',
+          //     \ cursor
+          selection: TextSelection.collapsed(offset: 0),
+        ),
+        expected: TextEditingValue(
+          text: '\'\'',
+          //       \ cursor
+          selection: TextSelection.collapsed(offset: 1),
+        ),
+        inputChar: '\'',
+      ),
+    ];
+
+    for (final example in examples) {
+      final controller = CodeController();
+      controller.value = example.initialValue;
+      controller.value = _addCharToSelectedPosition(
+        controller.value,
+        example.inputChar,
+      );
+
+      expect(
+        controller.value,
+        example.expected,
+        reason: example.name,
+      );
+    }
+  });
+}
+
+TextEditingValue _addCharToSelectedPosition(
+  TextEditingValue value,
+  String char,
+) {
+  final selection = value.selection;
+  final text = value.text;
+
+  final newText = text.substring(0, selection.start) +
+      char +
+      text.substring(selection.start);
+
+  return TextEditingValue(
+    text: newText,
+    selection: TextSelection.collapsed(
+      offset: selection.start + char.length,
+    ),
+  );
+}
+
+class _Example {
+  final String name;
+  final TextEditingValue initialValue;
+  final TextEditingValue expected;
+  final String inputChar;
+
+  const _Example(
+    this.name, {
+    required this.initialValue,
+    required this.expected,
+    required this.inputChar,
+  });
+}
diff --git a/test/src/code_modifiers/insertion_test.dart b/test/src/code_modifiers/insertion_test.dart
new file mode 100644
index 0000000..7c63875
--- /dev/null
+++ b/test/src/code_modifiers/insertion_test.dart
@@ -0,0 +1,73 @@
+import 'package:flutter/services.dart';
+import 'package:flutter_code_editor/src/code_field/editor_params.dart';
+import 'package:flutter_code_editor/src/code_modifiers/insertion.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+  group('InsertionCodeModifier', () {
+    const modifier = InsertionCodeModifier(openChar: '1', closeString: '23');
+    const editorParams = EditorParams();
+
+    test('inserts at the start of string correctly', () {
+      const text = 'Hello World';
+      final selection =
+          TextSelection.fromPosition(const TextPosition(offset: 0));
+
+      final result = modifier.updateString(text, selection, editorParams);
+
+      expect(result!.text, '123Hello World');
+      expect(result.selection.baseOffset, 1);
+      expect(result.selection.extentOffset, 1);
+    });
+
+    test('inserts in the middle of string correctly', () {
+      const text = 'Hello World';
+      final selection =
+          TextSelection.fromPosition(const TextPosition(offset: 5));
+
+      final result = modifier.updateString(text, selection, editorParams);
+
+      expect(result!.text, 'Hello123 World');
+      expect(result.selection.baseOffset, 6);
+      expect(result.selection.extentOffset, 6);
+    });
+
+    test('inserts at the end of string correctly', () {
+      const text = 'Hello World';
+      final selection =
+          TextSelection.fromPosition(const TextPosition(offset: text.length));
+
+      final result = modifier.updateString(text, selection, editorParams);
+
+      expect(result!.text, 'Hello World123');
+      expect(result.selection.baseOffset, text.length + 1);
+      expect(result.selection.extentOffset, text.length + 1);
+    });
+
+    test('inserts in the middle of string with selection correctly', () {
+      const text = 'Hello World';
+      const selection = TextSelection(
+        baseOffset: 5,
+        extentOffset: 7,
+      );
+
+      final result = modifier.updateString(text, selection, editorParams);
+
+      expect(result!.text, 'Hello123orld');
+      expect(result.selection.baseOffset, 6);
+      expect(result.selection.extentOffset, 6);
+    });
+
+    test('inserts at empty string correctly', () {
+      const text = '';
+      final selection =
+          TextSelection.fromPosition(const TextPosition(offset: 0));
+
+      final result = modifier.updateString(text, selection, editorParams);
+
+      expect(result!.text, '123');
+      expect(result.selection.baseOffset, 1);
+      expect(result.selection.extentOffset, 1);
+    });
+  });
+}
diff --git a/test/src/search/code_controller_test.dart b/test/src/search/code_controller_test.dart
index cd3c96f..35260bb 100644
--- a/test/src/search/code_controller_test.dart
+++ b/test/src/search/code_controller_test.dart
@@ -1,4 +1,5 @@
 import 'package:flutter/services.dart';
+import 'package:flutter_code_editor/flutter_code_editor.dart';
 import 'package:flutter_code_editor/src/search/match.dart';
 import 'package:flutter_code_editor/src/search/result.dart';
 import 'package:flutter_code_editor/src/search/settings.dart';
@@ -7,7 +8,7 @@ import 'package:flutter_test/flutter_test.dart';
 import '../common/create_app.dart';
 
 void main() {
-  group('CodeController', () {
+  group('CodeController, Search-related functionality', () {
     testWidgets('CTRL + F shows search, Escape hides', (wt) async {
       const text = 'AaAa';
       final controller = await pumpController(wt, text);
@@ -60,4 +61,102 @@ void main() {
       await wt.pumpAndSettle();
     });
   });
+
+  /// Requested to add at {@link https://github.com/akvelon/flutter-code-editor/pull/231}
+  group('CodeController-related formatting checks [Default params]', () {
+    /// tests insertion of existing modifiers into the code controller
+    /// at the defined index
+    void testInsertionAtIndex(
+      CodeController controller,
+      String initialText,
+      String insertedStart,
+      String insertedEnd,
+      int insertionIndex,
+    ) {
+      final selection = TextSelection(
+        baseOffset: insertionIndex,
+        extentOffset: insertionIndex,
+      );
+      // to move selection of textEditingValue at defined place
+      controller.value = TextEditingValue(
+        text: initialText,
+        selection: selection,
+      );
+
+      final textWithInsertedStart = initialText.replaceRange(
+        insertionIndex,
+        insertionIndex,
+        insertedStart,
+      );
+      controller.value = TextEditingValue(
+        text: textWithInsertedStart,
+        selection: selection,
+      );
+
+      final expectedText = initialText.replaceRange(
+        insertionIndex,
+        insertionIndex,
+        '$insertedStart$insertedEnd',
+      );
+      expect(controller.value.text, expectedText);
+    }
+
+    /// tests insertion at the start, middle and end of the initial text
+    Future<void> testInsertion(
+      WidgetTester wt,
+      String insertedStart, {
+      String? insertedEnd,
+    }) async {
+      insertedEnd ??= insertedStart;
+
+      const initialText = 'Hello';
+      final controller = await pumpController(wt, initialText);
+
+      testInsertionAtIndex(
+        controller,
+        initialText,
+        insertedStart,
+        insertedEnd,
+        0,
+      );
+      testInsertionAtIndex(
+        controller,
+        initialText,
+        insertedStart,
+        insertedEnd,
+        2,
+      );
+      testInsertionAtIndex(
+        controller,
+        initialText,
+        insertedStart,
+        insertedEnd,
+        initialText.length,
+      );
+    }
+
+    testWidgets('controller handles insertion of backticks', (wt) async {
+      await testInsertion(wt, '`');
+    });
+
+    testWidgets('controller handles insertion of single quotes', (wt) async {
+      await testInsertion(wt, '\'');
+    });
+
+    testWidgets('controller handles insertion of double quotes', (wt) async {
+      await testInsertion(wt, '"');
+    });
+
+    testWidgets('controller handles insertion of parentheses', (wt) async {
+      await testInsertion(wt, '(', insertedEnd: ')');
+    });
+
+    testWidgets('controller handles insertion of braces', (wt) async {
+      await testInsertion(wt, '{', insertedEnd: '}');
+    });
+
+    testWidgets('controller handles insertion of square brackets', (wt) async {
+      await testInsertion(wt, '[', insertedEnd: ']');
+    });
+  });
 }