From patchwork Fri Jan 30 03:58:24 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 1776 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org Authentication-Results: mail.u-boot.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.a=rsa-sha256 header.s=google header.b=RZFdBeTd; dkim-atps=neutral Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 94DB7697D6 for ; Thu, 29 Jan 2026 20:59:10 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 0asIg1DZ-eq2 for ; Thu, 29 Jan 2026 20:59:10 -0700 (MST) Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 83EC0697CD for ; Thu, 29 Jan 2026 20:59:10 -0700 (MST) Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 783CE697CD for ; Thu, 29 Jan 2026 20:59:08 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 9jUhYwca_Y6T for ; Thu, 29 Jan 2026 20:59:08 -0700 (MST) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=209.85.161.41; helo=mail-oo1-f41.google.com; envelope-from=sjg@chromium.org; receiver=u-boot.org Received: from mail-oo1-f41.google.com (mail-oo1-f41.google.com [209.85.161.41]) by mail.u-boot.org (Postfix) with ESMTPS id 1C63369738 for ; Thu, 29 Jan 2026 20:59:03 -0700 (MST) Received: by mail-oo1-f41.google.com with SMTP id 006d021491bc7-662f9aeb782so1163704eaf.3 for ; Thu, 29 Jan 2026 19:59:03 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1769745542; x=1770350342; darn=u-boot.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=bMHxMvRvwrZCyQYyE/JPKA4ENIUUDPipLjBTLIhic+Q=; b=RZFdBeTdSU64zUT3G3Q/PVJVkm43JmoIYxIqLIgKBfMuepk5+Z9Sf75pxYMpTm3ami 18vhrJsdN2N4X/+8s40Lr9XpbkTwSEUfkxqT0SScgNHE6cA95CJcGcGsTYDko0zU5v9/ wcddqFeATXjUqwnMCa6ItvBXYVDTvQeBBkeBU= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769745542; x=1770350342; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=bMHxMvRvwrZCyQYyE/JPKA4ENIUUDPipLjBTLIhic+Q=; b=nf7SBCBzuZQSQ1McyegbRCrQ08sMN+LuRdU6UrR2MC/74Tu4MYh05ORgJ3nLvWmXVe WJUDs8nnr05PHLPx+RYavR1RE/1Ay1jvMj6zenNnpA+r6EODRCaSPQS+CgiS/d9L4lH9 7y70mW0rqOIkcYADTo1qPUZL2+5yHx1mxCXJdpWQtlnwtNdgyRWbZXFAYmnSkF1JsT63 kzw0oTQUC/Uzzh3Mok7bAxwRjc3oe8VPs9MckGMKXILog3Gu98PffwgDlUlpEHumZIPk agVmPrc76Wj7cmQTG5SnjYQYp+Df1DgOQ+hW2wIpRI8FEkrkn2JDjrbgUoSO5Cs5MZoQ lbjw== X-Gm-Message-State: AOJu0Yxg3Sv9TbwZT9EV6OdfW5rMFAbvnop6SOKlPcdCr76L2yYDYwXJ 3SHJQt0PwOgj3OvS7XZbu+dOdvoMsfkE8aYfYocN3cu+vwkgtxX2e0y9m/D4pAd54t/zNVUAYqi q6EB35w== X-Gm-Gg: AZuq6aIfOZjPIyg4flIpda+5XnpM3vkyTYujL+U43q5/TKamHTNja7BVx9XJsL4nvzG QQ/UYxgYMCHLij2bobl5Tx5yIw9utYRI6ntRPLlUKjNusPneygbxLHRAJr/e1pptlKeanyc2/QF d9ALmq3GrN3E94ub0Rz4ISYBjbGl7YxgJbvvfyCnrHZllELOPge/RDKC22k6AnC+kMRxssLdjxp ViYQzt0x0ogZFvXUki7qS16kJwPvGVsSNsriy65+C0W3Ew/3nrXX/YatAiRY5OO6ZDfytKjQ158 eFYr+oO5VFunm+IyUnpcMt293oz+gfOXRTXoCBdJnPK0xxLRnqQpY0LOYKOAbmv0dj60QvaF/Gp hbo1GS6cEZ5yykPuZUt44MlDlrU0MvMZ1URP2+dGasSGOzGtpUWWBlFkxENhmcSsmTRFZTxA7Z5 YhCPtDt/IoJn46GhZM X-Received: by 2002:a05:6820:22a9:b0:663:96:a8e4 with SMTP id 006d021491bc7-6630f04040dmr817410eaf.26.1769745541861; Thu, 29 Jan 2026 19:59:01 -0800 (PST) Received: from chromium.org ([73.34.74.121]) by smtp.gmail.com with ESMTPSA id 006d021491bc7-662f9a4e491sm4128687eaf.16.2026.01.29.19.58.59 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Jan 2026 19:59:00 -0800 (PST) From: Simon Glass X-Google-Original-From: Simon Glass To: U-Boot Concept Date: Thu, 29 Jan 2026 20:58:24 -0700 Message-ID: <20260130035849.3580212-2-simon.glass@canonical.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260130035849.3580212-1-simon.glass@canonical.com> References: <20260130035849.3580212-1-simon.glass@canonical.com> MIME-Version: 1.0 Message-ID-Hash: DB5ROV6KFHYL7CWSQ4XTKKOUAMFYJUC2 X-Message-ID-Hash: DB5ROV6KFHYL7CWSQ4XTKKOUAMFYJUC2 X-MailFrom: sjg@chromium.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass , "Claude Opus 4 . 5" X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 01/19] buildman: Fix merge_config.sh path when using work directories List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: The run_merge_config() function constructs paths for merge_config.sh using out_dir and cfg_file which are relative to the original working directory. However, the commands run with cwd=src_dir (the work directory), so these paths resolve incorrectly. For example, with src_dir='../exph/.bm-work/00' and out_dir='../exph/.bm-work/00/build', the -O flag would pass the full out_dir path. When make runs from src_dir, it interprets this as '../exph/.bm-work/00/../exph/.bm-work/00/build', doubling the path. Fix this by converting out_dir and cfg_file to paths relative to src_dir using os.path.relpath(). This ensures the paths resolve correctly when commands execute from the work directory. Fixes: 635c5f5638a0 ("buildman: Use merge_config.sh for --adjust-cfg") Co-developed-by: Claude Opus 4.5 Signed-off-by: Simon Glass --- tools/buildman/cfgutil.py | 21 ++++++++++----- tools/buildman/test_cfgutil.py | 48 ++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 7 deletions(-) diff --git a/tools/buildman/cfgutil.py b/tools/buildman/cfgutil.py index cec33e1e62b..060f2762b96 100644 --- a/tools/buildman/cfgutil.py +++ b/tools/buildman/cfgutil.py @@ -368,9 +368,18 @@ def run_merge_config(src_dir, out_dir, cfg_file, adjust_cfg, env): # Create a minimal defconfig from the current .config # This is necessary for 'imply' to work - the full .config has # '# CONFIG_xxx is not set' lines that prevent imply from taking effect - defconfig_path = os.path.join(out_dir or '.', 'defconfig') - make_cmd = ['make', f'O={out_dir}' if out_dir else None, - f'KCONFIG_CONFIG={cfg_file}', 'savedefconfig'] + # + # Convert paths to be relative to src_dir since commands run with + # cwd=src_dir + if src_dir and out_dir: + rel_out_dir = os.path.relpath(out_dir, src_dir) + rel_cfg_file = os.path.relpath(cfg_file, src_dir) + else: + rel_out_dir = out_dir or '.' + rel_cfg_file = cfg_file + defconfig_path = os.path.join(rel_out_dir, 'defconfig') + make_cmd = ['make', f'O={rel_out_dir}' if rel_out_dir != '.' else None, + f'KCONFIG_CONFIG={rel_cfg_file}', 'savedefconfig'] make_cmd = [x for x in make_cmd if x] # Remove None elements result = command.run_one(*make_cmd, cwd=src_dir, env=env, capture=True, capture_stderr=True) @@ -382,10 +391,8 @@ def run_merge_config(src_dir, out_dir, cfg_file, adjust_cfg, env): try: # Run merge_config.sh with the minimal defconfig as base # -O sets output dir; defconfig is the base, fragment is merged - merge_script = os.path.join(src_dir or '.', 'scripts', 'kconfig', - 'merge_config.sh') - out = out_dir or '.' - cmd = [merge_script, '-O', out, defconfig_path, frag_path] + merge_script = os.path.join('scripts', 'kconfig', 'merge_config.sh') + cmd = [merge_script, '-O', rel_out_dir, defconfig_path, frag_path] result = command.run_one(*cmd, cwd=src_dir, env=env, capture=True, capture_stderr=True) finally: diff --git a/tools/buildman/test_cfgutil.py b/tools/buildman/test_cfgutil.py index 47e522d3d6c..b623a4c4f67 100644 --- a/tools/buildman/test_cfgutil.py +++ b/tools/buildman/test_cfgutil.py @@ -180,6 +180,54 @@ class TestAdjustCfg(unittest.TestCase): result) +class TestRunMergeConfig(unittest.TestCase): + """Tests for run_merge_config() function""" + + def test_merge_script_path(self): + """Test that merge_config.sh path is relative to cwd, not absolute""" + from unittest import mock + from u_boot_pylib import command + + # Track commands that were run + commands_run = [] + + def mock_run_one(*args, **kwargs): + commands_run.append((args, kwargs)) + result = command.CommandResult() + result.return_code = 0 + result.stdout = '' + result.stderr = '' + return result + + with mock.patch.object(command, 'run_one', mock_run_one): + with mock.patch('os.path.exists', return_value=True): + with mock.patch('os.unlink'): + # Use a work directory path like buildman does + src_dir = '../branch/.bm-work/00' + cfgutil.run_merge_config( + src_dir, 'build', 'build/.config', + {'LOCALVERSION_AUTO': '~LOCALVERSION_AUTO'}, {}) + + # Find the merge_config.sh command + merge_cmd = None + for args, kwargs in commands_run: + if args and 'merge_config.sh' in args[0]: + merge_cmd = args + merge_cwd = kwargs.get('cwd') + break + + self.assertIsNotNone(merge_cmd, 'merge_config.sh command not found') + + # The script path should be relative, not include src_dir + script_path = merge_cmd[0] + self.assertEqual('scripts/kconfig/merge_config.sh', script_path, + f'Script path should be relative, got: {script_path}') + + # The cwd should be src_dir + self.assertEqual(src_dir, merge_cwd, + f'cwd should be src_dir, got: {merge_cwd}') + + class TestProcessConfig(unittest.TestCase): """Tests for process_config() function""" From patchwork Fri Jan 30 03:58:25 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 1778 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org Authentication-Results: mail.u-boot.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.a=rsa-sha256 header.s=google header.b=Vq491uQk; dkim-atps=neutral Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 7F9D4697CD for ; Thu, 29 Jan 2026 20:59:18 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id TjfMMfTAHjW2 for ; Thu, 29 Jan 2026 20:59:18 -0700 (MST) Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 81924697E0 for ; Thu, 29 Jan 2026 20:59:16 -0700 (MST) Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id F23C3697CB for ; Thu, 29 Jan 2026 20:59:12 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id VRzV2wQEt1wD for ; Thu, 29 Jan 2026 20:59:12 -0700 (MST) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=209.85.210.43; helo=mail-ot1-f43.google.com; envelope-from=sjg@chromium.org; receiver=u-boot.org Received: from mail-ot1-f43.google.com (mail-ot1-f43.google.com [209.85.210.43]) by mail.u-boot.org (Postfix) with ESMTPS id A5E98697C8 for ; Thu, 29 Jan 2026 20:59:06 -0700 (MST) Received: by mail-ot1-f43.google.com with SMTP id 46e09a7af769-7d18dd2adf7so991330a34.1 for ; Thu, 29 Jan 2026 19:59:06 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1769745545; x=1770350345; darn=u-boot.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=HFod0DCEXCMVnlrA1DV5T4WXtObYF4fj2+3JZiJK8QI=; b=Vq491uQkPUPbmDcqO5u1w+00zE9y5Hzj7esXd6Jhz+uW/1I/li6BR6XrMP4tjtzmWf HEm9HsJAdBHb2dT8KZconhOA1CfsBdJ87kX78mtrzZMOrIFtZCtwNqWb5osJYO74/aYl PVmgf7a/K8VM3IPR0kCm90Knk4h9zonvYoan8= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769745545; x=1770350345; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=HFod0DCEXCMVnlrA1DV5T4WXtObYF4fj2+3JZiJK8QI=; b=on8o/0LlQXsdiyaPPW67ExqHP9u8I8Gf5oiERq/v2DYw/+XEzWKD2F2yIuQzj9QIXO CUUmMytccx/KiNxKbWE55zF4BourVjesB0jBK3qcayvvXDHBf2OWcxDZYtSsGTzyXeY8 SA8YEIeLJusq8e9Erp/eRiTc/rwnmxI0yK7crA951vDH7wRr7P6DsR3dghGgD+p/3+iq W1T5z+3ijWttxNW5A22GyR1+09+KMuFOHfdiQG/YqlqSJtbdMk6tkR3dL8fMxYhyGPIa 41fGP22A8GlcOeDnqWsCuVB0cWAsZ8AZdHcNJDpnTVTxFqjmxBaH8uaOZxMg5UyBCneH g06A== X-Gm-Message-State: AOJu0Yz63nOJBs6bASr0+13KudNAUj0LlCred+NOtQE2TgSp3k9W5MBp OYkrFkx+Zj4hItBIOxQAMHkOtEF1o6N1C0sQiRTLQ0MlpZKbAejyUJONLG6NrEw/V8S73n7CTNb BPK2wzQ== X-Gm-Gg: AZuq6aIHGnQwTdu2h3/eQuVbxUOgHkBF1LaKfPV2wht4kDuCtEiFQM/OfGDmDEWTkrK eFljho72qTyF3PCSyewW6bYg19ybWvI8wh14itpgQwk5A4Yk/pjf4jRSv/DnL32w7Xx4+U+mpTo SjxdILV+he4mq4oRE9dfhkxVd1DRtGlzYpcfnCyEU4pUxeELBWw7v6kE/howUGF4lCINdPMQS2t b7zxRgqS9PeCaRk3L9Ke+G8MjgvYCT3bMp27Kx6/9rdlsuPwu3vcRnqT6asqxBqen9FNYf+VbmE qJDRGl3tLQFUrpW+xhY53h4vxN4khfcoukjunq1ARa/RssnbCNxhdf/DbFSkaBEfXaU56iHyl6Z o1vcsII6HUIDj/MZ3ZyabXWhT/eaG7Z8LaSmVjGDfgxdgHXuVraLrlpOl9tX10x3orrf2M5tSVR ylAIj2stKw7wTcBj+e X-Received: by 2002:a05:6820:1787:b0:662:f74d:69f7 with SMTP id 006d021491bc7-6630f3847fcmr808954eaf.54.1769745545061; Thu, 29 Jan 2026 19:59:05 -0800 (PST) Received: from chromium.org ([73.34.74.121]) by smtp.gmail.com with ESMTPSA id 006d021491bc7-662f9a4e491sm4128687eaf.16.2026.01.29.19.59.02 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Jan 2026 19:59:03 -0800 (PST) From: Simon Glass X-Google-Original-From: Simon Glass To: U-Boot Concept Date: Thu, 29 Jan 2026 20:58:25 -0700 Message-ID: <20260130035849.3580212-3-simon.glass@canonical.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260130035849.3580212-1-simon.glass@canonical.com> References: <20260130035849.3580212-1-simon.glass@canonical.com> MIME-Version: 1.0 Message-ID-Hash: A5GTT6TKNYXSVXEEMEIVT452ELNRNADJ X-Message-ID-Hash: A5GTT6TKNYXSVXEEMEIVT452ELNRNADJ X-MailFrom: sjg@chromium.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass , "Claude Opus 4 . 5" X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 02/19] buildman: Add option to build then show summary List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: The -s option shows a summary of existing build results, but requires that a build has already been done. Add a -z/--build-summary option which performs the build first, then shows the summary afterwards. The summary is shown regardless of whether the build succeeds, and the return code reflects the build result. Co-developed-by: Claude Opus 4.5 Signed-off-by: Simon Glass --- tools/buildman/buildman.rst | 5 +- tools/buildman/cmdline.py | 2 + tools/buildman/control.py | 4 ++ tools/buildman/test.py | 110 ++++++++++++++++++++++++++++++++++++ 4 files changed, 120 insertions(+), 1 deletion(-) diff --git a/tools/buildman/buildman.rst b/tools/buildman/buildman.rst index 603b019540b..831431670d3 100644 --- a/tools/buildman/buildman.rst +++ b/tools/buildman/buildman.rst @@ -111,7 +111,10 @@ Otherwise buildman will perform random actions. Use -n to check what the random actions might be. Buildman effectively has two modes: without -s it builds, with -s it -summarises the results of previous (or active) builds. +summarises the results of previous (or active) builds. You can combine +both with -z (--build-summary), which builds first and then shows the +summary. This is useful when you want to do a build and immediately see +a summary of the results without running buildman twice. If you just want to build the current source tree, leave off the -b flag. This will display results and errors as they happen. You can still look at diff --git a/tools/buildman/cmdline.py b/tools/buildman/cmdline.py index fae1910307b..f4a1a3d018c 100644 --- a/tools/buildman/cmdline.py +++ b/tools/buildman/cmdline.py @@ -185,6 +185,8 @@ def add_after_m(parser): parser.add_argument('-Y', '--filter-migration-warnings', action='store_true', default=False, help='Filter out migration warnings from output') + parser.add_argument('-z', '--build-summary', action='store_true', + default=False, help='Build first, then show a summary of the results') def parse_args(): diff --git a/tools/buildman/control.py b/tools/buildman/control.py index 728389d9a8b..7fc0d20574a 100644 --- a/tools/buildman/control.py +++ b/tools/buildman/control.py @@ -573,6 +573,10 @@ def run_builder(builder, commits, board_selected, display_options, args): fail, warned, excs = builder.build_boards( commits, board_selected, args.keep_outputs, args.verbose, args.fragments) + if args.build_summary: + builder.commits = commits + builder.result_handler.show_summary( + commits, board_selected, args.step) if excs: return 102 if fail: diff --git a/tools/buildman/test.py b/tools/buildman/test.py index 0f4a5b9e543..37930ad9720 100644 --- a/tools/buildman/test.py +++ b/tools/buildman/test.py @@ -1222,6 +1222,116 @@ class TestBuildMisc(TestBuildBase): 'other_board')) +class TestBuildSummary(TestBuildBase): + """Tests for build summary functionality""" + + def test_build_summary(self): + """Test --build-summary option builds then shows summary""" + opts = DisplayOptions( + show_errors=False, show_sizes=False, show_detail=False, + show_bloat=False, show_config=False, show_environment=False, + show_unknown=False, ide=False, list_error_boards=False) + build = builder.Builder(self.toolchains, self.base_dir, None, 1, + 2, self._col, ResultHandler(self._col, opts), + checkout=False) + build._result_handler.set_builder(build) + + # Track calls to build_boards and show_summary + build_boards_called = [] + show_summary_called = [] + + def mock_build_boards(*args, **kwargs): + build_boards_called.append(True) + return False, False, False + + def mock_show_summary(*args, **kwargs): + show_summary_called.append(True) + + build.build_boards = mock_build_boards + build._result_handler.show_summary = mock_show_summary + + # Create args with build_summary=True + class Args: + summary = False + build_summary = True + step = 1 + keep_outputs = False + verbose = False + fragments = '' + ignore_warnings = False + ide = False + filter_dtb_warnings = False + filter_migration_warnings = False + git = '.' + threads = 1 + jobs = 1 + + args = Args() + board_selected = self.brds.get_selected_dict() + + # Mock gnu_make detection + with patch.object(command, 'output', return_value='make'): + control.run_builder(build, self.commits, board_selected, + opts, args) + + # Verify both build_boards and show_summary were called + self.assertEqual(1, len(build_boards_called), + 'build_boards should be called once') + self.assertEqual(1, len(show_summary_called), + 'show_summary should be called once') + + def test_build_summary_with_failures(self): + """Test --build-summary shows summary even when build fails""" + opts = DisplayOptions( + show_errors=False, show_sizes=False, show_detail=False, + show_bloat=False, show_config=False, show_environment=False, + show_unknown=False, ide=False, list_error_boards=False) + build = builder.Builder(self.toolchains, self.base_dir, None, 1, + 2, self._col, ResultHandler(self._col, opts), + checkout=False) + build._result_handler.set_builder(build) + + show_summary_called = [] + + def mock_build_boards(*args, **kwargs): + # Simulate build failure + return True, False, False + + def mock_show_summary(*args, **kwargs): + show_summary_called.append(True) + + build.build_boards = mock_build_boards + build._result_handler.show_summary = mock_show_summary + + class Args: + summary = False + build_summary = True + step = 1 + keep_outputs = False + verbose = False + fragments = '' + ignore_warnings = False + ide = False + filter_dtb_warnings = False + filter_migration_warnings = False + git = '.' + threads = 1 + jobs = 1 + + args = Args() + board_selected = self.brds.get_selected_dict() + + with patch.object(command, 'output', return_value='make'): + ret = control.run_builder(build, self.commits, board_selected, + opts, args) + + # Summary should still be shown even with failures + self.assertEqual(1, len(show_summary_called), + 'show_summary should be called even on failure') + # Return code should indicate failure + self.assertEqual(100, ret) + + class TestBuilderFuncs(TestBuildBase): """Tests for individual Builder methods""" From patchwork Fri Jan 30 03:58:26 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 1777 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org Authentication-Results: mail.u-boot.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.a=rsa-sha256 header.s=google header.b=k1/LXDmP; dkim-atps=neutral Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id BE657697CB for ; Thu, 29 Jan 2026 20:59:15 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id ut5RtjAEC2zi for ; Thu, 29 Jan 2026 20:59:15 -0700 (MST) Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id AC02A697C8 for ; Thu, 29 Jan 2026 20:59:15 -0700 (MST) Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id E12BE697DD for ; Thu, 29 Jan 2026 20:59:12 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id G3gXZUhnRwrX for ; Thu, 29 Jan 2026 20:59:12 -0700 (MST) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=209.85.161.50; helo=mail-oo1-f50.google.com; envelope-from=sjg@chromium.org; receiver=u-boot.org Received: from mail-oo1-f50.google.com (mail-oo1-f50.google.com [209.85.161.50]) by mail.u-boot.org (Postfix) with ESMTPS id 73E45697CB for ; Thu, 29 Jan 2026 20:59:08 -0700 (MST) Received: by mail-oo1-f50.google.com with SMTP id 006d021491bc7-662feac825eso847707eaf.3 for ; Thu, 29 Jan 2026 19:59:08 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1769745547; x=1770350347; darn=u-boot.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=wFTwxUxc0oFAqcVp00llX3LaAY8XsK4pAa8zl+Q5Nmg=; b=k1/LXDmP3EyEDzWBIHPqnhlzMGV8q+8la+NS87LtQItLQkAT83l57qH06vsoyaLeYM E9CBwHsEMKPQ1uCMMXn+IdTR81Y2kvbKWsRRkNL9T6PFmE0pBVoQ1/gbrx4+ngOnEXR0 cbfNPhnGIWlJEIgnkRzjG6GL87QHcqS450SPk= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769745547; x=1770350347; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=wFTwxUxc0oFAqcVp00llX3LaAY8XsK4pAa8zl+Q5Nmg=; b=PtgxblGRYX/976LM4vKLNmiIIuOILmsHRgAPLmkgnqP6bNG7XtfLmV4B0oGlD6hLFE 1Z9hmRj26wln/KBQ6cNFluwTEZgXxQZXK8QfROBaXXkiZMLzpUCNVpFgyi34iDbik1j/ 2VTpOedX1F4EIVViGGDfivTuysROoyhYl97pHffoyDknSQPQEej93KPA6nOzntNU+VRf s7G8nHEqUk3mw1dnifBMI9amIm78fpyiYos0pbfOPNDQtPd83z8zVVba78gNYXq+0j0X qq4OVkHvsHeJzlcM9h8E+J4iHc6Jen4iLJbt7aQw/mk/AjsfmOE2LBh4E4/ydsuw0RVq 18dQ== X-Gm-Message-State: AOJu0Yz2xcc2v1DKskhq5oMTuxnMvOQTzxPZBhZPDdoIFpCvtkMPSyMa /Gf0x+au6R6rPv70+S65R0EyPIIeQ6PJrzv011mClJbFjPN8cFEmooMlRKcxkNhrMihtcAMasSk EO5gFdA== X-Gm-Gg: AZuq6aKym1olCyqf643YyWDfHWMgHRAs8NjRmbuM9782Sp2mrjZMyBXb4+hgHFED/uV umiTNi0UMi3Rcftss/9iXQMypVTkcwPfqCVtw+sd+XE/br2V6dhw5y1r795mRRHudzeI2MrQHL/ ZqZx9k0cf84xzEhunHC0Y7VnIkrOt1Pke4nj3XtE57sHGOoM7oGp6MXi2jqoVDAvjHJ6pKAp4dL /P7Lu5w7/cOVww9o1nGIEZbC4gElME2GWLoA5DRKg7b9Qh8YcYt8o30c0zSJmLp2td84gNesQGW mKI3l4UnEVWpYztTtd3CBsVu0z2hnBKtr73ac7lu3Yq54RvYQHrYu9AIwAWi2aejew2map6yIku hI3NbQbJAHNBsMJgOOCRkcos0lEau8kUdX7IUizeZ3V0JXFEtThXUGtzyrQXD+6F4sF/VtjbnDF Wb5sxsAv+aFpHYL1zky+j8W2FDOLM= X-Received: by 2002:a05:6820:229d:b0:662:fd74:61d0 with SMTP id 006d021491bc7-6630f004344mr964310eaf.7.1769745547177; Thu, 29 Jan 2026 19:59:07 -0800 (PST) Received: from chromium.org ([73.34.74.121]) by smtp.gmail.com with ESMTPSA id 006d021491bc7-662f9a4e491sm4128687eaf.16.2026.01.29.19.59.05 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Jan 2026 19:59:06 -0800 (PST) From: Simon Glass X-Google-Original-From: Simon Glass To: U-Boot Concept Date: Thu, 29 Jan 2026 20:58:26 -0700 Message-ID: <20260130035849.3580212-4-simon.glass@canonical.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260130035849.3580212-1-simon.glass@canonical.com> References: <20260130035849.3580212-1-simon.glass@canonical.com> MIME-Version: 1.0 Message-ID-Hash: OCDHQY3X5REO7ZC543R3X7ZIXJAMOVJ7 X-Message-ID-Hash: OCDHQY3X5REO7ZC543R3X7ZIXJAMOVJ7 X-MailFrom: sjg@chromium.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass , "Claude Opus 4 . 5" X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 03/19] test: Add ut_check_video() helper function List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Many video tests need to get the first video device and then call video_compress_fb() on it. Add a convenience function that combines these two operations to reduce duplication. The msg parameter allows tests to describe which frame is being checked, which helps with debugging when a check fails. Co-developed-by: Claude Opus 4.5 Signed-off-by: Simon Glass --- include/test/video.h | 12 ++++++++++++ test/dm/video.c | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/include/test/video.h b/include/test/video.h index 000fd708c86..8676613e6a4 100644 --- a/include/test/video.h +++ b/include/test/video.h @@ -42,4 +42,16 @@ int video_compress_fb(struct unit_test_state *uts, struct udevice *dev, */ int video_check_copy_fb(struct unit_test_state *uts, struct udevice *dev); +/** + * ut_check_video() - Compress the frame buffer and return its size + * + * This is a convenience function that gets the first video device and calls + * video_compress_fb() on it. + * + * @uts: Test state + * @msg: Message describing the frame being checked (or NULL) + * Return: compressed size of the frame buffer, or -ve on error + */ +int ut_check_video(struct unit_test_state *uts, const char *msg); + #endif diff --git a/test/dm/video.c b/test/dm/video.c index 92b2ee9a6e3..b7aa835c86a 100644 --- a/test/dm/video.c +++ b/test/dm/video.c @@ -198,6 +198,18 @@ int video_check_copy_fb(struct unit_test_state *uts, struct udevice *dev) return 0; } +int ut_check_video(struct unit_test_state *uts, const char *msg) +{ + struct udevice *dev; + int ret; + + ret = uclass_first_device_err(UCLASS_VIDEO, &dev); + if (ret) + return ret; + + return video_compress_fb(uts, dev, false); +} + /* * Call this function at any point to halt and show the current display. Be * sure to run the test with the -l flag. From patchwork Fri Jan 30 03:58:27 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 1779 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org Authentication-Results: mail.u-boot.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.a=rsa-sha256 header.s=google header.b=gNs1814C; dkim-atps=neutral Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id BD09E69738 for ; Thu, 29 Jan 2026 20:59:18 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id XoC0pFmGy2G0 for ; Thu, 29 Jan 2026 20:59:18 -0700 (MST) Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id ECD8C697E9 for ; Thu, 29 Jan 2026 20:59:16 -0700 (MST) Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 002D4697CD for ; Thu, 29 Jan 2026 20:59:13 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 8Nd7WAq20c4i for ; Thu, 29 Jan 2026 20:59:12 -0700 (MST) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=209.85.160.48; helo=mail-oa1-f48.google.com; envelope-from=sjg@chromium.org; receiver=u-boot.org Received: from mail-oa1-f48.google.com (mail-oa1-f48.google.com [209.85.160.48]) by mail.u-boot.org (Postfix) with ESMTPS id 87FE769738 for ; Thu, 29 Jan 2026 20:59:11 -0700 (MST) Received: by mail-oa1-f48.google.com with SMTP id 586e51a60fabf-4097b420ce0so1033863fac.2 for ; Thu, 29 Jan 2026 19:59:11 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1769745550; x=1770350350; darn=u-boot.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=EFauf4fqicl44oAn+RykyYt4SJp9sRJ3UturRwVY7rw=; b=gNs1814CpTfZbKEFf8wERaPBwETZzrgSzDKl1hj0BVIHAn0MLNpE4WqjI5RrctlXFP J41CZei2l36Zn2ZtkFECTyRdSReZopdnWJ3MmSrdd6DZdenQlzAWOEFQIv+5NbUhcMrT tMWI1K5ijugldqO4YrYcMENsQ0B7SJLoh5p/w= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769745550; x=1770350350; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=EFauf4fqicl44oAn+RykyYt4SJp9sRJ3UturRwVY7rw=; b=R/kzDhhu2rFC36HXDJPIYz3PjunhqlilAZl6qiO0nhrZbswMLnkTfUVJ6T7bsXNM+V 3uLIEBvZUdYl0tdpoRWFKo5wvkZnTVlqp8nUi2xcrggpql3gqKLvGNEOe0HkUATEc9Eb LWIs8CbbrFNQI36B3xdVsT7p2X+mMK5l+2OFnkXk/K5Hx6Mm+NVgAsLG+++C+aC2JT3G Cpx/RjJ1fsgP38Oc+gxhb5xTBDvcO4ViIFoMWQwVQLNRdRmE21/AkcsJZCnIiihDHVIQ uzZ95O/tFlE9IBQyT5mjoc3+TM9vsBFP31fzEqjKwNTxE7UE1GzH3rbhE+YAyriPbZRc TWjg== X-Gm-Message-State: AOJu0Yz04wT0ojJ6HTnuML8dxL4W+9UK6ylsxh83792cK/gdYwpr8PsZ 76MNScY3pf0jv/vEoQ343k0tX3DrhppPTF36qgm5n8Fg82h0+7S8HiSFREJ6wfuvyes1LUBplDS wFiZy4Q== X-Gm-Gg: AZuq6aI2zYllZ2Sa2FfAjxtJCqXf5sy2h/Zfoq1zB1AfLbgPQDAYaeOcabNxtrEThql ZizEb1KljTg/S5hjNojuCHivB0qBWCrMWoJyzBHsed9+Ily/J8kPJGYk8/pcXM+YU0c7sHjCpsp hqO54uLGX+V70icqJwCze6MTlKvsMOympo+HW6SAXLFOyX+gynmCm42uZQ6aw1uwcvHjaI49l/4 c+Hs2LM4jeK5A4ncwVW1LrZV15V4F1PEdsA/Z9zfLozaIoi6kFvSAUDi6y1kXBger7EqwJyPbWU hX7ZakUm3zklVcnfpqhEjc5XbIFKel277YNk64BfCKD8+0lo98IOefLDNIKtUrtrvl+N24CBaEW dJqNCo18fu+EXnJ54IQ9Ua+PS3cJlv9rKOmQ8JhoT1YAtAgBg5NnhuqZR6nPAQE5Iu2LIdoRmty QthVXEN+J7XR3govA7 X-Received: by 2002:a4a:d383:0:b0:663:1239:9eac with SMTP id 006d021491bc7-6631239a21amr195825eaf.59.1769745550246; Thu, 29 Jan 2026 19:59:10 -0800 (PST) Received: from chromium.org ([73.34.74.121]) by smtp.gmail.com with ESMTPSA id 006d021491bc7-662f9a4e491sm4128687eaf.16.2026.01.29.19.59.07 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Jan 2026 19:59:08 -0800 (PST) From: Simon Glass X-Google-Original-From: Simon Glass To: U-Boot Concept Date: Thu, 29 Jan 2026 20:58:27 -0700 Message-ID: <20260130035849.3580212-5-simon.glass@canonical.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260130035849.3580212-1-simon.glass@canonical.com> References: <20260130035849.3580212-1-simon.glass@canonical.com> MIME-Version: 1.0 Message-ID-Hash: HWQQLIIOFZ4OI3WXGWW72WVR757MEOFR X-Message-ID-Hash: HWQQLIIOFZ4OI3WXGWW72WVR757MEOFR X-MailFrom: sjg@chromium.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass , "Claude Opus 4 . 5" X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 04/19] test: Add video_compress_fb_() with message parameter List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Add video_compress_fb_() which takes an additional msg parameter to describe the frame being checked. This helps with debugging when a frame check fails. The existing video_compress_fb() becomes a wrapper that passes NULL for the message. Co-developed-by: Claude Opus 4.5 Signed-off-by: Simon Glass --- test/dm/video.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/dm/video.c b/test/dm/video.c index b7aa835c86a..421d50df064 100644 --- a/test/dm/video.c +++ b/test/dm/video.c @@ -138,8 +138,8 @@ static int video_write_bmp(struct unit_test_state *uts, struct udevice *dev, return ret; } -int video_compress_fb(struct unit_test_state *uts, struct udevice *dev, - bool use_copy) +static int video_compress_fb_(struct unit_test_state *uts, struct udevice *dev, + bool use_copy, const char *msg) { struct sandbox_state *state = state_get_current(); struct video_priv *priv = dev_get_uclass_priv(dev); @@ -184,6 +184,12 @@ int video_compress_fb(struct unit_test_state *uts, struct udevice *dev, return destlen; } +int video_compress_fb(struct unit_test_state *uts, struct udevice *dev, + bool use_copy) +{ + return video_compress_fb_(uts, dev, use_copy, NULL); +} + int video_check_copy_fb(struct unit_test_state *uts, struct udevice *dev) { struct video_priv *priv = dev_get_uclass_priv(dev); From patchwork Fri Jan 30 03:58:28 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 1780 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org Authentication-Results: mail.u-boot.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.a=rsa-sha256 header.s=google header.b=RZPkgCgr; dkim-atps=neutral Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 0E2F6697ED for ; Thu, 29 Jan 2026 20:59:19 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id a8ny6FxBX6Ki for ; Thu, 29 Jan 2026 20:59:18 -0700 (MST) Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 9ABA7697F0 for ; Thu, 29 Jan 2026 20:59:17 -0700 (MST) Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 67AAC697CB for ; Thu, 29 Jan 2026 20:59:15 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id qu1R0BKAYCIh for ; Thu, 29 Jan 2026 20:59:15 -0700 (MST) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=209.85.167.178; helo=mail-oi1-f178.google.com; envelope-from=sjg@chromium.org; receiver=u-boot.org Received: from mail-oi1-f178.google.com (mail-oi1-f178.google.com [209.85.167.178]) by mail.u-boot.org (Postfix) with ESMTPS id F339C69738 for ; Thu, 29 Jan 2026 20:59:14 -0700 (MST) Received: by mail-oi1-f178.google.com with SMTP id 5614622812f47-45f015a3259so706899b6e.2 for ; Thu, 29 Jan 2026 19:59:14 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1769745553; x=1770350353; darn=u-boot.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=Llif9hck2vul2cgSiXg6WNyHvLzQGgnqAzchzZU9OfA=; b=RZPkgCgrmTGlgGUfw7It8X/T/iU5mu0baHCMTAEoMpf/HcWKrdIm+3MEJe0DBEGJOS teT+CqK3fykme8iByNDwIjkUo8sujK3k/K829DGy3UvQSlodMDE6hG2/qrnZ7W674t1a 3+NPCIrIrHYtPYnoi8Hsg1J1d6EKaI8cktTXw= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769745553; x=1770350353; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=Llif9hck2vul2cgSiXg6WNyHvLzQGgnqAzchzZU9OfA=; b=kxm9Y+ORUy5lnxLGaeJFTpJKhNJ6EvBwHSvgZQO9tzewG7tgPuCIQi+ibA5R/WIfJ0 iD1cWzIi3ooOzi8F6wbFqZyiyrm8X9GBvCxjhfNb5DmIeItGiVla4xZIWe4b8WxUvsxz 4sH7PrQzt5EgA/epEuJrDSJ6z/ie3Mkhco4fB6ZQrINGiNFofDIxOb3FeG9aIKWOhUrl G7yFrKKy+xV8bWNCCkXCpHIbEMPUiK4N+gouLyIgcZAmE9YNQlmvpv8gJGmAzTi82zMo 6csq0De82plE5RZ7HI+W2cPJn6KTnTozq+Arhh+OiGVJReBi8Q6/XoV2ai9BxBZ5tR+Y BLJg== X-Gm-Message-State: AOJu0YzvUOKfD+KwvQi9Qj/t7XxJ8cLhBFN9lhVIYLJFZxSykS15MTmC JkHsq1O/ckedVvNZ/uGiKCjfZVvtMRqPIGbnZhH1i6Q5meKSdopi12nnpgSMcZPmkA/ETM8iWvj v02FqWA== X-Gm-Gg: AZuq6aJS6hNsYE24CUlxN2LsaVaxyI83/qLykFM62RQ56sqFqhtiBSHsZZVLnrSHHYt qlOicNfGzx3Xd12ZLEfRdHNMiR8ducXDdET7Rww+xNBC2Pjd9SDlXqlw7tkakCsgft8VhJ+C8GY Mks8MPJcDyZ5GgxXwJIN0KjB/AqQCLfXlJZVqWzbRC81BbVFAtCagr0Ar5fbNl3JTth1/tKYrpS ee3wTNz4vtrNVGZU6/+aZbH1VVU0Xrvfb8A5LaVjkags/Zx7PqGHcF3CA2gM4soHvMwSUcB7VeV 61QkinD3dNWX9IaybifTmUZRolX4XbLYhxZTVNQTvKWN2rMbCChTQw3yYFHatSbHpcxZVqCDu/G dvOOmHfDqUWvE5LCNTCa+GE5b+OtCVID/ju0eMtEZSILlAQwi4n26shk2uKFT+lgdYGQnozT94I +vtiyaQCAfe3tqGhwd X-Received: by 2002:a05:6820:7610:b0:663:12e7:c51c with SMTP id 006d021491bc7-66312e7c86cmr161523eaf.58.1769745553325; Thu, 29 Jan 2026 19:59:13 -0800 (PST) Received: from chromium.org ([73.34.74.121]) by smtp.gmail.com with ESMTPSA id 006d021491bc7-662f9a4e491sm4128687eaf.16.2026.01.29.19.59.10 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Jan 2026 19:59:11 -0800 (PST) From: Simon Glass X-Google-Original-From: Simon Glass To: U-Boot Concept Date: Thu, 29 Jan 2026 20:58:28 -0700 Message-ID: <20260130035849.3580212-6-simon.glass@canonical.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260130035849.3580212-1-simon.glass@canonical.com> References: <20260130035849.3580212-1-simon.glass@canonical.com> MIME-Version: 1.0 Message-ID-Hash: URUD6RHDWHFKQFQKXJUH7YR5O72ZJTGN X-Message-ID-Hash: URUD6RHDWHFKQFQKXJUH7YR5O72ZJTGN X-MailFrom: sjg@chromium.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass , "Claude Opus 4 . 5" X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 05/19] test: Display message at top right during video test delay List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: When a message is passed to video_compress_fb_() and a delay is active (via -V flag or LOG_DEBUG), display the message at the top right of the screen. The affected framebuffer region is saved before drawing and restored afterwards, so the message is only visible during the delay. Add save_video() and restore_video() functions in test/dm/video.c to handle saving and restoring framebuffer regions. The save buffer is allocated on first use and stored in uts->video_save, then freed in ut_uninit_state(). Guard the message display with CONFIG_UNIT_TEST since the unit_test_state structure is only available when unit tests are enabled. This helps identify which frame check is being displayed when debugging video tests. Co-developed-by: Claude Opus 4.5 Signed-off-by: Simon Glass --- doc/develop/expo.rst | 7 +++ include/test/test.h | 5 ++ test/dm/video.c | 123 ++++++++++++++++++++++++++++++++++++++++--- test/test-main.c | 2 + 4 files changed, 130 insertions(+), 7 deletions(-) diff --git a/doc/develop/expo.rst b/doc/develop/expo.rst index 7ef714be3da..71e227c532d 100644 --- a/doc/develop/expo.rst +++ b/doc/develop/expo.rst @@ -824,6 +824,13 @@ For example, to watch an expo test render with a visible display:: ./u-boot -T -l -V 500 --video_frames /tmp/good -c "ut bootstd expo_render_image" +When using ``-V`` or with ``LOG_DEBUG`` enabled, some video tests call +ut_check_video() to display a message at the top right corner of the screen +identifying the current frame check. This helps identify which assertion is +being displayed when debugging test failures. The message is automatically +removed after the delay, so it does not affect the framebuffer checksums used by +video tests. + The :doc:`../usage/cmd/sb` ``grid`` subcommand can be used to overlay a grid on the display, to help with checking alignment of objects. The grid size defaults to 0x20 pixels but can be specified as a parameter. diff --git a/include/test/test.h b/include/test/test.h index 56e25f6fa9d..c3b251e2cd4 100644 --- a/include/test/test.h +++ b/include/test/test.h @@ -6,6 +6,7 @@ #ifndef __TEST_TEST_H #define __TEST_TEST_H +#include #include #include @@ -100,6 +101,8 @@ struct ut_arg { * @arg_error: Set if ut_str/int/bool() detects a type mismatch * @keep_record: Preserve console recording when ut_fail() is called * @emit_result: Emit result line after each test completes + * @video_ctx: Vidconsole context for test message display (allocated on use) + * @video_save: Saved framebuffer region for video tests * @priv: Private data for tests to use as needed */ struct unit_test_state { @@ -134,6 +137,8 @@ struct unit_test_state { bool arg_error; bool keep_record; bool emit_result; + void *video_ctx; + struct abuf video_save; char priv[UT_PRIV_SIZE]; }; diff --git a/test/dm/video.c b/test/dm/video.c index 421d50df064..f97e2183a64 100644 --- a/test/dm/video.c +++ b/test/dm/video.c @@ -138,8 +138,106 @@ static int video_write_bmp(struct unit_test_state *uts, struct udevice *dev, return ret; } -static int video_compress_fb_(struct unit_test_state *uts, struct udevice *dev, - bool use_copy, const char *msg) +/** + * save_video() - Save a portion of the framebuffer + * + * Saves the top portion of the framebuffer so it can be restored later. + * The buffer is allocated on first use and stored in uts->video_save. + * + * @uts: Unit test state + * @dev: Video device + * @lines: Number of lines to save + * Return: 0 on success, -ve on error + */ +static int save_video(struct unit_test_state *uts, struct udevice *dev, + uint lines) +{ + struct video_priv *priv = dev_get_uclass_priv(dev); + int size; + + size = priv->line_length * lines; + if (size > priv->fb_size) + return -EINVAL; + + /* Allocate/resize the save buffer to exact size needed */ + if (!abuf_realloc(&uts->video_save, size)) + return -ENOMEM; + + memcpy(abuf_data(&uts->video_save), priv->fb, size); + + return 0; +} + +/** + * restore_video() - Restore a saved portion of the framebuffer + * + * Restores the framebuffer region previously saved by save_video(). + * + * @uts: Unit test state + * @dev: Video device + * Return: 0 on success, -ve on error + */ +static int restore_video(struct unit_test_state *uts, struct udevice *dev) +{ + struct video_priv *priv = dev_get_uclass_priv(dev); + + if (!abuf_size(&uts->video_save)) + return -ENOENT; + + memcpy(priv->fb, abuf_data(&uts->video_save), + abuf_size(&uts->video_save)); + + return 0; +} + +/** + * show_test_msg() - Display a test message at the top right of the screen + * + * @uts: Unit test state + * @dev: Video device + * @msg: Message to display + * Return: true if framebuffer was saved and needs restoring, false otherwise + */ +static bool show_test_msg(struct unit_test_state *uts, struct udevice *dev, + const char *msg) +{ + struct video_priv *priv = dev_get_uclass_priv(dev); + struct vidconsole_ctx *ctx = uts->video_ctx; + struct udevice *con; + bool saved = false; + int len = strlen(msg); + int x, ret; + + ret = uclass_first_device_err(UCLASS_VIDEO_CONSOLE, &con); + if (ret) + return false; + + /* Allocate context on first use and select 8x16 font */ + if (!ctx) { + ret = vidconsole_ctx_new(con, (void **)&ctx); + if (ret) + return false; + uts->video_ctx = ctx; + vidconsole_select_font(con, ctx, "8x16", 0); + } + + /* Calculate position at top right */ + x = priv->xsize - (len + 2) * ctx->x_charsize; + + /* Save the affected region (one line) */ + if (!save_video(uts, dev, ctx->y_charsize)) + saved = true; + + /* Draw the message */ + vidconsole_set_cursor_pos(con, ctx, x, 0); + vidconsole_put_string(con, ctx, msg); + video_manual_sync(dev, VIDSYNC_COPY | VIDSYNC_FLUSH); + + return saved; +} + +int video_compress_fb_(struct unit_test_state *uts, struct udevice *dev, + bool use_copy, const char *msg) { struct sandbox_state *state = state_get_current(); struct video_priv *priv = dev_get_uclass_priv(dev); @@ -176,10 +274,21 @@ static int video_compress_fb_(struct unit_test_state *uts, struct udevice *dev, } /* provide a useful delay if -V flag is used or LOG_DEBUG is set */ - if (state->video_test) - mdelay(state->video_test); - else if (_DEBUG) - mdelay(300); + if (state->video_test || _DEBUG) { + int delay = state->video_test ? state->video_test : 300; + bool saved = false; + + if (msg) + saved = show_test_msg(uts, dev, msg); + + mdelay(delay); + + /* Restore the framebuffer region */ + if (saved) { + restore_video(uts, dev); + video_manual_sync(dev, VIDSYNC_COPY | VIDSYNC_FLUSH); + } + } return destlen; } @@ -213,7 +322,7 @@ int ut_check_video(struct unit_test_state *uts, const char *msg) if (ret) return ret; - return video_compress_fb(uts, dev, false); + return video_compress_fb_(uts, dev, false, msg); } /* diff --git a/test/test-main.c b/test/test-main.c index a2c4e32423b..5db35b59760 100644 --- a/test/test-main.c +++ b/test/test-main.c @@ -87,6 +87,8 @@ void ut_uninit_state(struct unit_test_state *uts) os_free(uts->fdt_copy); os_free(uts->other_fdt); } + /* video_ctx is freed when the vidconsole is unbound */ + abuf_uninit(&uts->video_save); } /** From patchwork Fri Jan 30 03:58:29 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 1781 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org Authentication-Results: mail.u-boot.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.a=rsa-sha256 header.s=google header.b=lOACtlvY; dkim-atps=neutral Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 1E037697DF for ; Thu, 29 Jan 2026 20:59:23 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id v0k_Ls0cAnZP for ; Thu, 29 Jan 2026 20:59:23 -0700 (MST) Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 0D912697CB for ; Thu, 29 Jan 2026 20:59:23 -0700 (MST) Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 4F9DA69738 for ; Thu, 29 Jan 2026 20:59:21 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 6YSvdtsDlHFr for ; Thu, 29 Jan 2026 20:59:21 -0700 (MST) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=209.85.167.182; helo=mail-oi1-f182.google.com; envelope-from=sjg@chromium.org; receiver=u-boot.org Received: from mail-oi1-f182.google.com (mail-oi1-f182.google.com [209.85.167.182]) by mail.u-boot.org (Postfix) with ESMTPS id B2602697E6 for ; Thu, 29 Jan 2026 20:59:16 -0700 (MST) Received: by mail-oi1-f182.google.com with SMTP id 5614622812f47-45c715116dbso1197540b6e.3 for ; Thu, 29 Jan 2026 19:59:16 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1769745555; x=1770350355; darn=u-boot.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=1iKyZ0tCq7KWFgDxftp7p2CX6XgqgnS23WTCCnXWBTc=; b=lOACtlvYLDBTWar5QsIQgpkNsofHM10djCVUPK/+NC/Dt3uh1hb90It9cfgzH35IEO gMrm3mFelVgzUqMJOZ8egJfS2O7xCZu40LfyUzqeKunugIRrsRhzFkt8KvzGVxuD7RmZ FzVGMIs18daJx8LJwTRas/aYUcdc2hyZ9k7oA= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769745555; x=1770350355; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=1iKyZ0tCq7KWFgDxftp7p2CX6XgqgnS23WTCCnXWBTc=; b=Xrnikue1aGF00bpEgh2652BIE8iiinMTAJkL8oyb4DHUufgTgPVE39jbRj3FyqN0gZ 6SJxPaMP4JLzOn3mJjEXynbPT7ipFYNCGhmBFx0CV8zPIjVzZDXD33Wgi1Whm9HpPx+9 nJ2gEaO9Fe/9EyKOyZs2T8MKrbhkDfhxxE0SxgueYwJbY+kJ8gifpx+v94S8iEYhGhbE 0MP/Glq6ujKDHxgxXHZgyzVNq4oRKxT7dY2QW+5ah0hgZWWn4SDg7tH9/eVz+rp2opzr L18sM6obUgA4P1LkGFNjNoqXwMS4+j9bF7hl7e3YkPysm4KOa8bXRJYwkm8JGpDB70KW sD0w== X-Gm-Message-State: AOJu0YzNpPcLNbu7WfLGcJG+JwIpCe7Len5Fyj0sAXo2iXQBo/vnRnIn Glr27s1f5C3bPtFiS8fVVRHMBSMdn5pGKd3WiQj07eeFTnHuQlpxiJ/Vk2nVv5l63Oz82dlZq44 1HMVN3Q== X-Gm-Gg: AZuq6aJhD9ewx34c2KqnwCWxwHVhAU5Rbz0Ct4MDFP8kb2JaJZ36IQ3ad57xRT2kcab gP9jgE3C4XjifKeTYCpOvT3AJip6w1ir9W+Xeums4JX30M4aD9ktUfYTvd3uQ4hDSY9pwYpuNHP 7kix+UulBKDfrka2PwJ3/0y2n1OBHoUVCKUoqGPQsVIXvxIHiWDVC349eaMxgDPrOC74alhu3Cv uUtc05hwTuGVzMrmKNWOSKkN1L/h+EoLgPhZmcIlzsCkTJ8AgnEeB6MmXNzmfa2SMtVaA6icziW DtJ+3VKQ3M3EELgUvaamPwTdp3R4w87YhLx+FMaG0DaGNjRyID4eI+UWTHrmUfx3uhO4RMDv+9l A1VCOnpkFtMPFJHuQFJNjhPwhhotZsATT4f+y4g7CPWabScmWUaM9vylojQOVdOhSsiHPnxnJZZ 2N9uvqLBSgbU+FfEO/ X-Received: by 2002:a05:6820:220d:b0:662:fd74:61d5 with SMTP id 006d021491bc7-6630f004c4cmr927741eaf.3.1769745555522; Thu, 29 Jan 2026 19:59:15 -0800 (PST) Received: from chromium.org ([73.34.74.121]) by smtp.gmail.com with ESMTPSA id 006d021491bc7-662f9a4e491sm4128687eaf.16.2026.01.29.19.59.13 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Jan 2026 19:59:14 -0800 (PST) From: Simon Glass X-Google-Original-From: Simon Glass To: U-Boot Concept Date: Thu, 29 Jan 2026 20:58:29 -0700 Message-ID: <20260130035849.3580212-7-simon.glass@canonical.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260130035849.3580212-1-simon.glass@canonical.com> References: <20260130035849.3580212-1-simon.glass@canonical.com> MIME-Version: 1.0 Message-ID-Hash: KW3NYU6BO6GRKIRHF3IJ5XJD7HWRRKAG X-Message-ID-Hash: KW3NYU6BO6GRKIRHF3IJ5XJD7HWRRKAG X-MailFrom: sjg@chromium.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass , "Claude Opus 4 . 5" X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 06/19] expo: Free gen.lines alist when destroying text objects List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Text scene objects (SCENEOBJT_TEXT) have a gen.lines alist that stores line measurement info populated during rendering. This alist was not being freed when the object was destroyed, causing a memory leak. Add cleanup for gen.lines in scene_obj_destroy() for TEXT objects. Co-developed-by: Claude Opus 4.5 Signed-off-by: Simon Glass --- boot/scene.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/boot/scene.c b/boot/scene.c index ff21b524843..b075697adb6 100644 --- a/boot/scene.c +++ b/boot/scene.c @@ -110,6 +110,8 @@ void scene_obj_destroy(struct scene_obj *obj) else if (obj->type == SCENEOBJT_TEXTLINE || obj->type == SCENEOBJT_TEXTEDIT) scene_txtin_destroy(obj->scene, scene_obj_txtin(obj)); + if (obj->type == SCENEOBJT_TEXT) + alist_uninit(&((struct scene_obj_txt *)obj)->gen.lines); free(obj->name); free(obj); } From patchwork Fri Jan 30 03:58:30 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 1782 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org Authentication-Results: mail.u-boot.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.a=rsa-sha256 header.s=google header.b=e6/A7IrF; dkim-atps=neutral Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 70316697CB for ; Thu, 29 Jan 2026 20:59:25 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 3ce9tI2qSMqo for ; Thu, 29 Jan 2026 20:59:25 -0700 (MST) Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 33C17697E6 for ; Thu, 29 Jan 2026 20:59:24 -0700 (MST) Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 5904769738 for ; Thu, 29 Jan 2026 20:59:21 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id wX64N8i0f6zL for ; Thu, 29 Jan 2026 20:59:21 -0700 (MST) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=209.85.160.54; helo=mail-oa1-f54.google.com; envelope-from=sjg@chromium.org; receiver=u-boot.org Received: from mail-oa1-f54.google.com (mail-oa1-f54.google.com [209.85.160.54]) by mail.u-boot.org (Postfix) with ESMTPS id 0700C697EB for ; Thu, 29 Jan 2026 20:59:19 -0700 (MST) Received: by mail-oa1-f54.google.com with SMTP id 586e51a60fabf-4086661715cso1232996fac.2 for ; Thu, 29 Jan 2026 19:59:18 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1769745558; x=1770350358; darn=u-boot.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=eW6vvfurqkh9rPe4lw48FSmesggNsBFieiy3DyAlrTw=; b=e6/A7IrFBljf3xXAvYOLSq5y1WFzjvHu1oXR5/oVMaE1L/qs+Hkv1z66T72os6n7Q6 eNecYVfhgjW+l0zccbPoKoBeuF8H1oxqfb9XkhrJhRGkhneK7puGtfsm6MPXaP1FKFHT /g9jlQWptYRJAvAGm6JK6mI5juYzb67w9Cqj4= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769745558; x=1770350358; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=eW6vvfurqkh9rPe4lw48FSmesggNsBFieiy3DyAlrTw=; b=ohjBDiE0e73R+c2sq8CYCKyhHG5jglDKVpgEPL3CZBo5zVOi+uVQaR7TQNLKx2uIKM jqTfwHVKV0/V0aZrckqauK2L2Ck+YxAUu+7FVxOawPy4s2Vme3O+WNdlj13kJSrMrvuc seidizL863lchNsZG/EritTDI4ZJFDPJxHfvGmWSIz0EQh9xqCgoqQ6sS6qD4ILJGxlF lQEgwRXb/qvuVBbNWyOmEVrriYJL0MokngUiY7h+SiIl3QwEV8vaenOv8YNTaCXRjsPv 8aPQ4kq9mM5cjwdQKpUEBJfjwDc3sMTZxA/w/mqVE+3Avq89BinPQZRos7GrRVJcIdxR y+4Q== X-Gm-Message-State: AOJu0YzsO2FbBujw6ho5C8ipuZi831yovEJOc3RlQltoSTkXDwapLk0H YZvXgrNUK+zmeCkX6TLXwTiTvCcWjEXs0Ss3qzUXk6SjCIuGzNnm8GReqHH0rivPyKOJpHhymX1 284u2uw== X-Gm-Gg: AZuq6aL8OxrsEVcKoWE5GPFbtA+XZlTGFQa4Pc3QSSgWB3TIOQ8qDW076ydtD2KHeAP wdGNtVesz7i0L2exKo1IGdpuAsyPir7rG7FKgYZaE18zBWPCedaFhL4sepnqYZpKBaNSghMDoK2 JZU3w3N1c3OsAX447BOxezxUBDnUtWwkd0Q72lkDlnUYoGHuzA3khPoPrYh+KF3H+zrcXwjvOrO qwoiNNNTV+YsXVwf0/0SLYT3tLOrEqjepTq8oByv94M8B7zMqoFr73FAN5GYnr6089d5OH4AS75 wQHx4/6UZsuTayEUtIYVlrXcL5v7FMexSs2wOwy+AQPZWGAWoWs4OBSMKPr1KQXyZiyH/krVCCX ZDQMJkf6Rp/4gh4PLyqLLhExzapX6Z3u+igsuyR1kBJjQ0c2OhuAUi0ocozPl141XF4U86/p/n+ H/gm7WvQpNwrftFGslpqGTOJNM/Y0= X-Received: by 2002:a05:6820:1689:b0:663:b6f:c261 with SMTP id 006d021491bc7-6630f38c7fcmr820114eaf.43.1769745557784; Thu, 29 Jan 2026 19:59:17 -0800 (PST) Received: from chromium.org ([73.34.74.121]) by smtp.gmail.com with ESMTPSA id 006d021491bc7-662f9a4e491sm4128687eaf.16.2026.01.29.19.59.15 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Jan 2026 19:59:16 -0800 (PST) From: Simon Glass X-Google-Original-From: Simon Glass To: U-Boot Concept Date: Thu, 29 Jan 2026 20:58:30 -0700 Message-ID: <20260130035849.3580212-8-simon.glass@canonical.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260130035849.3580212-1-simon.glass@canonical.com> References: <20260130035849.3580212-1-simon.glass@canonical.com> MIME-Version: 1.0 Message-ID-Hash: ZC6M2NA37XIQ6HQ3WLMJMCUZD3CMY6O7 X-Message-ID-Hash: ZC6M2NA37XIQ6HQ3WLMJMCUZD3CMY6O7 X-MailFrom: sjg@chromium.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass , "Claude Opus 4 . 5" X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 07/19] expo: Read all available input to keep up with key repeat List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: When the user holds down a key, the terminal sends characters faster than expo can process them if it only reads one character per poll. Change poll_keys() to drain the input buffer by reading all available characters until a complete key is decoded or no more input is available. There are no tests for this feature. Co-developed-by: Claude Opus 4.5 Signed-off-by: Simon Glass --- boot/expo.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/boot/expo.c b/boot/expo.c index 842bacae163..e44c7552f30 100644 --- a/boot/expo.c +++ b/boot/expo.c @@ -498,10 +498,12 @@ static int poll_keys(struct expo *exp) ichar = cli_ch_process(&exp->cch, 0); if (!ichar) { - /* Check once for available input */ - if (tstc()) { + /* Read all available input to keep up with key repeat */ + while (tstc()) { ch = getchar(); ichar = cli_ch_process(&exp->cch, ch); + if (ichar) + break; } if (!ch && get_timer(exp->last_key_ms) >= 10) From patchwork Fri Jan 30 03:58:31 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 1784 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org Authentication-Results: mail.u-boot.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.a=rsa-sha256 header.s=google header.b=GeLgeJM4; dkim-atps=neutral Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 401EC697E8 for ; Thu, 29 Jan 2026 20:59:32 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id JNbzimqTNHBN for ; Thu, 29 Jan 2026 20:59:32 -0700 (MST) Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 33BD4697DD for ; Thu, 29 Jan 2026 20:59:30 -0700 (MST) Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id AABC0697C8 for ; Thu, 29 Jan 2026 20:59:27 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id PbHzyptYH1QV for ; Thu, 29 Jan 2026 20:59:27 -0700 (MST) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=209.85.160.54; helo=mail-oa1-f54.google.com; envelope-from=sjg@chromium.org; receiver=u-boot.org Received: from mail-oa1-f54.google.com (mail-oa1-f54.google.com [209.85.160.54]) by mail.u-boot.org (Postfix) with ESMTPS id 2E10869738 for ; Thu, 29 Jan 2026 20:59:22 -0700 (MST) Received: by mail-oa1-f54.google.com with SMTP id 586e51a60fabf-4094fbd1808so696649fac.1 for ; Thu, 29 Jan 2026 19:59:22 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1769745561; x=1770350361; darn=u-boot.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=E0CEEEc36T3w2clnLtK/xgCAr/WT5NGErmazRO316b8=; b=GeLgeJM47576YVSWcHG3LFvOvAgm3QLhjod6g3R1twqKOItkBT6pqBTkM3j5sZ+KPj +OkNkSp+Meet8E5rsFUrDGwNUec26mae6K0p11Ec39stzeWgCeflD87TaxZr7k8kf1cA xASg9cYHCqfUtRWt6r9gC+RKw9OiQdYYarGoQ= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769745561; x=1770350361; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=E0CEEEc36T3w2clnLtK/xgCAr/WT5NGErmazRO316b8=; b=W070bxCUH3+MURUUA8xqz8W+GsAVLTwOHxpUM5VyniGGwb67mHyFE1Yq/omQ+fIn2P 4zNOWOtg6PlgZ/FXLw+xOhYL/DxU7y1FoqPzzmkntZFXP19Dnc2gRwSzDcSrf04uE4Fk fpNaubpDUzCpC5PpQcAaaY+jB1Y3coTEsvhg976YfMhAfAgjb0RXqKAi4uG2j9XjmjxD kG0WdlzdNSj1EQviVGM3Xv1qu7axJlEgEBQaKufTOrWrC9e0rGvfelDKfISGlWRaLzY5 Hz8NlF8eyuUecdNCuMTCg96hiGhjiI8J0OMiC5KgM4iwULvO45LSVw06T7eob+Z10DK8 vcMQ== X-Gm-Message-State: AOJu0Yw4DMaes3kpFXhhrFTsA+gnGjx/tl7wCX5B+Bi9lG7zykI58Uyw YG0mvmgskGWPsOItjZUeYV+h0K7WNuUogK+OPGUptubahJV+ddBa+mb0EmNPTCUIKOlPIpUmWpf Kqn2uiw== X-Gm-Gg: AZuq6aIafJxAlDasMvp5dbymAbuCu+m4n8s6QlY44P/yzJEApNE5lMw+R+5+x+Jdrww zW5EhYSIHSIJ0m4LXVojMviUY9OjQd19+ezK8Edloyv1joWsHnHK/U4IwB1EKwqupStPGZIC4SL Aazshv5uyecbUcnLSCYqnVxjBxLdeq4ovVvLMEtwu3KHJvdFYzT6V2SAQqYv/kYRh+NuUGj9BeZ rjaJOfbfYapXa18PoVmsczhzzAXwEk/ltMjHg+IohFYBKHvdYGjOOXvW1ftWhYFKH9pY7pM/FPN SX96Ruj7igULT1RB4OA6LVpPcWJmjoTAyKrD1nbHDZqJx+PanJpVfx7EXm1kMTBTT4fTiCXm1Qz 1HAA3Z9i32cptjWaxe4wjKmoOFM4ICq6WcyvRZlaQR0DABsXGlZMA7oS3IODoZDNaWUTkBHWUrS PD5AyG8nUmkxuq589F X-Received: by 2002:a05:6820:1621:b0:663:12f:214d with SMTP id 006d021491bc7-6630f34f006mr802603eaf.27.1769745560677; Thu, 29 Jan 2026 19:59:20 -0800 (PST) Received: from chromium.org ([73.34.74.121]) by smtp.gmail.com with ESMTPSA id 006d021491bc7-662f9a4e491sm4128687eaf.16.2026.01.29.19.59.18 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Jan 2026 19:59:19 -0800 (PST) From: Simon Glass X-Google-Original-From: Simon Glass To: U-Boot Concept Date: Thu, 29 Jan 2026 20:58:31 -0700 Message-ID: <20260130035849.3580212-9-simon.glass@canonical.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260130035849.3580212-1-simon.glass@canonical.com> References: <20260130035849.3580212-1-simon.glass@canonical.com> MIME-Version: 1.0 Message-ID-Hash: RSCLIAIHSF7VSFNKTYVZATYBBIBESNO2 X-Message-ID-Hash: RSCLIAIHSF7VSFNKTYVZATYBBIBESNO2 X-MailFrom: sjg@chromium.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass , "Claude Opus 4 . 5" X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 08/19] input: Add Ctrl+arrow key support for sandbox SDL console List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Track the Ctrl modifier state in the input driver and generate the appropriate escape sequences (ESC [ 1 ; 5 D/C) when Ctrl+Left or Ctrl+Right arrow keys are pressed. This enables word navigation in the sandbox SDL console. There are no tests for this feature. Co-developed-by: Claude Opus 4.5 Signed-off-by: Simon Glass --- drivers/input/input.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/drivers/input/input.c b/drivers/input/input.c index 3f146fb07e6..5206d6f68d6 100644 --- a/drivers/input/input.c +++ b/drivers/input/input.c @@ -28,6 +28,9 @@ enum { /* Special flag ORed with key code to indicate release */ KEY_RELEASE = 1 << 15, KEY_MASK = 0xfff, + + /* Modifier bits for config->modifiers */ + MOD_CTRL = 1 << 0, }; /* @@ -428,6 +431,31 @@ static int input_keycode_to_ansi364(struct input_config *config, int ch_count; int i; + /* Handle Ctrl+arrow keys for word navigation */ + if (config->modifiers & MOD_CTRL) { + const char *seq = NULL; + + switch (keycode) { + case KEY_LEFT: + seq = "[1;5D"; /* Ctrl+Left: backward-word */ + break; + case KEY_RIGHT: + seq = "[1;5C"; /* Ctrl+Right: forward-word */ + break; + } + if (seq) { + ch_count = 0; + output_ch[ch_count++] = 0x1b; + while (*seq) { + if (ch_count < max_chars) + output_ch[ch_count] = *seq; + ch_count++; + seq++; + } + return ch_count; + } + } + for (i = ch_count = 0; i < ARRAY_SIZE(kbd_to_ansi364); i++) { if (keycode != kbd_to_ansi364[i].kbd_scan_code) continue; @@ -483,6 +511,9 @@ static int input_keycodes_to_ascii(struct input_config *config, table = process_modifier(config, key, keycode[i] & KEY_RELEASE); } + /* Track Ctrl state for special key handling */ + if (key == KEY_LEFTCTRL || key == KEY_RIGHTCTRL) + config->modifiers |= MOD_CTRL; } /* Start conversion by looking for the first new keycode (by same). */ From patchwork Fri Jan 30 03:58:32 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 1783 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org Authentication-Results: mail.u-boot.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.a=rsa-sha256 header.s=google header.b=VfZ8Tb/2; dkim-atps=neutral Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id AF083697DF for ; Thu, 29 Jan 2026 20:59:29 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 08_lZT5kYnaD for ; Thu, 29 Jan 2026 20:59:29 -0700 (MST) Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 9D71D697CE for ; Thu, 29 Jan 2026 20:59:29 -0700 (MST) Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id AAED1697CE for ; Thu, 29 Jan 2026 20:59:27 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id CNTE7G1czRYv for ; Thu, 29 Jan 2026 20:59:27 -0700 (MST) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=209.85.161.45; helo=mail-oo1-f45.google.com; envelope-from=sjg@chromium.org; receiver=u-boot.org Received: from mail-oo1-f45.google.com (mail-oo1-f45.google.com [209.85.161.45]) by mail.u-boot.org (Postfix) with ESMTPS id 24E1F697DF for ; Thu, 29 Jan 2026 20:59:24 -0700 (MST) Received: by mail-oo1-f45.google.com with SMTP id 006d021491bc7-6611c3b147eso1183340eaf.2 for ; Thu, 29 Jan 2026 19:59:24 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1769745563; x=1770350363; darn=u-boot.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=w5OSh5hW9Cwx6k20XkkhCxPYmJFfl//uXcV+Ve37Q+Y=; b=VfZ8Tb/2ccokrkWk1Y+yx/75+xFxuCylVPv9et9TQxLJtUeINpa09WkfBQGlGFC1er WceApLjByWd7iJ9VGKkM+ACP5zQHM9+f0c0LVP6woLGwNI9pfgqxC5seYaNU3UPWbh9X DoxaGxr0NVBSbvkCG27wDgopcoMvKKrwzMlw4= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769745563; x=1770350363; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=w5OSh5hW9Cwx6k20XkkhCxPYmJFfl//uXcV+Ve37Q+Y=; b=khWeXx8v4fWMlE/srhKzjmkGwlhQmP2s3aAQNSizMs6P7ng1s+LIbEqoPpY2Qq9CJX 7s91g/Xbk1T82/IcLqVdXLCRM1fdXD4OxTlquNubFDv3AgkrV/GrYxia7wfT4k7G0I28 xgsDwZbaTODUApCm9/zeiuKjek50L0NH5PRKXsXubjpRlKixLYPIn70BmY7bBsgLEIYP 2tZ43e5hSKIrqKchnkSgNs/TQOTS+Rv8zMoEsGIIuXA4b3ry0cTSsXC3paaHwRb6vsz9 evCuCp1weGt+tWx8dT6by2XSwbHzTvALZ1E3OFfyLNwY+6uWTeowCx+j3R/2F/FoPuwl fHmA== X-Gm-Message-State: AOJu0Yym8qgstJTrCKmeq7T2NU5dZrXHRU4Yg36Z/KKdmWF0j5ZrlbwC izjDiCOddS0rMIn5/c3+bD4+NqAUCGv4CsW5445in2lc+khTYs+37wB+OiyZdsPyFDMzo/Uiqz0 w2gkOdw== X-Gm-Gg: AZuq6aIzQUoFXZWfLZVl7KX/9yWNZi4Vap5tNLkKEvNk5Vlu0dPyNHOuaFwUGKwn/nc kUea6V2sFUy5TLmggziXk1ICPe7Eurz0DCWZLPm9wp35tZD1KGduIJC4sS9SWjwkO1dsnpYOawO SaAl++eN0kdbsPtaZP2NzXJqfIpyr53+iH6m9RrFLUuqJmtee1ctD7w9mVqVluxN9FYlnKDIpaW FngwmZuzhhBuRe9x28fXbxK/tVT6r0CNnG6ULrDn0HqERCaU0gY/9P91bokorEyYFQpSYtxZ4km JaK8dR/Ria2v3Zv1dj3mdc7etU0XdBDstWCqO/CTNff7AfsteNVcAlmtv50WQLTSAwebqSSpuVH NG1/XsXAjM60SbL9+24S4jxVJESmgWCDwVk5WvBtweD8IWEAmQ4ugubNL1QEClL2ldMDT/EJA94 desn/Fi49M1toMRjVd X-Received: by 2002:a05:6820:62a:b0:662:e066:7394 with SMTP id 006d021491bc7-6630f3a9a01mr814298eaf.73.1769745562817; Thu, 29 Jan 2026 19:59:22 -0800 (PST) Received: from chromium.org ([73.34.74.121]) by smtp.gmail.com with ESMTPSA id 006d021491bc7-662f9a4e491sm4128687eaf.16.2026.01.29.19.59.21 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Jan 2026 19:59:21 -0800 (PST) From: Simon Glass X-Google-Original-From: Simon Glass To: U-Boot Concept Date: Thu, 29 Jan 2026 20:58:32 -0700 Message-ID: <20260130035849.3580212-10-simon.glass@canonical.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260130035849.3580212-1-simon.glass@canonical.com> References: <20260130035849.3580212-1-simon.glass@canonical.com> MIME-Version: 1.0 Message-ID-Hash: QGS2PEGBQWFWO2363J75A7KYQ42P4WRT X-Message-ID-Hash: QGS2PEGBQWFWO2363J75A7KYQ42P4WRT X-MailFrom: sjg@chromium.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass , "Claude Opus 4 . 5" X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 09/19] input: Add Home and End key support for sandbox SDL console List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Add KEY_HOME and KEY_END to the ANSI escape sequence table so that these keys work in the sandbox SDL console. They generate the escape sequences ESC [ H and ESC [ F respectively, which are then decoded by cli_getch.c to move the cursor to the beginning or end of the line. Also add these keys (plus KEY_DELETE) to the sandbox cros-ec keyboard matrix so they are recognized by the emulated keyboard controller. There are no tests for this feature. Co-developed-by: Claude Opus 4.5 Signed-off-by: Simon Glass --- arch/sandbox/dts/cros-ec-keyboard.dtsi | 4 ++++ drivers/input/input.c | 3 +++ 2 files changed, 7 insertions(+) diff --git a/arch/sandbox/dts/cros-ec-keyboard.dtsi b/arch/sandbox/dts/cros-ec-keyboard.dtsi index d885a5ecd22..a8a2c790b9a 100644 --- a/arch/sandbox/dts/cros-ec-keyboard.dtsi +++ b/arch/sandbox/dts/cros-ec-keyboard.dtsi @@ -110,6 +110,10 @@ MATRIX_KEY(0x07, 0x09, KEY_O) MATRIX_KEY(0x07, 0x0b, KEY_UP) MATRIX_KEY(0x07, 0x0c, KEY_LEFT) + + MATRIX_KEY(0x00, 0x07, KEY_HOME) + MATRIX_KEY(0x00, 0x09, KEY_END) + MATRIX_KEY(0x00, 0x0b, KEY_DELETE) >; }; }; diff --git a/drivers/input/input.c b/drivers/input/input.c index 5206d6f68d6..5d265837a25 100644 --- a/drivers/input/input.c +++ b/drivers/input/input.c @@ -188,6 +188,9 @@ static struct { { KEY_LEFT, "[D"}, { KEY_RIGHT, "[C"}, { KEY_DOWN, "[B"}, + { KEY_HOME, "[H"}, + { KEY_END, "[F"}, + { KEY_DELETE, "[3~"}, { KEY_F1, "OP"}, { KEY_F2, "OQ"}, { KEY_F3, "OR"}, From patchwork Fri Jan 30 03:58:33 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 1785 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org Authentication-Results: mail.u-boot.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.a=rsa-sha256 header.s=google header.b=DFtbbUeO; dkim-atps=neutral Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 2D24D697E0 for ; Thu, 29 Jan 2026 20:59:35 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id sYVdWFC5OJg3 for ; Thu, 29 Jan 2026 20:59:35 -0700 (MST) Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 1C6D7697CE for ; Thu, 29 Jan 2026 20:59:35 -0700 (MST) Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 2C587697E8 for ; Thu, 29 Jan 2026 20:59:32 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id DzJQOCXf4dO4 for ; Thu, 29 Jan 2026 20:59:32 -0700 (MST) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=209.85.161.51; helo=mail-oo1-f51.google.com; envelope-from=sjg@chromium.org; receiver=u-boot.org Received: from mail-oo1-f51.google.com (mail-oo1-f51.google.com [209.85.161.51]) by mail.u-boot.org (Postfix) with ESMTPS id 5C979697CB for ; Thu, 29 Jan 2026 20:59:26 -0700 (MST) Received: by mail-oo1-f51.google.com with SMTP id 006d021491bc7-6611cab8ca3so989790eaf.3 for ; Thu, 29 Jan 2026 19:59:26 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1769745565; x=1770350365; darn=u-boot.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=N6hYXefBTtIncmvmAWbpLQLrMp8FxfxXBQq4GLTbeJg=; b=DFtbbUeOlpjUka4WzQ3pNgc7Z2Tl2bjWgW1ouA9W3eOAz7glxzmgfAFiSHH8sUnQre wmZLpB+aThxar+anvH7xDQJkAlZE31uMGQ/o+BLpxmVJUXp3MlZTbcgQ1hmF0IidlrqL IwIiSj8VAQSGzsWmgFmOyGXM1/nCZAMEA1+kE= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769745565; x=1770350365; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=N6hYXefBTtIncmvmAWbpLQLrMp8FxfxXBQq4GLTbeJg=; b=Z8D7eskDyFb6NXmhtujuK69HXF5wFhUVGEwiB1y8AmGTgz8w3PBgEAOqjjpVOB+i09 PmDuEhbx0LpxwqTYdgVwpO5TDdJzP/+aFtnD73Kbkjzp37w6QM6iKkY66AfWY08ZiSj3 6yiregJfgPc3xbkGzQe4FDirSLnVIRBh0DjjNikKZGSOH/5nqCtDAKUPxVbbNlLyGm4F VNeIKn/fboU7OgLCZdKXmy6OKZXmWrqFrYpm+vg7jvhSBhUz7FAI8sPrQGAyPdf6qml5 gjA5UF/Ojyr+7XkXYrqQbn6WY3s6vuRBTgga0/55N8k6tsXDH7AZM2ixwh2MkrC3Azj8 h0KQ== X-Gm-Message-State: AOJu0Yx3qqkOS7GuWs2U8I/7jmG8vS6AtupZuATIPaGveWpk5BrvbnIk ZJtVkjLK6aLEbkNqqq+dSoAGdpXBTTVQ7VAMx0vk8jjb5SwI8/tPkNtmngAHkJnzlL4kHvwK9S8 2i6Ucbw== X-Gm-Gg: AZuq6aIpbZ+yb6R+M0pYOkQlut7K7duGm46C2x0mc9ArhmJBrHjDjPU/Gk1LhS3GA96 13FRn1Ih3w2h24EtVMpWKahJoOTD0qFTHnDP/NiNwU1AzOOjgx2QebhfLMtC8Jvvj0h7ckURIAb u0zIWEMj2YTfvYPy5bm/GBlDqDFvu/00MWGYiSxPNqGS1ti0SJcE0xNOB2nyxcUlyuR7kjT2heo aXCmeGBU6OYKia7+9MssMDNjvUffsgCCyPuTsOvthE9dUbgdxhq1/uXJ9lE23jw6iZLSsNVYMwX l87Szv5CVSiBuWxFIh3w0efED31MK9/hrtDopYoCKqiyMoW+M07BrgQ3TkM9MdxARUVx247qg8u ochTRsdoHe4aLVsitvmE39ZAuFYCH57Y7GHlNGrb3RT0DvGWnNmRLhaTBri2MiwM+RS5RsWSorz PkncNG4wpgfHSwMSR3 X-Received: by 2002:a05:6820:f024:b0:662:f0cb:84bf with SMTP id 006d021491bc7-6630f05112emr972435eaf.36.1769745565112; Thu, 29 Jan 2026 19:59:25 -0800 (PST) Received: from chromium.org ([73.34.74.121]) by smtp.gmail.com with ESMTPSA id 006d021491bc7-662f9a4e491sm4128687eaf.16.2026.01.29.19.59.23 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Jan 2026 19:59:24 -0800 (PST) From: Simon Glass X-Google-Original-From: Simon Glass To: U-Boot Concept Date: Thu, 29 Jan 2026 20:58:33 -0700 Message-ID: <20260130035849.3580212-11-simon.glass@canonical.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260130035849.3580212-1-simon.glass@canonical.com> References: <20260130035849.3580212-1-simon.glass@canonical.com> MIME-Version: 1.0 Message-ID-Hash: M2A2Z27VBEORUBEK6OF3E5I67ZRAZTCU X-Message-ID-Hash: M2A2Z27VBEORUBEK6OF3E5I67ZRAZTCU X-MailFrom: sjg@chromium.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass , "Claude Opus 4 . 5" X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 10/19] input: Add Ctrl+Shift+Z support for redo List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Add support for detecting Ctrl+Shift+Z key combination in the input driver and generating the escape sequence ESC [ 1 ; 6 z which is then decoded by cli_getch.c to trigger redo. This requires tracking both Ctrl and Shift modifier state, so add MOD_SHIFT to the modifier flags. Co-developed-by: Claude Opus 4.5 Signed-off-by: Simon Glass --- drivers/input/input.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/drivers/input/input.c b/drivers/input/input.c index 5d265837a25..683569f59a1 100644 --- a/drivers/input/input.c +++ b/drivers/input/input.c @@ -31,6 +31,7 @@ enum { /* Modifier bits for config->modifiers */ MOD_CTRL = 1 << 0, + MOD_SHIFT = 1 << 1, }; /* @@ -459,6 +460,23 @@ static int input_keycode_to_ansi364(struct input_config *config, } } + /* Handle Ctrl+Shift+Z for redo */ + if ((config->modifiers & (MOD_CTRL | MOD_SHIFT)) == + (MOD_CTRL | MOD_SHIFT) && keycode == KEY_Z) { + /* Generate ESC [ 1 ; 6 z for Ctrl+Shift+Z */ + const char *seq = "[1;6z"; + + ch_count = 0; + output_ch[ch_count++] = 0x1b; + while (*seq) { + if (ch_count < max_chars) + output_ch[ch_count] = *seq; + ch_count++; + seq++; + } + return ch_count; + } + for (i = ch_count = 0; i < ARRAY_SIZE(kbd_to_ansi364); i++) { if (keycode != kbd_to_ansi364[i].kbd_scan_code) continue; @@ -514,9 +532,11 @@ static int input_keycodes_to_ascii(struct input_config *config, table = process_modifier(config, key, keycode[i] & KEY_RELEASE); } - /* Track Ctrl state for special key handling */ + /* Track Ctrl and Shift state for special key handling */ if (key == KEY_LEFTCTRL || key == KEY_RIGHTCTRL) config->modifiers |= MOD_CTRL; + if (key == KEY_LEFTSHIFT || key == KEY_RIGHTSHIFT) + config->modifiers |= MOD_SHIFT; } /* Start conversion by looking for the first new keycode (by same). */ From patchwork Fri Jan 30 03:58:34 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 1786 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org Authentication-Results: mail.u-boot.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.a=rsa-sha256 header.s=google header.b=YAICvnQn; dkim-atps=neutral Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 9D86E697E8 for ; Thu, 29 Jan 2026 20:59:37 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id OBtvSxcXiDk4 for ; Thu, 29 Jan 2026 20:59:37 -0700 (MST) Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 923A6697CE for ; Thu, 29 Jan 2026 20:59:35 -0700 (MST) Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 68420697E6 for ; Thu, 29 Jan 2026 20:59:32 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id gJlBUiMBvB72 for ; Thu, 29 Jan 2026 20:59:32 -0700 (MST) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=209.85.161.43; helo=mail-oo1-f43.google.com; envelope-from=sjg@chromium.org; receiver=u-boot.org Received: from mail-oo1-f43.google.com (mail-oo1-f43.google.com [209.85.161.43]) by mail.u-boot.org (Postfix) with ESMTPS id F35AF69738 for ; Thu, 29 Jan 2026 20:59:28 -0700 (MST) Received: by mail-oo1-f43.google.com with SMTP id 006d021491bc7-6611c3b147eso1183367eaf.2 for ; Thu, 29 Jan 2026 19:59:28 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1769745568; x=1770350368; darn=u-boot.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=qJgH3z0FwmV2eXY3R2+ZGG0EcQn+eJVO2hcQlYjmUQw=; b=YAICvnQntVymvlxq35nGKssEg0GuyErFMphoRIRmU4A6CMFwRX4MLpRP+3MiTPSfg7 oyPNUP5b4Ughtsa+o774OMx/fVnNiM11vkFAvBHUQP2iG28WJeGL/N/3ZcZQ7d1adlhF dxZx7yexs/pSwzVZlXI/FxcAy9zk2RdHzXIUQ= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769745568; x=1770350368; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=qJgH3z0FwmV2eXY3R2+ZGG0EcQn+eJVO2hcQlYjmUQw=; b=C05PamJfj77XZgW4rlHJWyjYc7BhOmmbd2vorZZYDPXJdEEGDF1jEEfRjIn/pjanWO CM1IlzWIPjon1irBlEXVJ8qbWtAgtWkw0MBsQOLAKnGuRrbqTCvJA477JPPPhRJgutDS ny6ctS98xYX9Q9ebWnMR3q3wAigozmKpzTu7e45gOm7uaAV++U2spIAGjApD0VCgaW0E 7Xg7r5r2Epecjf/fWoS9+8LDLPwquiHtYJ69CY4gfl3aoLoO/fm+V62/uzK815LorqhY SzPI4jU4a+HVKGaJTEFC3aHBpbpT/oX2+PAWO/+qys2oGv8GfsdI/01OCUt/797DmmMt GPnQ== X-Gm-Message-State: AOJu0Yw19h9o53Wi7aednzFD0UH+vUcE0l45flVX/Z1t2cqDfXGYHDGl 3ZrmgiyQ1+G+Y3YPujZrvMByUvXBuaugUty7Zgko+Lxaw46r0UOBAs/XZkLlRC6+MqshD8+OeDn rUi58yQ== X-Gm-Gg: AZuq6aKiB5TdPNrvP+kaHxx0wXtTaA7lzWxJ3Vr7RkN+fRAJCr+J449lrf4QWQRRLGk 8CbvNVeDtMLS+O3i58mIHMbGxOrorBuXA4/5bGHfFQ1NBgwLhSR2LM5oEfapX3tseGNOTJDFLwW 26EJFpi8oL3E9kQhvrQM3uHx3LuduZa8zq0pQUx5MY2NPuBlcLKu1EthTQj2K1lewTAi6xrzE/X D0YiUhN43G/Mq+s2g2y8aIuYb7Lpjzp57PvKSuHO97RV6928h6+4P47NmGzOWOTAlbVW5Xb4lOf FA/p46Yt7ci+dEcJqjqpk0grvPO8jyIn3NzQ3hSYuQ+0U53ygTMFTep6/LwIaQOpGU+IB1hVbEH 1xzuVAqb6ocRsCPzYRZAf7Msa797CVlOvdBfDQKOzUBZYR6C9dgYfpgeLVAO/oXWD2sBE87JZ+r 6U3/gPYFCyty4cPJEa X-Received: by 2002:a05:6820:826:b0:662:f452:648f with SMTP id 006d021491bc7-6630f0043admr828061eaf.17.1769745567748; Thu, 29 Jan 2026 19:59:27 -0800 (PST) Received: from chromium.org ([73.34.74.121]) by smtp.gmail.com with ESMTPSA id 006d021491bc7-662f9a4e491sm4128687eaf.16.2026.01.29.19.59.25 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Jan 2026 19:59:26 -0800 (PST) From: Simon Glass X-Google-Original-From: Simon Glass To: U-Boot Concept Date: Thu, 29 Jan 2026 20:58:34 -0700 Message-ID: <20260130035849.3580212-12-simon.glass@canonical.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260130035849.3580212-1-simon.glass@canonical.com> References: <20260130035849.3580212-1-simon.glass@canonical.com> MIME-Version: 1.0 Message-ID-Hash: N3XIF6ZK7ZNYMFULABC6FBCE7GPRQDC4 X-Message-ID-Hash: N3XIF6ZK7ZNYMFULABC6FBCE7GPRQDC4 X-MailFrom: sjg@chromium.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass , "Claude Opus 4 . 5" X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 11/19] cli: Add a Kconfig for enhanced editing List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Add a new Kconfig option CONFIG_CMDLINE_EDITOR to enable enhanced command-line editing features. This replaces CLI_READLINE_CALLBACK and is enabled by default when EXPO is enabled. Create struct cli_editor_state to hold state for enhanced editing features, initially containing: - putch: callback for character output redirection - line_nav: callback for multi-line navigation (Ctrl-P/N) - multiline: flag for multi-line input mode This struct is embedded in cli_line_state when CMDLINE_EDITOR is enabled. Add cli_editor() accessor function that returns a pointer to the editor state, or NULL if CMDLINE_EDITOR is not enabled. Update cli_readline.c and scene_txtin.c to use the new accessor. Co-developed-by: Claude Opus 4.5 Signed-off-by: Simon Glass --- boot/scene_txtin.c | 10 ++++--- cmd/Kconfig | 16 +++++++---- common/cli_readline.c | 12 +++++--- include/cli.h | 66 +++++++++++++++++++++++++++++++++++++------ 4 files changed, 81 insertions(+), 23 deletions(-) diff --git a/boot/scene_txtin.c b/boot/scene_txtin.c index cde9fdb8ccf..79814891cdc 100644 --- a/boot/scene_txtin.c +++ b/boot/scene_txtin.c @@ -112,6 +112,7 @@ int scene_txtin_render_deps(struct scene *scn, struct scene_obj *obj, struct scene_txtin *tin) { struct cli_line_state *cls = &tin->cls; + struct cli_editor_state *ed = cli_editor(cls); const bool open = obj->flags & SCENEOF_OPEN; struct udevice *cons = scn->expo->cons; void *ctx = tin->ctx; @@ -121,7 +122,7 @@ int scene_txtin_render_deps(struct scene *scn, struct scene_obj *obj, if (open) { scene_render_obj(scn, tin->edit_id, ctx); - if (cls->multiline) { + if (ed->multiline) { /* for multiline, set cursor position directly */ struct scene_obj_txt *txt; @@ -260,6 +261,7 @@ int scene_txtin_open(struct scene *scn, struct scene_obj *obj, struct scene_txtin *tin) { struct cli_line_state *cls = &tin->cls; + struct cli_editor_state *ed = cli_editor(cls); struct udevice *cons = scn->expo->cons; struct scene_obj_txt *txt; void *ctx; @@ -286,11 +288,11 @@ int scene_txtin_open(struct scene *scn, struct scene_obj *obj, vidconsole_entry_start(cons, ctx); cli_cread_init(cls, abuf_data(&tin->buf), abuf_size(&tin->buf)); cls->insert = true; - cls->putch = scene_txtin_putch; + ed->putch = scene_txtin_putch; cls->priv = scn; if (obj->type == SCENEOBJT_TEXTEDIT) { - cls->multiline = true; - cls->line_nav = scene_txtin_line_nav; + ed->multiline = true; + ed->line_nav = scene_txtin_line_nav; } cli_cread_add_initial(cls); diff --git a/cmd/Kconfig b/cmd/Kconfig index d8f57c446a5..448a6e9fe39 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -54,13 +54,17 @@ config CMDLINE_EDITING Enable editing and History functions for interactive command line input operations -config CLI_READLINE_CALLBACK - bool "Support a callback for character output" +config CMDLINE_EDITOR + bool "Enhanced command-line editing features" depends on CMDLINE_EDITING - help - Enable a callback for character output during line editing. This - allows redirection of output to a different destination, such as - a vidconsole. This is used by expo to support textline editing. + default y if EXPO + help + Enable enhanced editing features for the command line, including: + - Character-output callback for redirection to vidconsole + - Multi-line navigation callback (Ctrl-P/N) + - Ctrl+Left/Right arrow keys to move by words + - Undo/redo support (Ctrl+Z / Ctrl+Shift+Z) + - Yank/paste of killed text (Ctrl+Y) config CMDLINE_PS_SUPPORT bool "Enable support for changing the command prompt string at run-time" diff --git a/common/cli_readline.c b/common/cli_readline.c index 38f825184df..d2b02933fd7 100644 --- a/common/cli_readline.c +++ b/common/cli_readline.c @@ -79,8 +79,10 @@ static char *delete_char (char *buffer, char *p, int *colp, int *np, int plen) */ static void cls_putch(struct cli_line_state *cls, int ch) { - if (CONFIG_IS_ENABLED(CLI_READLINE_CALLBACK) && cls->putch) - cls->putch(cls, ch); + struct cli_editor_state *ed = cli_editor(cls); + + if (ed && ed->putch) + ed->putch(cls, ch); else putc(ch); } @@ -299,6 +301,7 @@ static void cread_add_str(struct cli_line_state *cls, char *str, int strsize, int cread_line_process_ch(struct cli_line_state *cls, char ichar) { + struct cli_editor_state *ed; char *buf = cls->buf; /* ichar=0x0 when error occurs in U-Boot getc */ @@ -405,10 +408,11 @@ int cread_line_process_ch(struct cli_line_state *cls, char ichar) break; case CTL_CH('p'): case CTL_CH('n'): - if (cls->multiline && cls->line_nav) { + ed = cli_editor(cls); + if (ed && ed->multiline && ed->line_nav) { int new_num; - new_num = cls->line_nav(cls, ichar == CTL_CH('p')); + new_num = ed->line_nav(cls, ichar == CTL_CH('p')); if (new_num < 0) { getcmd_cbeep(cls); break; diff --git a/include/cli.h b/include/cli.h index f1e5887fa56..b6a8a6be1dd 100644 --- a/include/cli.h +++ b/include/cli.h @@ -25,6 +25,43 @@ struct cli_ch_state { bool emitting; }; +struct cli_line_state; + +/** + * struct cli_editor_state - state for enhanced editing features + * + * This is only available when CONFIG_CMDLINE_EDITOR is enabled. + * + * @putch: Output a character (NULL to use putc()) + * @line_nav: Handle multi-line navigation (Ctrl-P/N) + * @multiline: true if input may contain multiple lines (enables + * Ctrl-P/N for line navigation instead of history) + */ +struct cli_editor_state { + /** + * @putch: Output a character (NULL to use putc()) + * + * @cls: CLI line state + * @ch: Character to output + */ + void (*putch)(struct cli_line_state *cls, int ch); + + /** + * @line_nav: Handle multi-line navigation (Ctrl-P/N) + * + * @cls: CLI line state + * @up: true for previous line, false for next + * Return: new cursor position, or -ve if at boundary + */ + int (*line_nav)(struct cli_line_state *cls, bool up); + + /** + * @multiline: true if input may contain multiple lines (enables + * Ctrl-P/N for line navigation instead of history) + */ + bool multiline; +}; + /** * struct cli_line_state - state of the line editor * @@ -34,15 +71,10 @@ struct cli_ch_state { * @history: true if history should be accessible * @cmd_complete: true if tab completion should be enabled (requires @prompt to * be set) - * @multiline: true if input may contain multiple lines (enables Ctrl-P/N for - * line navigation instead of history) * @buf: Buffer containing line * @prompt: Prompt for the line - * @putch: Function to call to output a character (NULL to use putc()) - * @line_nav: Function to call for multi-line navigation (Ctrl-P/N). Called with - * @up true for previous line, false for next. Returns new cursor position, - * or -ve if at boundary * @priv: Private data for callbacks + * @ed: Editor state for enhanced features (if CONFIG_CMDLINE_EDITOR) */ struct cli_line_state { uint num; @@ -51,14 +83,30 @@ struct cli_line_state { bool insert; bool history; bool cmd_complete; - bool multiline; char *buf; const char *prompt; - void (*putch)(struct cli_line_state *cls, int ch); - int (*line_nav)(struct cli_line_state *cls, bool up); void *priv; +#if CONFIG_IS_ENABLED(CMDLINE_EDITOR) + struct cli_editor_state ed; +#endif }; +/** + * cli_editor() - Get the editor state from a line state + * + * @cls: CLI line state + * Return: Pointer to editor state, or NULL if CONFIG_CMDLINE_EDITOR is not + * enabled + */ +static inline struct cli_editor_state *cli_editor(struct cli_line_state *cls) +{ +#if CONFIG_IS_ENABLED(CMDLINE_EDITOR) + return &cls->ed; +#else + return NULL; +#endif +} + /** * Go into the command loop * From patchwork Fri Jan 30 03:58:35 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 1787 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org Authentication-Results: mail.u-boot.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.a=rsa-sha256 header.s=google header.b=f8d1JkW2; dkim-atps=neutral Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 2CC96697DD for ; Thu, 29 Jan 2026 20:59:40 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id DaEyZQ556s22 for ; Thu, 29 Jan 2026 20:59:40 -0700 (MST) Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 17FB7697CB for ; Thu, 29 Jan 2026 20:59:40 -0700 (MST) Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id D861E697ED for ; Thu, 29 Jan 2026 20:59:37 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id rkyd7uSdJQbF for ; Thu, 29 Jan 2026 20:59:37 -0700 (MST) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=209.85.160.43; helo=mail-oa1-f43.google.com; envelope-from=sjg@chromium.org; receiver=u-boot.org Received: from mail-oa1-f43.google.com (mail-oa1-f43.google.com [209.85.160.43]) by mail.u-boot.org (Postfix) with ESMTPS id 8E08569738 for ; Thu, 29 Jan 2026 20:59:32 -0700 (MST) Received: by mail-oa1-f43.google.com with SMTP id 586e51a60fabf-40974bf7781so1135409fac.0 for ; Thu, 29 Jan 2026 19:59:32 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1769745571; x=1770350371; darn=u-boot.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=XgQf8W2szUm4TjIufcRntmhr50KKs/4EIfNFHnCxnPA=; b=f8d1JkW2P9bI8/dcuQydPfJuW1Yc2DI1LH0nRbQGM8Oi3KpkD9U8HdcEWQVNdXLBAX lscODcO9+0EJb/pacY8oxJ1aE1S9ubF2o3b8/7hg+aUqfyXhSSzM0RvMDA3xjwzyVnqI fMnRdML5JzwFFEYAMsKdL2B0qmQoNWV32Vj1s= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769745571; x=1770350371; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=XgQf8W2szUm4TjIufcRntmhr50KKs/4EIfNFHnCxnPA=; b=GVrEBVRv1Skkpxt0uFrVNuX5UhGz17fVNyG3Njwbl2uk6OkfKOG4G7UMp1RF4YNfIz i2xiyZhN+R0YTv5eK4Ycrt46wKVM5lp9hP8VAKdvNdHR1aHzVNVtBPkfBhaLZ+VZjPKJ oVlY7DeDWKpqZcVrhdEloQeUroAgi/BeU94l1fVEtMWDhyo9urlH0jRWkR9/HYQn3n76 DY/+gK1AxpZXe27O0laUZaq5GpsO7Za3cVF+BXv53EA8U3xk7Xv0jzMWJAxm/Xpn7t2v PqfAiyEt2gjuSaQvbLHsP7ZSqL1oHlwQG6faxgcUeENnxcuINO+njLGlqCsELcBiiMxi 714g== X-Gm-Message-State: AOJu0YyITsP/8wtiuvxoF3STRntl0g9uM266065kK3M5QAwfq/vRdij5 Z+1oC8bRqt2BqGSz/ZEMf/mWvDRB9Ae+Xl9R6QBBoP3Rt6i32mmRgkeiEKygfhHhALFzi2EPOjB vY2zrQg== X-Gm-Gg: AZuq6aLoNCjqFCQmePDUVDGkx6kX5qGmGTWd1OL6Tzh1e/sch7QvLVPHWduyF/OJ/8U P+tnecc384mKUqPyyI7X77EgcB4PS99LmYVBWa34pZN2KoC6A2NbWqBcVDQoTr3z1cPE8RpG3si D7azkB0+jbI6CJdCY0g1kGCJZFDEH4CiLJBt9Xw/v9HZRqebp1U2N1hJ6aBLOfaDWlUT2jYnnk3 W+wkIylwWNFgidovrHb7C4KWxE2NgaeWkHG1A4/QIiGO5V5o/klwmo5rYOFuUYI6s3K3iWqoqR8 5/TRUmt9x5HscqioxdghrydZS3oJqdtZwr0kk7XUxsQoRN4gAYvvRZutxkDnYZqG6FovZhcpbdJ ErgVXBDPpAFT2EeWwGINUYUnESzkvydyITVa0qG5INIjrfcsPcVFIegbP+Vtm1pTsA1nHfyyjQn lONyZYBJqLr0oGu3XG X-Received: by 2002:a05:6820:4510:b0:65c:faa7:7077 with SMTP id 006d021491bc7-6631037c91cmr585922eaf.23.1769745570908; Thu, 29 Jan 2026 19:59:30 -0800 (PST) Received: from chromium.org ([73.34.74.121]) by smtp.gmail.com with ESMTPSA id 006d021491bc7-662f9a4e491sm4128687eaf.16.2026.01.29.19.59.28 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Jan 2026 19:59:29 -0800 (PST) From: Simon Glass X-Google-Original-From: Simon Glass To: U-Boot Concept Date: Thu, 29 Jan 2026 20:58:35 -0700 Message-ID: <20260130035849.3580212-13-simon.glass@canonical.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260130035849.3580212-1-simon.glass@canonical.com> References: <20260130035849.3580212-1-simon.glass@canonical.com> MIME-Version: 1.0 Message-ID-Hash: HBFW3KMGNCAOJK3D2K4SUOLEVXQ47UO5 X-Message-ID-Hash: HBFW3KMGNCAOJK3D2K4SUOLEVXQ47UO5 X-MailFrom: sjg@chromium.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass , "Claude Opus 4 . 5" X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 12/19] cmd: editenv: Add -e flag for expo-based editing List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Add a Kconfig option CMD_EDITENV_EXPO to enable graphical environment variable editing using the expo framework. When enabled, users can use 'editenv -e varname' to edit a variable using a textedit widget in a graphical interface. The expo-based editor creates a simple scene with a textedit object, allowing the user to edit the variable value with full cursor movement support. Press Enter to accept changes or Escape to cancel. The implementation is in boot/editenv.c controlled by EXPO_EDITENV, allowing it to be used without CONFIG_CMDLINE. The command support (CMD_EDITENV_EXPO) depends on EXPO_EDITENV. Co-developed-by: Claude Opus 4.5 Signed-off-by: Simon Glass --- boot/Kconfig | 9 +++ boot/Makefile | 1 + boot/editenv.c | 191 ++++++++++++++++++++++++++++++++++++++++++++ cmd/Kconfig | 9 +++ cmd/nvedit.c | 50 ++++++++++-- include/expo.h | 64 +++++++++++++++ test/boot/Makefile | 1 + test/boot/editenv.c | 94 ++++++++++++++++++++++ 8 files changed, 411 insertions(+), 8 deletions(-) create mode 100644 boot/editenv.c create mode 100644 test/boot/editenv.c diff --git a/boot/Kconfig b/boot/Kconfig index 7a8f9862ba7..e117e5b0479 100644 --- a/boot/Kconfig +++ b/boot/Kconfig @@ -1012,6 +1012,15 @@ config EXPO_LOG_FILTER Only objects whose name contains the filter string are logged. This is useful for debugging specific expo objects. +config EXPO_EDITENV + bool "Expo-based environment variable editor" + depends on EXPO + default y if SANDBOX + help + Enable a graphical environment variable editor using expo. This + provides a textedit widget for editing environment variables with + full cursor movement support. + config BOOTMETH_SANDBOX def_bool y depends on SANDBOX diff --git a/boot/Makefile b/boot/Makefile index b9129a174c7..b2a475d4917 100644 --- a/boot/Makefile +++ b/boot/Makefile @@ -62,6 +62,7 @@ obj-$(CONFIG_$(PHASE_)LOAD_FIT) += common_fit.o obj-$(CONFIG_$(PHASE_)EXPO) += expo.o scene.o expo_build.o obj-$(CONFIG_$(PHASE_)EXPO_DUMP) += expo_dump.o obj-$(CONFIG_$(PHASE_)EXPO) += scene_menu.o scene_textline.o scene_textedit.o scene_txtin.o +obj-$(CONFIG_$(PHASE_)EXPO_EDITENV) += editenv.o obj-$(CONFIG_$(PHASE_)EXPO_TEST) += expo_test.o ifdef CONFIG_COREBOOT_SYSINFO obj-$(CONFIG_$(PHASE_)EXPO) += expo_build_cb.o diff --git a/boot/editenv.c b/boot/editenv.c new file mode 100644 index 00000000000..de7df6fe3fa --- /dev/null +++ b/boot/editenv.c @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Expo-based environment variable editor + * + * Copyright 2025 Google LLC + * Written by Simon Glass + */ + +#include +#include +#include +#include + +/* IDs for expo objects */ +enum { + EDITENV_SCENE = EXPOID_BASE_ID + 1, + EDITENV_OBJ_TEXTEDIT, + EDITENV_OBJ_LABEL, + EDITENV_OBJ_EDIT, +}; + +static int editenv_setup(struct expo *exp, struct udevice *dev, + const char *varname, const char *value, + struct editenv_info *info) +{ + struct scene_obj_txtedit *ted; + struct scene *scn; + const char *name; + uint font_size; + int ret; + + ret = expo_set_display(exp, dev); + if (ret) + return log_msg_ret("dis", ret); + + ret = vidconsole_get_font_size(exp->cons, NULL, &name, &font_size); + if (ret) + font_size = 16; + + exp->theme.font_size = font_size; + exp->theme.textline_label_margin_x = 10; + + ret = scene_new(exp, "edit", EDITENV_SCENE, &scn); + if (ret < 0) + return log_msg_ret("scn", ret); + + ret = scene_texted(scn, "textedit", EDITENV_OBJ_TEXTEDIT, 70, &ted); + if (ret < 0) + return log_msg_ret("ted", ret); + + ret = scene_obj_set_bbox(scn, EDITENV_OBJ_TEXTEDIT, 50, 200, 1300, 400); + if (ret < 0) + return log_msg_ret("sbb", ret); + + /* Create the label text object */ + ret = scene_txt_str(scn, "label", EDITENV_OBJ_LABEL, 0, varname, NULL); + if (ret < 0) + return log_msg_ret("lab", ret); + + ted->tin.label_id = EDITENV_OBJ_LABEL; + + /* Create the edit text object pointing to the textedit buffer */ + ret = scene_txt_str(scn, "edit", EDITENV_OBJ_EDIT, 0, + abuf_data(&ted->tin.buf), NULL); + if (ret < 0) + return log_msg_ret("edi", ret); + + ted->tin.edit_id = EDITENV_OBJ_EDIT; + + ret = expo_apply_theme(exp, true); + if (ret) + return log_msg_ret("thm", ret); + + /* Copy initial value into the textedit buffer */ + if (value) + strlcpy(abuf_data(&ted->tin.buf), value, + abuf_size(&ted->tin.buf)); + + ret = expo_set_scene_id(exp, EDITENV_SCENE); + if (ret) + return log_msg_ret("sid", ret); + + /* Set the textedit as highlighted and open for editing */ + scene_set_highlight_id(scn, EDITENV_OBJ_TEXTEDIT); + ret = scene_set_open(scn, EDITENV_OBJ_TEXTEDIT, true); + if (ret) + return log_msg_ret("ope", ret); + + expo_enter_mode(exp); + + info->exp = exp; + info->scn = scn; + info->ted = ted; + + ret = scene_arrange(scn); + if (ret) + return log_msg_ret("arr", ret); + + ret = expo_render(exp); + if (ret) + return log_msg_ret("ren", ret); + + return 0; +} + +int expo_editenv_init(const char *varname, const char *value, + struct editenv_info *info) +{ + struct udevice *dev; + struct expo *exp; + int ret; + + ret = uclass_first_device_err(UCLASS_VIDEO, &dev); + if (ret) + return log_msg_ret("vid", ret); + + ret = expo_new("editenv", NULL, &exp); + if (ret) + return log_msg_ret("exp", ret); + + ret = editenv_setup(exp, dev, varname, value, info); + if (ret) { + expo_destroy(exp); + return log_msg_ret("set", ret); + } + + return 0; +} + +int expo_editenv_poll(struct editenv_info *info) +{ + struct expo_action act; + int ret; + + ret = scene_arrange(info->scn); + if (ret) + return log_msg_ret("arr", ret); + + ret = expo_render(info->exp); + if (ret) + return log_msg_ret("ren", ret); + + ret = expo_poll(info->exp, &act); + if (ret == -EAGAIN) + return -EAGAIN; + if (ret) + return log_msg_ret("pol", ret); + + if (act.type == EXPOACT_QUIT) + return -ECANCELED; + + if (act.type == EXPOACT_CLOSE) + return 0; + + return -EAGAIN; +} + +void expo_editenv_uninit(struct editenv_info *info) +{ + expo_exit_mode(info->exp); + expo_destroy(info->exp); +} + +const char *expo_editenv_result(struct editenv_info *info) +{ + return abuf_data(&info->ted->tin.buf); +} + +int expo_editenv(const char *varname, const char *value, char *buf, int size) +{ + struct editenv_info info; + int ret; + + ret = expo_editenv_init(varname, value, &info); + if (ret) + return log_msg_ret("ini", ret); + + /* Render and process input */ + while (1) { + ret = expo_editenv_poll(&info); + if (ret != -EAGAIN) + break; + } + + if (!ret) + strlcpy(buf, expo_editenv_result(&info), size); + + expo_editenv_uninit(&info); + + return ret; +} diff --git a/cmd/Kconfig b/cmd/Kconfig index 448a6e9fe39..606a34f8869 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -707,6 +707,15 @@ config CMD_EDITENV help Edit environment variable. +config CMD_EDITENV_EXPO + bool "editenv expo support" + depends on CMD_EDITENV && EXPO_EDITENV + default y if EXPO_EDITENV + help + Enable the -e flag for the editenv command, which provides a + graphical editor using the expo framework. This requires a video + console. + config CMD_GREPENV bool "search env" help diff --git a/cmd/nvedit.c b/cmd/nvedit.c index f67c268da84..f62b4cca242 100644 --- a/cmd/nvedit.c +++ b/cmd/nvedit.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -427,31 +428,55 @@ static int do_env_edit(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) { char buffer[CONFIG_SYS_CBSIZE]; + bool use_expo = false; + const char *varname; char *init_val; + if (IS_ENABLED(CONFIG_CMD_EDITENV_EXPO) && + argc >= 2 && !strcmp(argv[1], "-e")) { + use_expo = true; + argc--; + argv++; + } + if (argc < 2) return CMD_RET_USAGE; + varname = argv[1]; + /* before import into hashtable */ if (!(gd->flags & GD_FLG_ENV_READY)) return 1; - /* Set read buffer to initial value or empty sting */ - init_val = env_get(argv[1]); + /* Set read buffer to initial value or empty string */ + init_val = env_get(varname); if (init_val) snprintf(buffer, CONFIG_SYS_CBSIZE, "%s", init_val); else buffer[0] = '\0'; - if (cli_readline_into_buffer("edit: ", buffer, 0) < 0) - return 1; + if (IS_ENABLED(CONFIG_CMD_EDITENV_EXPO) && use_expo) { + int ret; + + ret = expo_editenv(varname, init_val, buffer, + CONFIG_SYS_CBSIZE); + if (ret == -EAGAIN) + return 0; /* User cancelled, no change */ + if (ret) { + printf("Edit failed (err=%d)\n", ret); + return CMD_RET_FAILURE; + } + } else { + if (cli_readline_into_buffer("edit: ", buffer, 0) < 0) + return 1; + } if (buffer[0] == '\0') { - const char * const _argv[3] = { "setenv", argv[1], NULL }; + const char * const _argv[3] = { "setenv", varname, NULL }; return env_do_env_set(0, 2, (char * const *)_argv, H_INTERACTIVE); } else { - const char * const _argv[4] = { "setenv", argv[1], buffer, + const char * const _argv[4] = { "setenv", varname, buffer, NULL }; return env_do_env_set(0, 3, (char * const *)_argv, H_INTERACTIVE); @@ -1065,7 +1090,7 @@ static struct cmd_tbl cmd_env_sub[] = { U_BOOT_CMD_MKENT(default, 1, 0, do_env_default, "", ""), U_BOOT_CMD_MKENT(delete, CONFIG_SYS_MAXARGS, 0, do_env_delete, "", ""), #if defined(CONFIG_CMD_EDITENV) - U_BOOT_CMD_MKENT(edit, 2, 0, do_env_edit, "", ""), + U_BOOT_CMD_MKENT(edit, 3, 0, do_env_edit, "", ""), #endif #if defined(CONFIG_CMD_ENV_CALLBACK) U_BOOT_CMD_MKENT(callbacks, 1, 0, do_env_callback, "", ""), @@ -1141,8 +1166,12 @@ U_BOOT_LONGHELP(env, " \"-k\": keep variables not defined in default environment\n" "env delete [-f] var [...] - [forcibly] delete variable(s)\n" #if defined(CONFIG_CMD_EDITENV) +#if defined(CONFIG_CMD_EDITENV_EXPO) + "env edit [-e] name - edit environment variable (-e for expo)\n" +#else "env edit name - edit environment variable\n" #endif +#endif #if defined(CONFIG_CMD_ENV_EXISTS) "env exists name - tests for existence of variable\n" #endif @@ -1208,9 +1237,14 @@ U_BOOT_CMD( #if defined(CONFIG_CMD_EDITENV) U_BOOT_CMD_COMPLETE( - editenv, 2, 0, do_env_edit, + editenv, 3, 0, do_env_edit, "edit environment variable", +#if defined(CONFIG_CMD_EDITENV_EXPO) + "[-e] name\n" + " -e - use expo (graphical editor)\n" +#else "name\n" +#endif " - edit environment variable 'name'", var_complete ); diff --git a/include/expo.h b/include/expo.h index d63fbd0c8ad..5a35d72c58f 100644 --- a/include/expo.h +++ b/include/expo.h @@ -1181,6 +1181,70 @@ int expo_setup_theme(struct expo *exp, ofnode node); */ int expo_apply_theme(struct expo *exp, bool do_objs); +/** + * struct editenv_info - Context for environment-variable editing + * + * @exp: Expo being used + * @scn: Scene in the expo + * @ted: Textedit object for editing + */ +struct editenv_info { + struct expo *exp; + struct scene *scn; + struct scene_obj_txtedit *ted; +}; + +/** + * expo_editenv_init() - Set up a new editenv expo + * + * @varname: Name of the variable to edit + * @value: Initial value (may be NULL) + * @info: Returns info about the editenv state + * Return: 0 if OK, -ve on error + */ +int expo_editenv_init(const char *varname, const char *value, + struct editenv_info *info); + +/** + * expo_editenv_poll() - Poll for user input + * + * @info: Editenv info + * Return: 0 if editing is complete, -EAGAIN if more polling is needed, + * -ECANCELED if user quit, other -ve on error + */ +int expo_editenv_poll(struct editenv_info *info); + +/** + * expo_editenv_uninit() - Free resources used by editenv + * + * @info: Editenv info + */ +void expo_editenv_uninit(struct editenv_info *info); + +/** + * expo_editenv_result() - Get the result string from editenv + * + * @info: Editenv info + * Return: Pointer to the edited string + */ +const char *expo_editenv_result(struct editenv_info *info); + +/** + * expo_editenv() - Edit an environment variable using expo + * + * Creates a simple expo with a textedit object to edit the variable. + * This is a convenience function that calls expo_editenv_init(), + * expo_editenv_poll() in a loop, and expo_editenv_uninit(). + * + * @varname: Name of the variable to edit + * @value: Initial value (may be NULL) + * @buf: Buffer to receive the edited text + * @size: Size of buf + * Return: 0 if OK and text was edited, -ECANCELED if cancelled, other -ve on + * error + */ +int expo_editenv(const char *varname, const char *value, char *buf, int size); + /** * expo_build() - Build an expo from an FDT description * diff --git a/test/boot/Makefile b/test/boot/Makefile index ceb863969dd..329f4acbd52 100644 --- a/test/boot/Makefile +++ b/test/boot/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_$(PHASE_)FIT_PRINT) += fit_print.o obj-$(CONFIG_BLK_LUKS) += luks.o obj-$(CONFIG_EXPO) += expo.o expo_common.o +obj-$(CONFIG_EXPO_EDITENV) += editenv.o obj-$(CONFIG_CEDIT) += cedit.o expo_common.o obj-$(CONFIG_UT_BOOTCTL) += bootctl/ endif diff --git a/test/boot/editenv.c b/test/boot/editenv.c new file mode 100644 index 00000000000..02bc025e216 --- /dev/null +++ b/test/boot/editenv.c @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Test for expo environment editor + * + * Copyright 2025 Google LLC + * Written by Simon Glass + */ + +#include +#include +#include +#include +#include +#include +#include "bootstd_common.h" + +/* Check expo_editenv() basic functionality */ +static int editenv_test_base(struct unit_test_state *uts) +{ + char buf[256]; + int ret; + + /* + * Type "test" then press Enter to accept + * \x0d is Ctrl-M (Enter/carriage return) + */ + console_in_puts("test\x0d"); + ret = expo_editenv("myvar", NULL, buf, sizeof(buf)); + ut_assertok(ret); + ut_asserteq_str("test", buf); + + return 0; +} +BOOTSTD_TEST(editenv_test_base, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); + +/* Check expo_editenv() with initial value - prepend text */ +static int editenv_test_initial(struct unit_test_state *uts) +{ + char buf[256]; + int ret; + + /* + * Start with "world", go to start with Ctrl-A, type "hello ", then + * press Enter + */ + console_in_puts("\x01hello \x0d"); + ret = expo_editenv("myvar", "world", buf, sizeof(buf)); + ut_assertok(ret); + ut_asserteq_str("hello world", buf); + + return 0; +} +BOOTSTD_TEST(editenv_test_initial, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); + +/* Check expo_editenv() escape closes editor (accepts current value) */ +static int editenv_test_escape(struct unit_test_state *uts) +{ + char buf[256]; + int ret; + + /* + * Press Escape immediately - this closes the editor and accepts + * the current (initial) value + */ + console_in_puts("\x1b"); + ret = expo_editenv("myvar", "unchanged", buf, sizeof(buf)); + ut_assertok(ret); + ut_asserteq_str("unchanged", buf); + + return 0; +} +BOOTSTD_TEST(editenv_test_escape, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); + +/* Check expo_editenv() renders correctly */ +static int editenv_test_video(struct unit_test_state *uts) +{ + struct udevice *dev; + char buf[256]; + int ret; + + ut_assertok(uclass_first_device_err(UCLASS_VIDEO, &dev)); + + /* Type "abc" then press Enter */ + console_in_puts("abc\x0d"); + ret = expo_editenv("testvar", "initial", buf, sizeof(buf)); + ut_assertok(ret); + ut_asserteq_str("initialabc", buf); + + /* Check the framebuffer has expected content */ + ut_asserteq(1029, video_compress_fb(uts, dev, false)); + + return 0; +} +BOOTSTD_TEST(editenv_test_video, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); From patchwork Fri Jan 30 03:58:36 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 1788 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org Authentication-Results: mail.u-boot.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.a=rsa-sha256 header.s=google header.b=cHl2Dp5Q; dkim-atps=neutral Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 9E8EE697CB for ; Thu, 29 Jan 2026 20:59:42 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id FpQCLJ_0a2av for ; Thu, 29 Jan 2026 20:59:42 -0700 (MST) Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id A657D697CE for ; Thu, 29 Jan 2026 20:59:40 -0700 (MST) Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id DFA0A69738 for ; Thu, 29 Jan 2026 20:59:37 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id oYkq6GSYmOYz for ; Thu, 29 Jan 2026 20:59:37 -0700 (MST) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=209.85.167.173; helo=mail-oi1-f173.google.com; envelope-from=sjg@chromium.org; receiver=u-boot.org Received: from mail-oi1-f173.google.com (mail-oi1-f173.google.com [209.85.167.173]) by mail.u-boot.org (Postfix) with ESMTPS id 3A807697CB for ; Thu, 29 Jan 2026 20:59:34 -0700 (MST) Received: by mail-oi1-f173.google.com with SMTP id 5614622812f47-45c70afbeebso1143678b6e.0 for ; Thu, 29 Jan 2026 19:59:34 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1769745573; x=1770350373; darn=u-boot.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=BsToSrH9sdKqP0sx8AXQu8h5C0IEUSBbdcbHyXxopoE=; b=cHl2Dp5Q+vz0PZPvdSrezBUtW2X5pNFXRRpAaKE75tkQt79O6sI+j5rwzPyC5rPIkO BDR+VRdscGvecG1edC2APpO8zZkJxZTPRtHG/9oupP/a7U8kl0m9frdzS/Gz2ehQ9rFN oFzp8d3eyPsFkwsu/O86vyIkXrAhFAVQTLu6A= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769745573; x=1770350373; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=BsToSrH9sdKqP0sx8AXQu8h5C0IEUSBbdcbHyXxopoE=; b=MJs+qb4R3jJduc9tb10yzeGRtbeBcLT7iz0lCgtRq3QqFTnV/EYCA/Yht529g6bmnA 5hdjxc2aeiu4Al8EQ6y0dE8GAggdmx3awEzb1/ENAi2Vjs9QtLcE1tetJwl7hz3Ux/oS a1OEtADvYfzPcbGelO1/jHHolyYaYWOIFvO3fGoeM72BrqvGu+mI9CD4YJIQQiavo7sz KdYbVo2jOO6BdofDk4R81To0R5iJXc30kzVZzGiCzORP4LD5oVdUbj4EhEz6Ba8x436N v8Oam5WXhsycz3EC3jd7hgk357cb0qM5MCIZUca3X9+0ihSFMqRJlyj0NPPie5QtqmHy 78WA== X-Gm-Message-State: AOJu0YxouJl65kfTmYavULyosVYUwO6gB10AYHjokZQmvXqNWo/FicvI aduP7i/uX3dErUyDs0+EOn+SznCO51NQonj7IvUIB8f79An8WEBqE47YrOuTzTZ3j8jl17xz3hp BAmb71A== X-Gm-Gg: AZuq6aI1+JgXgK6TCuHKmAU/xdRaElcnsIqYRK/Dp1Hmsv/p47TXUM7BOXQMHlon9Nj XwaavJcluHEyIO+sCJ0U8HRelLepgxK4zdx4MWJxKlYaBDqZeTLN7jTYFsaZxUe7Ur+yevudMrY 4fQa4hqT+eaH7Jw6bPeDyzmKTHzLOXkkpv3FvMe3XjP8fkhuXqu6PIK8QhDK6qBUIJnPpVc/zQU si8P2yz5J1IoUHaBx38lP/6GO19G6UyvPDEJRwErUdvsapOs3yjmWc8ffwR+0hkafBibU4AE9bo fl/PJW/StjTo7gqPVI+2pGTlzGi4FJkqLVaNy/okVyYkB6aSTI7SRYvVOitCnDH9GRAyqaPow4W 4exnxndH0ZZ9xxErOp7DOA64Hn51CyzUg86tGcNcn/1yGi1/RwxGZQo1WABjD9iifo1CjfXQ+65 k6wsQ7Huo5T9h0WOeV X-Received: by 2002:a05:6820:f0a:b0:663:89a:812a with SMTP id 006d021491bc7-6630f035b90mr895737eaf.33.1769745572948; Thu, 29 Jan 2026 19:59:32 -0800 (PST) Received: from chromium.org ([73.34.74.121]) by smtp.gmail.com with ESMTPSA id 006d021491bc7-662f9a4e491sm4128687eaf.16.2026.01.29.19.59.31 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Jan 2026 19:59:31 -0800 (PST) From: Simon Glass X-Google-Original-From: Simon Glass To: U-Boot Concept Date: Thu, 29 Jan 2026 20:58:36 -0700 Message-ID: <20260130035849.3580212-14-simon.glass@canonical.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260130035849.3580212-1-simon.glass@canonical.com> References: <20260130035849.3580212-1-simon.glass@canonical.com> MIME-Version: 1.0 Message-ID-Hash: 4R4FRMEF6HCS3ON2VGK7KIBHCGRU7N3K X-Message-ID-Hash: 4R4FRMEF6HCS3ON2VGK7KIBHCGRU7N3K X-MailFrom: sjg@chromium.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass , "Claude Opus 4 . 5" X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 13/19] test: Add editenv test for init/poll/uninit functions List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Add a test for the expo_editenv_init(), expo_editenv_poll() and expo_editenv_uninit() functions which allow more flexible use of the environment editor. The test uses a helper function editenv_send() to send keys directly to the expo and verify the result. Co-developed-by: Claude Opus 4.5 Signed-off-by: Simon Glass --- test/boot/editenv.c | 84 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/test/boot/editenv.c b/test/boot/editenv.c index 02bc025e216..62a33f1ba0f 100644 --- a/test/boot/editenv.c +++ b/test/boot/editenv.c @@ -9,11 +9,64 @@ #include #include #include +#include #include +#include #include #include #include "bootstd_common.h" +static const char initial[] = + "This is a long string that will wrap to multiple lines " + "when displayed in the textedit widget. It needs to be " + "long enough to span several lines so that the up and down " + "arrow keys can be tested properly.\n" + "The arrow keys should " + "move the cursor between lines in the multiline editor."; + +/** + * editenv_send() - Send a key to the editenv expo + * + * Arranges and renders the scene, sends the key, then checks for any + * resulting action. + * + * @info: Editenv info + * @key: Key to send (ASCII or BKEY_...) + * Return: 0 if OK, 1 if editing is complete, -ECANCELED if user quit, + * other -ve on error + */ +static int editenv_send(struct editenv_info *info, int key) +{ + struct expo_action act; + int ret; + + ret = expo_send_key(info->exp, key); + if (ret) + return ret; + + ret = scene_arrange(info->scn); + if (ret) + return ret; + + ret = expo_render(info->exp); + if (ret) + return ret; + + ret = expo_action_get(info->exp, &act); + if (ret == -EAGAIN) + return 0; + if (ret) + return ret; + + if (act.type == EXPOACT_QUIT) + return -ECANCELED; + + if (act.type == EXPOACT_CLOSE) + return 1; + + return 0; +} + /* Check expo_editenv() basic functionality */ static int editenv_test_base(struct unit_test_state *uts) { @@ -92,3 +145,34 @@ static int editenv_test_video(struct unit_test_state *uts) return 0; } BOOTSTD_TEST(editenv_test_video, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); + +/* Check the init/poll/uninit functions work correctly */ +static int editenv_test_funcs(struct unit_test_state *uts) +{ + struct editenv_info info; + struct udevice *dev, *con; + + ut_assertok(uclass_first_device_err(UCLASS_VIDEO, &dev)); + ut_assertok(uclass_first_device_err(UCLASS_VIDEO_CONSOLE, &con)); + + /* Set font size to 30 */ + ut_assertok(vidconsole_select_font(con, NULL, NULL, 30)); + + ut_assertok(expo_editenv_init("testvar", initial, &info)); + ut_asserteq(16611, ut_check_video(uts, "init")); + + /* Type a character and press Enter to accept */ + ut_assertok(editenv_send(&info, '*')); + ut_asserteq(16689, ut_check_video(uts, "insert")); + + ut_asserteq(1, editenv_send(&info, BKEY_SELECT)); + + /* The '*' should be appended to the initial text */ + ut_assert(strstr(expo_editenv_result(&info), "editor.*")); + ut_asserteq(16689, ut_check_video(uts, "save")); + + expo_editenv_uninit(&info); + + return 0; +} +BOOTSTD_TEST(editenv_test_funcs, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); From patchwork Fri Jan 30 03:58:37 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 1789 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org Authentication-Results: mail.u-boot.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.a=rsa-sha256 header.s=google header.b=Z1KupLNe; dkim-atps=neutral Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 6C3BD697DF for ; Thu, 29 Jan 2026 20:59:45 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id eicyckWcJner for ; Thu, 29 Jan 2026 20:59:45 -0700 (MST) Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 5603F697CB for ; Thu, 29 Jan 2026 20:59:45 -0700 (MST) Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id E09FD697C0 for ; Thu, 29 Jan 2026 20:59:42 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id uqskjwxbllni for ; Thu, 29 Jan 2026 20:59:42 -0700 (MST) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=209.85.160.54; helo=mail-oa1-f54.google.com; envelope-from=sjg@chromium.org; receiver=u-boot.org Received: from mail-oa1-f54.google.com (mail-oa1-f54.google.com [209.85.160.54]) by mail.u-boot.org (Postfix) with ESMTPS id 7EF9C697DF for ; Thu, 29 Jan 2026 20:59:37 -0700 (MST) Received: by mail-oa1-f54.google.com with SMTP id 586e51a60fabf-408778a8ec4so1071050fac.0 for ; Thu, 29 Jan 2026 19:59:37 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1769745576; x=1770350376; darn=u-boot.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=RQZ9ATFr9dBKZl9RUtzMP64cJbV9HCKKuBfrgZs8iEQ=; b=Z1KupLNeL7Z7zbTUpOJ1kxZiunL+hfo9xrNL01O50sjgR3BMv68o+pM9rODtvhXkcU ArfHOj5cmS8pFrV2VcfkQwlg50udD8d4LxcEAjm+jl6inltilkrY6DQ+Cl7iHTmflb6H XwDTIEvtm3VBMpBuOwh7jlcsbB40eKh33don4= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769745576; x=1770350376; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=RQZ9ATFr9dBKZl9RUtzMP64cJbV9HCKKuBfrgZs8iEQ=; b=bsAC/PgEMN1WxoLngjb6u7nDh20ZXOorVaW2k1qYxpAhu6srzrqeh+glqu7mZpYQA5 y5fNe8JFyShmfOj5Ae43urTL9/SHeRv/WUefN8m4f5dRVk57Z4RKH/3stl6Lvv2axb+z uyqhr9eOVR/e6xbJSzHKlKmURIhIFTpnIayLPgBRERRVXp0Qu8RXOJ+w7UnW1reysG+s TUBTHoX1eL3kXcSxk3CbCzED8SpbfdWPfBwGo7oN9Rjb5UJnjR8dynyKM3d8VuscsB0k Nk6QtKs0DP8mu3gM1vWoW8h8XD9eCnt3k9h4zTyQw+XmNxU9b7fg7/L3Tvso7YMq8QRE HC7Q== X-Gm-Message-State: AOJu0YyqIGY/qbzSaOvPDVr4qtjYEVfCVGazD4mOHhRbZTw4cqZXaCEn aCHlP5O/wSWiwGAKqjFcFz57n8ZVYbdPQ2UIqdOr70OifQkJz9LtamA/xEcT+vwZYNrAFppG4fE ZWAF0Aw== X-Gm-Gg: AZuq6aJebTYBc76hXHYo/kI9Kgw5jCSawXOv0/aS9AyL6wIqwhkbuiKmtYUM1WyfvoA YJX7ZWFJNQq4xbA5ViJNqa2NNRDBsoO6HkLhO7lSFr+iy1hogEQRVVvkZus7SlzRE9ivAagBC5W bWSJIeT4rvZ/EdEEI/UTgsaKCixF9tDXrdwyQv6y0WHnuOWXjs0UK3GfScYsb9GcDrAp8OWTYCo Z/rpoZqLvNEkq5zvNUtts18cnhGaM9kbkpQVZGDVc/+6cl8H65ilFJj3K5EM0LUGLhyELsY6GIf ijgoNoNR64pmp/oRcOBLG0UPG/6iGCIaaHyrclZHEv2usDC+5uxrd9O4NjDvkZGtOLNWfSpiGnI EoRf6YD7Pe+Gm2gWA4d5HgujMN7DDSNQObOwbePGHISpd+Zu7fV4xFcanTS/+eCx0Efc2MecysQ FkDG38MiopCvjNGy07 X-Received: by 2002:a05:6820:3092:b0:662:c114:b28b with SMTP id 006d021491bc7-6630f00427fmr992165eaf.16.1769745576027; Thu, 29 Jan 2026 19:59:36 -0800 (PST) Received: from chromium.org ([73.34.74.121]) by smtp.gmail.com with ESMTPSA id 006d021491bc7-662f9a4e491sm4128687eaf.16.2026.01.29.19.59.33 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Jan 2026 19:59:34 -0800 (PST) From: Simon Glass X-Google-Original-From: Simon Glass To: U-Boot Concept Date: Thu, 29 Jan 2026 20:58:37 -0700 Message-ID: <20260130035849.3580212-15-simon.glass@canonical.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260130035849.3580212-1-simon.glass@canonical.com> References: <20260130035849.3580212-1-simon.glass@canonical.com> MIME-Version: 1.0 Message-ID-Hash: PYKGEDSQG3RXCW24ZQDIUYZ7ZJB3TIQ5 X-Message-ID-Hash: PYKGEDSQG3RXCW24ZQDIUYZ7ZJB3TIQ5 X-MailFrom: sjg@chromium.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass , "Claude Opus 4 . 5" X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 14/19] expo: Convert BKEY_UP/DOWN to control characters for text input List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: The expo keyboard handling converts arrow keys to BKEY_UP and BKEY_DOWN before sending them to scene objects. However, cread_line_process_ch() expects CTL_CH('p') and CTL_CH('n') for line navigation in multiline text editors. Add explicit handling for BKEY_UP and BKEY_DOWN in scene_txtin_send_key() to convert them back to the control characters that cread_line_process_ch() expects. This fixes arrow key navigation in textedit objects. Update the editenv_test_video() to use a longer string and a larger font size so we can test this. Co-developed-by: Claude Opus 4.5 Signed-off-by: Simon Glass --- boot/scene_txtin.c | 6 ++++++ test/boot/editenv.c | 26 ++++++++++++++++++-------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/boot/scene_txtin.c b/boot/scene_txtin.c index 79814891cdc..8b49daddd56 100644 --- a/boot/scene_txtin.c +++ b/boot/scene_txtin.c @@ -349,6 +349,12 @@ int scene_txtin_send_key(struct scene_obj *obj, struct scene_txtin *tin, event->select.id = obj->id; scene_txtin_close(scn, tin); break; + case BKEY_UP: + cread_line_process_ch(cls, CTL_CH('p')); + break; + case BKEY_DOWN: + cread_line_process_ch(cls, CTL_CH('n')); + break; default: cread_line_process_ch(cls, key); break; diff --git a/test/boot/editenv.c b/test/boot/editenv.c index 62a33f1ba0f..0f9db54474d 100644 --- a/test/boot/editenv.c +++ b/test/boot/editenv.c @@ -124,23 +124,33 @@ static int editenv_test_escape(struct unit_test_state *uts) } BOOTSTD_TEST(editenv_test_escape, UTF_DM | UTF_SCAN_FDT | UTF_CONSOLE); -/* Check expo_editenv() renders correctly */ +/* Check expo_editenv() renders correctly with multiline text and navigation */ static int editenv_test_video(struct unit_test_state *uts) { - struct udevice *dev; - char buf[256]; + struct udevice *dev, *con; + char buf[512]; int ret; ut_assertok(uclass_first_device_err(UCLASS_VIDEO, &dev)); + ut_assertok(uclass_first_device_err(UCLASS_VIDEO_CONSOLE, &con)); - /* Type "abc" then press Enter */ - console_in_puts("abc\x0d"); - ret = expo_editenv("testvar", "initial", buf, sizeof(buf)); + /* Set font size to 30 */ + ut_assertok(vidconsole_select_font(con, NULL, NULL, 30)); + + /* + * Navigate with up arrow, insert text, then press Enter. The up arrow + * should be converted to Ctrl-P by scene_txtin_send_key(). + * \x1b[A is the escape sequence for up arrow + */ + console_in_puts("\x1b[A!\x0d"); + ret = expo_editenv("testvar", initial, buf, sizeof(buf)); ut_assertok(ret); - ut_asserteq_str("initialabc", buf); + + /* The '!' should be inserted one visual line up from the end */ + ut_assert(strstr(buf, "tes!ted")); /* Check the framebuffer has expected content */ - ut_asserteq(1029, video_compress_fb(uts, dev, false)); + ut_asserteq(16829, video_compress_fb(uts, dev, false)); return 0; } From patchwork Fri Jan 30 03:58:38 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 1790 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org Authentication-Results: mail.u-boot.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.a=rsa-sha256 header.s=google header.b=QNHX0+it; dkim-atps=neutral Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id D4878697F9 for ; Thu, 29 Jan 2026 20:59:47 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id HakdLfs7ASLe for ; Thu, 29 Jan 2026 20:59:47 -0700 (MST) Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id D3D28697C0 for ; Thu, 29 Jan 2026 20:59:45 -0700 (MST) Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id E3D13697DF for ; Thu, 29 Jan 2026 20:59:42 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id UzFLOuYYEcR0 for ; Thu, 29 Jan 2026 20:59:42 -0700 (MST) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=209.85.160.52; helo=mail-oa1-f52.google.com; envelope-from=sjg@chromium.org; receiver=u-boot.org Received: from mail-oa1-f52.google.com (mail-oa1-f52.google.com [209.85.160.52]) by mail.u-boot.org (Postfix) with ESMTPS id 7480469738 for ; Thu, 29 Jan 2026 20:59:40 -0700 (MST) Received: by mail-oa1-f52.google.com with SMTP id 586e51a60fabf-4097b420ce0so1034040fac.2 for ; Thu, 29 Jan 2026 19:59:40 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1769745579; x=1770350379; darn=u-boot.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=PedH6FkvczNA6vv11FqO02i1jDRoJ029ifROI8COUbo=; b=QNHX0+itpv0yyxVwBURUrVepH+1peBPkgZYHMbZTJpWRlLTfOp1fAVKO5jyFHebcN3 jxz84J1d927XODk2h/JHHwa4H4LtbMUx11PUzJIvH1L9jZS3g/eB6aHdDTfq6Yqr8/wa Z1fTL/0oG8Gaf9Ze52cT98Obg62IB4sQy2ZNM= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769745579; x=1770350379; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=PedH6FkvczNA6vv11FqO02i1jDRoJ029ifROI8COUbo=; b=VivVOGLd8sHJEgIi7VxNr26iAWFn68Lftl/mSlsDTedXIiG5vEUk87ynvYLfodD0+F bdkkfYRBxkJpSOFHuHhdsnsadxNznr04VNvWUrQA4aVb/y3OJ3G/WaHW7UDLnI8a4bcJ eY1ADPmCYXX2huAdiHYMQxkWbJhCxk2QQL4GLV4u+MyiF68HYwTT8EyJeWLWe1KNtXFv tOCl83PP6xjhIVHeQLOYSMf9FNtdtZmoOKxRBWVH335mgAKDCJvialIRiWX44edfbYr2 eytgifZoAsRfWW7N8ks6judfX/NrerEcjl+zIcIcq8AjisT5dmO5nC8NzdVn+JrDh1zl Xm6w== X-Gm-Message-State: AOJu0YwOEN/fPH68NTaJZaIdCrhv1DNek0mNb9ApWqJKn/18aQe9Xc4Q Qsm8fE6D8sAVwSWmzUyCG9V7ACvICm4KtjWN1ZZGSdKLkU2ekm5qMD8HfAZWuzTwTTOU2En6ON8 cInLHDw== X-Gm-Gg: AZuq6aKP/XO8QL/BRx8VUiUX2ZWFnthM9SfwWqSsIk9gBtv2e9sPRwTt3WVxN7CeSPl ZPkRD8MS4zuOTyOo8rWPpo2v3fVbUqWqVM8KIerlZlCJ7etyP7QqSP9ubqJ1wNevSoVLMdEN17q jJStnSzSYFazMik1Jdy4zrwA96pu98SpdoGEGzAvH1DJE+Qc1RWbploUKiRRWKAlyTIC7I7yFk9 Z4to3V5p7ZggFSwh+KDiKYXyUTT+NSWa6kHDFIiel2bcs0yz1yMQBSl9ziC7n7Q3m4swvVlT2G4 acSP7lg322N/3AOPsUGQFJ8mAJqN5PCr+56Et9iHQg8H0H34+yMsXRhXofH9iJVlA1BoYiaMPuO 7VtZrwcvCn+SxwxOSV85zdJf6p8/XVGCDaux6GmJv6ledOHbW+fft2I9Wk0AimB0V319t+ypnrr lHh9BEKXBwTkkIp6CR X-Received: by 2002:a05:6820:4dfb:b0:663:d21:9f2 with SMTP id 006d021491bc7-6630f004395mr788533eaf.8.1769745579091; Thu, 29 Jan 2026 19:59:39 -0800 (PST) Received: from chromium.org ([73.34.74.121]) by smtp.gmail.com with ESMTPSA id 006d021491bc7-662f9a4e491sm4128687eaf.16.2026.01.29.19.59.36 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Jan 2026 19:59:37 -0800 (PST) From: Simon Glass X-Google-Original-From: Simon Glass To: U-Boot Concept Date: Thu, 29 Jan 2026 20:58:38 -0700 Message-ID: <20260130035849.3580212-16-simon.glass@canonical.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260130035849.3580212-1-simon.glass@canonical.com> References: <20260130035849.3580212-1-simon.glass@canonical.com> MIME-Version: 1.0 Message-ID-Hash: B2HYPPWDWQUHWZTFSJKJKF6CRD47C5Y2 X-Message-ID-Hash: B2HYPPWDWQUHWZTFSJKJKF6CRD47C5Y2 X-MailFrom: sjg@chromium.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass , "Claude Opus 4 . 5" X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 15/19] cli: Add Ctrl+Left/Right arrow support for word navigation List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Add support for Ctrl+Left and Ctrl+Right arrow keys to move the cursor by word in the command line editor. Ctrl+Left moves backward to the start of the previous word, and Ctrl+Right moves forward to the end of the next word. Decode the escape sequences (ESC[1;5D and ESC[1;5C) arein cli_getch.c and convert then to CTL_CH('r') and CTL_CH('t') respectively. Handle the actual word-movement logic is then handled in cli_readline.c, guarded by CONFIG_CMDLINE_EDITOR Co-developed-by: Claude Opus 4.5 Signed-off-by: Simon Glass --- common/cli_getch.c | 25 +++++++++++++++++++++++++ common/cli_readline.c | 31 +++++++++++++++++++++++++++++++ test/boot/editenv.c | 8 ++++++++ 3 files changed, 64 insertions(+) diff --git a/common/cli_getch.c b/common/cli_getch.c index a5ed6eb6fcf..8df1997911a 100644 --- a/common/cli_getch.c +++ b/common/cli_getch.c @@ -114,6 +114,12 @@ static int cli_ch_esc(struct cli_ch_state *cch, int ichar, if (cch->esc_save[2] == '2') act = ESC_SAVE; break; + case ';': + /* Ctrl+arrow: ESC [ 1 ; */ + if (CONFIG_IS_ENABLED(CMDLINE_EDITOR) && + cch->esc_save[2] == '1') + act = ESC_SAVE; + break; } break; case 4: @@ -122,12 +128,31 @@ static int cli_ch_esc(struct cli_ch_state *cch, int ichar, case '1': act = ESC_SAVE; break; /* bracketed paste */ + case '5': + /* Ctrl+arrow: ESC [ 1 ; 5 */ + if (CONFIG_IS_ENABLED(CMDLINE_EDITOR) && + cch->esc_save[3] == ';') + act = ESC_SAVE; + break; } break; case 5: if (ichar == '~') { /* bracketed paste */ ichar = 0; act = ESC_CONVERTED; + } else if (CONFIG_IS_ENABLED(CMDLINE_EDITOR) && + cch->esc_save[4] == '5') { + /* Ctrl+arrow: ESC [ 1 ; 5 D/C */ + switch (ichar) { + case 'D': /* Ctrl+<- key */ + ichar = CTL_CH('r'); + act = ESC_CONVERTED; + break; /* pass to backward-word handler */ + case 'C': /* Ctrl+-> key */ + ichar = CTL_CH('t'); + act = ESC_CONVERTED; + break; /* pass to forward-word handler */ + } } } diff --git a/common/cli_readline.c b/common/cli_readline.c index d2b02933fd7..4c25e9a04ba 100644 --- a/common/cli_readline.c +++ b/common/cli_readline.c @@ -333,6 +333,37 @@ int cread_line_process_ch(struct cli_line_state *cls, char ichar) cls->num--; } break; + case CTL_CH('r'): /* backward-word */ + if (CONFIG_IS_ENABLED(CMDLINE_EDITOR) && cls->num) { + uint pos = cls->num; + + /* skip spaces before word */ + while (pos > 0 && buf[pos - 1] == ' ') + pos--; + /* skip word characters */ + while (pos > 0 && buf[pos - 1] != ' ') + pos--; + cls_putchars(cls, cls->num - pos, CTL_BACKSPACE); + cls->num = pos; + } + break; + case CTL_CH('t'): /* forward-word */ + if (CONFIG_IS_ENABLED(CMDLINE_EDITOR) && cls->num < cls->eol_num) { + uint pos = cls->num; + + /* skip spaces after cursor */ + while (pos < cls->eol_num && buf[pos] == ' ') { + cls_putch(cls, buf[pos]); + pos++; + } + /* skip word characters */ + while (pos < cls->eol_num && buf[pos] != ' ') { + cls_putch(cls, buf[pos]); + pos++; + } + cls->num = pos; + } + break; case CTL_CH('d'): if (cls->num < cls->eol_num) { uint wlen; diff --git a/test/boot/editenv.c b/test/boot/editenv.c index 0f9db54474d..20273c53647 100644 --- a/test/boot/editenv.c +++ b/test/boot/editenv.c @@ -171,6 +171,14 @@ static int editenv_test_funcs(struct unit_test_state *uts) ut_assertok(expo_editenv_init("testvar", initial, &info)); ut_asserteq(16611, ut_check_video(uts, "init")); + /* Navigate up to previous line */ + ut_assertok(editenv_send(&info, BKEY_UP)); + ut_asserteq(16684, ut_check_video(uts, "up")); + + /* Navigate back down */ + ut_assertok(editenv_send(&info, BKEY_DOWN)); + ut_asserteq(16611, ut_check_video(uts, "down")); + /* Type a character and press Enter to accept */ ut_assertok(editenv_send(&info, '*')); ut_asserteq(16689, ut_check_video(uts, "insert")); From patchwork Fri Jan 30 03:58:39 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 1791 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org Authentication-Results: mail.u-boot.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.a=rsa-sha256 header.s=google header.b=VSrUxW/E; dkim-atps=neutral Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 3DAB2697DF for ; Thu, 29 Jan 2026 20:59:48 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id mhIpUgsUTFxf for ; Thu, 29 Jan 2026 20:59:48 -0700 (MST) Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 8D736697EA for ; Thu, 29 Jan 2026 20:59:47 -0700 (MST) Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 8BB8B697E8 for ; Thu, 29 Jan 2026 20:59:45 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id quqfTsDSCvri for ; Thu, 29 Jan 2026 20:59:45 -0700 (MST) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=209.85.161.41; helo=mail-oo1-f41.google.com; envelope-from=sjg@chromium.org; receiver=u-boot.org Received: from mail-oo1-f41.google.com (mail-oo1-f41.google.com [209.85.161.41]) by mail.u-boot.org (Postfix) with ESMTPS id EC10C697DD for ; Thu, 29 Jan 2026 20:59:41 -0700 (MST) Received: by mail-oo1-f41.google.com with SMTP id 006d021491bc7-662f2fa7e67so634882eaf.1 for ; Thu, 29 Jan 2026 19:59:41 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1769745581; x=1770350381; darn=u-boot.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=LhxIORmTTG2ge2cWorK8EFPo3UMupl96NwELofm+erE=; b=VSrUxW/E7fTimvwbu2KSisiM6TyuuZ5HDNOJChpDp4tdl/Wqm3gmA41pHIaLgAzlGJ XjMT199eVvWRrs2clMu9VGcPtNS2jouHLZkxYvTXTGuABbd315Kx/xvUSYU3Rg0Z589g aM39gkNGOHFKj2KdXsWiBk3eSmrGHfIBSxHJ0= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769745581; x=1770350381; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=LhxIORmTTG2ge2cWorK8EFPo3UMupl96NwELofm+erE=; b=rw+ZcsfzR36V8nj46GKkGYMz1m2Ib0kCvO30jbL+yWRqYuxLDm7CmxwnCfQhIzWpdI ktGCL1hTDqkBa+dM+Jt91LBLcPkQZjaH2D6Mt7B0sVFgu3TkcB+X5KwuV0+yEqpJDTOq fU/H/kqtmbxk7PgjMcGLhW5f7/WPGXRUk9bEhdL3aw5QFD44gH3gz7eQjswxYbJUD+wP veeODr1sEpzWYvPvxvSnZk1EiUg//YpenUlOc9zdIRugSGku38QINbE/LUhg0Ls0y03f TECUS4CoAoH4O4pHz7C4w6hEsxh6nYgAx0XpcEHb94445CW4gTBmnCsNT1q/XFNmlFkX 1hHw== X-Gm-Message-State: AOJu0YzQVtzeV7YCJRkUOPe0FK7zBm1uggvEPfTVK1/ffi7aTECFkvn2 Qg/0+Hz7zah9dkV1a/Clusuz04SIcn4VkUnGZM/A6WvsGt3s9Uf+v87w+W6CBR7sViC85B594bt ovY9mYg== X-Gm-Gg: AZuq6aIGFcbg7zsyhcnwk7mKHEk6DeQs4VTKFeYkevGkAbxzUxaNDE23k8mLCA1DvG/ yrrwnhqe0M9CWiwsbHPx/6vzaw12wr2V4ra32YCzns1BKd7gRV+1YA3FGrsQRVhLqwSu/iLUN/b CCd/LJkQud+xix0WArIfkwTgLaDWVqceul0G9xjGQ+gr8W15BcqtUgu3ErsDxle6WpES3wt75+E IG/goO655TBtLVPyNWt3XNL2lJEbCypupA5O6zjmf/k2rQijMZn/EbzGP2XE+2B6fqZotsf3YKe ldB6sw6oTWbS0YW7EFa3H2TZZSpyn398HRgVnJCwQxybgE2khLhkAG13xm7G2scGibGNC9sGlKE AuDh3WiEjTiohJhsNzZK1MWYjXHvgEVChLagGeZQEya/3xEjSI48x6SGcc1rVw9jhw/mRIWa3xZ oc4JaxMAOT6CYuWq4O X-Received: by 2002:a05:6820:179a:b0:654:18f9:10f4 with SMTP id 006d021491bc7-6630ef30dd8mr824900eaf.0.1769745580636; Thu, 29 Jan 2026 19:59:40 -0800 (PST) Received: from chromium.org ([73.34.74.121]) by smtp.gmail.com with ESMTPSA id 006d021491bc7-662f9a4e491sm4128687eaf.16.2026.01.29.19.59.39 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Jan 2026 19:59:40 -0800 (PST) From: Simon Glass X-Google-Original-From: Simon Glass To: U-Boot Concept Date: Thu, 29 Jan 2026 20:58:39 -0700 Message-ID: <20260130035849.3580212-17-simon.glass@canonical.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260130035849.3580212-1-simon.glass@canonical.com> References: <20260130035849.3580212-1-simon.glass@canonical.com> MIME-Version: 1.0 Message-ID-Hash: W6URR4HH2SOYLAVYRKKXMKU6J2MYEMFN X-Message-ID-Hash: W6URR4HH2SOYLAVYRKKXMKU6J2MYEMFN X-MailFrom: sjg@chromium.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass , "Claude Opus 4 . 5" X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 16/19] expo: Add SCENEOF_MULTILINE flag for textedit List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Add a new flag SCENEOF_MULTILINE that allows the textedit widget to accept multiline input. When this flag is set, pressing Enter inserts a newline character instead of closing the textedit. Co-developed-by: Claude Opus 4.5 Signed-off-by: Simon Glass --- boot/editenv.c | 2 ++ boot/scene_txtin.c | 21 ++++++++++++++++++++- include/expo.h | 2 ++ test/boot/editenv.c | 22 +++++++++++----------- test/boot/expo.c | 16 ++++++++++++++-- 5 files changed, 49 insertions(+), 14 deletions(-) diff --git a/boot/editenv.c b/boot/editenv.c index de7df6fe3fa..fd7670263b7 100644 --- a/boot/editenv.c +++ b/boot/editenv.c @@ -47,6 +47,7 @@ static int editenv_setup(struct expo *exp, struct udevice *dev, ret = scene_texted(scn, "textedit", EDITENV_OBJ_TEXTEDIT, 70, &ted); if (ret < 0) return log_msg_ret("ted", ret); + ted->obj.flags |= SCENEOF_MULTILINE; ret = scene_obj_set_bbox(scn, EDITENV_OBJ_TEXTEDIT, 50, 200, 1300, 400); if (ret < 0) @@ -122,6 +123,7 @@ int expo_editenv_init(const char *varname, const char *value, if (ret) { expo_destroy(exp); return log_msg_ret("set", ret); + } return 0; diff --git a/boot/scene_txtin.c b/boot/scene_txtin.c index 8b49daddd56..ab4fd5056a0 100644 --- a/boot/scene_txtin.c +++ b/boot/scene_txtin.c @@ -342,13 +342,32 @@ int scene_txtin_send_key(struct scene_obj *obj, struct scene_txtin *tin, log_debug("menu quit\n"); } break; - case BKEY_SELECT: + case BKEY_SAVE: if (!open) break; + /* Accept contents even in multiline mode */ event->type = EXPOACT_CLOSE; event->select.id = obj->id; scene_txtin_close(scn, tin); break; + case BKEY_SELECT: + if (!open) + break; + if (obj->flags & SCENEOF_MULTILINE) { + char *buf = cls->buf; + int wlen = cls->eol_num - cls->num; + + /* Insert newline at cursor position */ + memmove(&buf[cls->num + 1], &buf[cls->num], wlen); + buf[cls->num] = '\n'; + cls->num++; + cls->eol_num++; + } else { + event->type = EXPOACT_CLOSE; + event->select.id = obj->id; + scene_txtin_close(scn, tin); + } + break; case BKEY_UP: cread_line_process_ch(cls, CTL_CH('p')); break; diff --git a/include/expo.h b/include/expo.h index 5a35d72c58f..3846b58abdd 100644 --- a/include/expo.h +++ b/include/expo.h @@ -321,6 +321,7 @@ enum scene_obj_align { * @SCENEOF_MANUAL: manually arrange the items associated with this object * @SCENEOF_DIRTY: object has been modified and needs to be redrawn * @SCENEOF_PASSWORD: textline input should show stars instead of characters + * @SCENEOF_MULTILINE: textedit allows multiline input (Enter adds newline) * @SCENEOF_LAST: used just as a check for the size of the flags mask */ enum scene_obj_flags_t { @@ -335,6 +336,7 @@ enum scene_obj_flags_t { SCENEOF_MANUAL = BIT(8), SCENEOF_DIRTY = BIT(9), SCENEOF_PASSWORD = BIT(10), + SCENEOF_MULTILINE = BIT(11), SCENEOF_LAST, /* check for size of flags below */ }; diff --git a/test/boot/editenv.c b/test/boot/editenv.c index 20273c53647..69e543ea51f 100644 --- a/test/boot/editenv.c +++ b/test/boot/editenv.c @@ -74,10 +74,10 @@ static int editenv_test_base(struct unit_test_state *uts) int ret; /* - * Type "test" then press Enter to accept - * \x0d is Ctrl-M (Enter/carriage return) + * Type "test" then press Ctrl-S to save + * \x13 is Ctrl-S */ - console_in_puts("test\x0d"); + console_in_puts("test\x13"); ret = expo_editenv("myvar", NULL, buf, sizeof(buf)); ut_assertok(ret); ut_asserteq_str("test", buf); @@ -94,9 +94,9 @@ static int editenv_test_initial(struct unit_test_state *uts) /* * Start with "world", go to start with Ctrl-A, type "hello ", then - * press Enter + * press Ctrl-S to save */ - console_in_puts("\x01hello \x0d"); + console_in_puts("\x01hello \x13"); ret = expo_editenv("myvar", "world", buf, sizeof(buf)); ut_assertok(ret); ut_asserteq_str("hello world", buf); @@ -138,11 +138,11 @@ static int editenv_test_video(struct unit_test_state *uts) ut_assertok(vidconsole_select_font(con, NULL, NULL, 30)); /* - * Navigate with up arrow, insert text, then press Enter. The up arrow - * should be converted to Ctrl-P by scene_txtin_send_key(). - * \x1b[A is the escape sequence for up arrow + * Navigate with up arrow, insert text, then press Ctrl-S to save. + * The up arrow should be converted to Ctrl-P by scene_txtin_send_key(). + * \x1b[A is the escape sequence for up arrow, \x13 is Ctrl-S (save) */ - console_in_puts("\x1b[A!\x0d"); + console_in_puts("\x1b[A!\x13"); ret = expo_editenv("testvar", initial, buf, sizeof(buf)); ut_assertok(ret); @@ -179,11 +179,11 @@ static int editenv_test_funcs(struct unit_test_state *uts) ut_assertok(editenv_send(&info, BKEY_DOWN)); ut_asserteq(16611, ut_check_video(uts, "down")); - /* Type a character and press Enter to accept */ + /* Type a character and press Ctrl-S to save */ ut_assertok(editenv_send(&info, '*')); ut_asserteq(16689, ut_check_video(uts, "insert")); - ut_asserteq(1, editenv_send(&info, BKEY_SELECT)); + ut_asserteq(1, editenv_send(&info, BKEY_SAVE)); /* The '*' should be appended to the initial text */ ut_assert(strstr(expo_editenv_result(&info), "editor.*")); diff --git a/test/boot/expo.c b/test/boot/expo.c index 824fd0cca38..f598b9cb86c 100644 --- a/test/boot/expo.c +++ b/test/boot/expo.c @@ -1703,7 +1703,19 @@ static int expo_render_textedit(struct unit_test_state *uts) ut_assertok(expo_render(exp)); ut_asserteq(21083, video_compress_fb(uts, dev, false)); - /* close the textedit with Enter (BKEY_SELECT) */ + /* set multiline mode and check Enter inserts newline */ + ted->obj.flags |= SCENEOF_MULTILINE; + ut_assertok(expo_send_key(exp, BKEY_SELECT)); + ut_asserteq(90, ted->tin.cls.num); + ut_asserteq(90, ted->tin.cls.eol_num); + ut_assert(ted->obj.flags & SCENEOF_OPEN); + ut_asserteq('\n', ((char *)abuf_data(&ted->tin.buf))[89]); + ut_assertok(scene_arrange(scn)); + ut_assertok(expo_render(exp)); + ut_asserteq(21091, video_compress_fb(uts, dev, false)); + + /* clear multiline mode, close the textedit with Enter (BKEY_SELECT) */ + ted->obj.flags &= ~SCENEOF_MULTILINE; ut_assertok(expo_send_key(exp, BKEY_SELECT)); ut_assertok(expo_action_get(exp, &act)); ut_asserteq(EXPOACT_CLOSE, act.type); @@ -1713,7 +1725,7 @@ static int expo_render_textedit(struct unit_test_state *uts) /* check the textedit is closed and text is changed */ ut_asserteq(0, ted->obj.flags & SCENEOF_OPEN); ut_asserteq_str("his\nis the initial contents of the text " - "editor but it is ely that more will be added latr", + "editor but it is ely that more will be added latr\n", abuf_data(&ted->tin.buf)); ut_assertok(scene_arrange(scn)); ut_assertok(expo_render(exp)); From patchwork Fri Jan 30 03:58:40 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 1792 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org Authentication-Results: mail.u-boot.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.a=rsa-sha256 header.s=google header.b=G/QNuQlz; dkim-atps=neutral Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id AC91969738 for ; Thu, 29 Jan 2026 20:59:50 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id jqgzCPvZkhN6 for ; Thu, 29 Jan 2026 20:59:50 -0700 (MST) Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 0F1A3697CB for ; Thu, 29 Jan 2026 20:59:49 -0700 (MST) Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 1F6AF697E8 for ; Thu, 29 Jan 2026 20:59:48 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id hIEAmEgLI2Mi for ; Thu, 29 Jan 2026 20:59:48 -0700 (MST) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=209.85.210.42; helo=mail-ot1-f42.google.com; envelope-from=sjg@chromium.org; receiver=u-boot.org Received: from mail-ot1-f42.google.com (mail-ot1-f42.google.com [209.85.210.42]) by mail.u-boot.org (Postfix) with ESMTPS id 9675A69738 for ; Thu, 29 Jan 2026 20:59:43 -0700 (MST) Received: by mail-ot1-f42.google.com with SMTP id 46e09a7af769-7cfd95f77a3so1102361a34.0 for ; Thu, 29 Jan 2026 19:59:43 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1769745582; x=1770350382; darn=u-boot.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=d04mrpUelM5OQMjEgvAUUsV147RQGD2/eQU1nfX01u0=; b=G/QNuQlzWh6Q/FhbnX382Imw7HPkOQwxhvs3P8nf45YxUQl+qfo82ofGXKv4dnSiUL AC5n6Lj1sV4WK7p5/eYS4+FzIGV5bSDFVTmPViu35l5RZYfBgT5X8ktI+v4vtlb8Mi4O frCj3ztUunBXdrnmMOIk2JehT/kpC7lH2GK+Q= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769745582; x=1770350382; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=d04mrpUelM5OQMjEgvAUUsV147RQGD2/eQU1nfX01u0=; b=f81dcVfYhmva0ItBPdfWp1lyQXSH7gk+NUFxbmZ3ZLrPWcUdvCES6IYxG0kVntHVTx Hndo2Y1sluFCw0doo90BCpe4BXBLTB45qVgR4EZu8f6J1TW2RtxZCVQyPXwERHA50POB NmsdQZQxWnnCR1oqV1+te58l6mHHwqqLbS+tHIBD3cALbqG74oQnycACcnfYaRkCNODZ S2shzeIiSDwsPSbvhAXKizoJ7MsJ1YZOOLDuFDPotDRvOD0He26OWdF86wNYvFFrYqpA 3Ersv5HPBMOUggNK/Xpd9JhYl3iF5hvV6iIOY1SB45odkVV9yUHXIcsX/xNaUSSQ6VLv ACGw== X-Gm-Message-State: AOJu0YxP4klFnPjuFIt5tpMTB5uRmaKdR+gvLPH9XmU9TZv72SQI9aPb Yqb4bu3DIVKtWjVCds/7SzEZuhZ1bJ15X/UZtOeAiqYjn6+683V6xdksr6D4UIKpei89Ic3vRx9 A6z2/6g== X-Gm-Gg: AZuq6aLu0PVwwVmkD+3DZJrPxoIbrBVb0ghzzgoFdzaW/yUs9BR8Rx4KfZkA5sNQEG0 /5RwxBp0n59a/AB556LEg4KQEZ2jVlLUkwcbPxpgr0kHwctYC4opgnH93Y0cTXIBxdn1/d1Mlpz vM1rUOfKNJWejHuejxTa4pxrA3tVaUlpyZ+BOtgF2JKdwA5tSG0dKipdpArggjbIyul/vg0pVdL Ak5ziuKSosAAyf3a1TcBSWMIWVHLsJj9zqssYR9yVob3tuFKRW/EFfUBpHHSH8ain4DONhE7xTK yTUp4NNGiXEQuXgKKd9bvN4gAZY1pQCVSB1A+kfpavM5r/dInEmIg0imYTXpGQlt/DSwuheqysj 6OZQdeehuL27bR/PNjiD0F/fG376i6FsrOnvQrJ+OeT8C527bqnoMoUozZkypMkQHKE8xziQ5iQ 32B0FOMLIgbtlOQM+fLB4ef81yHkw= X-Received: by 2002:a05:6820:260:b0:663:1239:9e9e with SMTP id 006d021491bc7-6631239a1eamr218693eaf.55.1769745582297; Thu, 29 Jan 2026 19:59:42 -0800 (PST) Received: from chromium.org ([73.34.74.121]) by smtp.gmail.com with ESMTPSA id 006d021491bc7-662f9a4e491sm4128687eaf.16.2026.01.29.19.59.40 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Jan 2026 19:59:41 -0800 (PST) From: Simon Glass X-Google-Original-From: Simon Glass To: U-Boot Concept Date: Thu, 29 Jan 2026 20:58:40 -0700 Message-ID: <20260130035849.3580212-18-simon.glass@canonical.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260130035849.3580212-1-simon.glass@canonical.com> References: <20260130035849.3580212-1-simon.glass@canonical.com> MIME-Version: 1.0 Message-ID-Hash: 7TD3444OXGCMMEP52QNJKA4H7V7CBRAV X-Message-ID-Hash: 7TD3444OXGCMMEP52QNJKA4H7V7CBRAV X-MailFrom: sjg@chromium.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass , "Claude Opus 4 . 5" X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 17/19] cli: Make Ctrl+K clear to end of line in multiline mode List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: In multiline mode, Ctrl+K (kill to end of line) currently erases all text from the cursor to the end of the buffer. This is not the expected behaviour for multiline editing. Update cread_erase_to_eol() to only erase to the next newline character in multiline mode, preserving text on subsequent lines. Use a parameterized ERASE_TO() macro to share the erase logic between the multiline-aware function (when CMDLINE_EDITOR is enabled) and a simpler version (when disabled), avoiding code growth on boards without CMDLINE_EDITOR Co-developed-by: Claude Opus 4.5 Signed-off-by: Simon Glass --- common/cli_readline.c | 114 +++++++++++++++++++++++++++++++++++++++--- test/boot/editenv.c | 17 +++++-- test/boot/expo.c | 33 ++++++++---- 3 files changed, 144 insertions(+), 20 deletions(-) diff --git a/common/cli_readline.c b/common/cli_readline.c index 4c25e9a04ba..847b49450b5 100644 --- a/common/cli_readline.c +++ b/common/cli_readline.c @@ -231,13 +231,86 @@ void cread_print_hist_list(void) } } -#define BEGINNING_OF_LINE() { \ - while (cls->num) { \ +#define GOTO_LINE_START(target) { \ + while (cls->num > (target)) { \ cls_putch(cls, CTL_BACKSPACE); \ cls->num--; \ } \ } +#define ERASE_TO(erase_to) { \ + if (cls->num < (erase_to)) { \ + uint wlen = (erase_to) - cls->num; \ + \ + /* erase characters on screen */ \ + printf("%*s", wlen, ""); \ + while (wlen--) \ + cls_putch(cls, CTL_BACKSPACE); \ + \ + /* remove characters from buffer */ \ + memmove(&buf[cls->num], &buf[erase_to], \ + cls->eol_num - (erase_to) + 1); \ + cls->eol_num -= (erase_to) - cls->num; \ + } \ +} + +#if CONFIG_IS_ENABLED(CMDLINE_EDITOR) +/** + * cread_start_of_line() - Move cursor to start of line + * + * In multiline mode, moves to the character after the previous newline. + * Otherwise moves to position 0. + * + * @cls: CLI line state + */ +static void cread_start_of_line(struct cli_line_state *cls) +{ + struct cli_editor_state *ed = cli_editor(cls); + uint target = 0; + + if (ed && ed->multiline) { + char *buf = cls->buf; + uint i; + + /* find previous newline */ + for (i = cls->num; i > 0; i--) { + if (buf[i - 1] == '\n') { + target = i; + break; + } + } + } + GOTO_LINE_START(target); +} +#define BEGINNING_OF_LINE() cread_start_of_line(cls) +#else +#define BEGINNING_OF_LINE() GOTO_LINE_START(0) +#endif + +#if CONFIG_IS_ENABLED(CMDLINE_EDITOR) +static void cread_erase_to_eol(struct cli_line_state *cls) +{ + struct cli_editor_state *ed = cli_editor(cls); + char *buf = cls->buf; + uint erase_to; + + if (cls->num >= cls->eol_num) + return; + + /* + * In multiline mode, only erase to end of current line (next newline + * or end of buffer) + */ + erase_to = cls->eol_num; + if (ed && ed->multiline) { + char *nl = strchr(&buf[cls->num], '\n'); + + if (nl) + erase_to = nl - buf; + } + ERASE_TO(erase_to); +} +#else static void cread_erase_to_eol(struct cli_line_state *cls) { if (cls->num < cls->eol_num) { @@ -247,15 +320,44 @@ static void cread_erase_to_eol(struct cli_line_state *cls) } while (--cls->eol_num > cls->num); } } +#endif -#define REFRESH_TO_EOL() { \ - if (cls->num < cls->eol_num) { \ - uint wlen = cls->eol_num - cls->num; \ +#define GOTO_LINE_END(target) { \ + if (cls->num < (target)) { \ + uint wlen = (target) - cls->num; \ cls_putnstr(cls, buf + cls->num, wlen); \ - cls->num = cls->eol_num; \ + cls->num = (target); \ } \ } +#if CONFIG_IS_ENABLED(CMDLINE_EDITOR) +/** + * cread_end_of_line() - Move cursor to end of line + * + * In multiline mode, moves to the next newline character. + * Otherwise moves to end of buffer. + * + * @cls: CLI line state + */ +static void cread_end_of_line(struct cli_line_state *cls) +{ + struct cli_editor_state *ed = cli_editor(cls); + char *buf = cls->buf; + uint target = cls->eol_num; + + if (ed && ed->multiline) { + char *nl = strchr(&buf[cls->num], '\n'); + + if (nl) + target = nl - buf; + } + GOTO_LINE_END(target); +} +#define REFRESH_TO_EOL() cread_end_of_line(cls) +#else +#define REFRESH_TO_EOL() GOTO_LINE_END(cls->eol_num) +#endif + static void cread_add_char(struct cli_line_state *cls, char ichar, int insert, uint *num, uint *eol_num, char *buf, uint len) { diff --git a/test/boot/editenv.c b/test/boot/editenv.c index 69e543ea51f..ab3c6648886 100644 --- a/test/boot/editenv.c +++ b/test/boot/editenv.c @@ -179,15 +179,22 @@ static int editenv_test_funcs(struct unit_test_state *uts) ut_assertok(editenv_send(&info, BKEY_DOWN)); ut_asserteq(16611, ut_check_video(uts, "down")); - /* Type a character and press Ctrl-S to save */ + /* Navigate with up arrow and insert '*' */ + ut_assertok(editenv_send(&info, BKEY_UP)); + ut_asserteq(16684, ut_check_video(uts, "up2")); + ut_assertok(editenv_send(&info, '*')); - ut_asserteq(16689, ut_check_video(uts, "insert")); + ut_asserteq(16877, ut_check_video(uts, "insert")); + + /* Use Ctrl-K to kill to end of line (stops at the existing newline) */ + ut_assertok(editenv_send(&info, CTL_CH('k'))); + ut_asserteq(16033, ut_check_video(uts, "kill")); ut_asserteq(1, editenv_send(&info, BKEY_SAVE)); - /* The '*' should be appended to the initial text */ - ut_assert(strstr(expo_editenv_result(&info), "editor.*")); - ut_asserteq(16689, ut_check_video(uts, "save")); + /* The '*' is inserted after "tes", Ctrl-K killed "ted properly." */ + ut_assert(strstr(expo_editenv_result(&info), "tes*\n")); + ut_asserteq(16033, ut_check_video(uts, "save")); expo_editenv_uninit(&info); diff --git a/test/boot/expo.c b/test/boot/expo.c index f598b9cb86c..366183e4a79 100644 --- a/test/boot/expo.c +++ b/test/boot/expo.c @@ -1681,16 +1681,16 @@ static int expo_render_textedit(struct unit_test_state *uts) ut_assertok(expo_render(exp)); ut_asserteq(21211, video_compress_fb(uts, dev, false)); - /* go to start of buffer and delete a character */ + /* go to start of line (multiline Home goes to start of current line) */ ut_assertok(expo_send_key(exp, CTL_CH('a'))); - ut_asserteq(0, ted->tin.cls.num); + ut_asserteq(5, ted->tin.cls.num); ut_asserteq(91, ted->tin.cls.eol_num); ut_assertok(expo_send_key(exp, CTL_CH('d'))); - ut_asserteq(0, ted->tin.cls.num); + ut_asserteq(5, ted->tin.cls.num); ut_asserteq(90, ted->tin.cls.eol_num); ut_assertok(scene_arrange(scn)); ut_assertok(expo_render(exp)); - ut_asserteq(21147, video_compress_fb(uts, dev, false)); + ut_asserteq(21174, video_compress_fb(uts, dev, false)); /* go to end of buffer and backspace */ ut_assertok(expo_send_key(exp, CTL_CH('e'))); @@ -1701,7 +1701,7 @@ static int expo_render_textedit(struct unit_test_state *uts) ut_asserteq(89, ted->tin.cls.eol_num); ut_assertok(scene_arrange(scn)); ut_assertok(expo_render(exp)); - ut_asserteq(21083, video_compress_fb(uts, dev, false)); + ut_asserteq(21079, video_compress_fb(uts, dev, false)); /* set multiline mode and check Enter inserts newline */ ted->obj.flags |= SCENEOF_MULTILINE; @@ -1712,7 +1712,22 @@ static int expo_render_textedit(struct unit_test_state *uts) ut_asserteq('\n', ((char *)abuf_data(&ted->tin.buf))[89]); ut_assertok(scene_arrange(scn)); ut_assertok(expo_render(exp)); - ut_asserteq(21091, video_compress_fb(uts, dev, false)); + ut_asserteq(21109, video_compress_fb(uts, dev, false)); + + /* go back 5 characters (before the newline) and use Ctrl+K */ + ut_assertok(expo_send_key(exp, CTL_CH('b'))); + ut_assertok(expo_send_key(exp, CTL_CH('b'))); + ut_assertok(expo_send_key(exp, CTL_CH('b'))); + ut_assertok(expo_send_key(exp, CTL_CH('b'))); + ut_assertok(expo_send_key(exp, CTL_CH('b'))); + ut_asserteq(85, ted->tin.cls.num); + ut_asserteq(90, ted->tin.cls.eol_num); + + /* Ctrl+K in multiline mode should only delete to the newline */ + ut_assertok(expo_send_key(exp, CTL_CH('k'))); + ut_asserteq(85, ted->tin.cls.num); + ut_asserteq(86, ted->tin.cls.eol_num); + ut_asserteq('\n', ((char *)abuf_data(&ted->tin.buf))[85]); /* clear multiline mode, close the textedit with Enter (BKEY_SELECT) */ ted->obj.flags &= ~SCENEOF_MULTILINE; @@ -1724,12 +1739,12 @@ static int expo_render_textedit(struct unit_test_state *uts) /* check the textedit is closed and text is changed */ ut_asserteq(0, ted->obj.flags & SCENEOF_OPEN); - ut_asserteq_str("his\nis the initial contents of the text " - "editor but it is ely that more will be added latr\n", + ut_asserteq_str("This\ns the initial contents of the text " + "editor but it is ely that more will be added \n", abuf_data(&ted->tin.buf)); ut_assertok(scene_arrange(scn)); ut_assertok(expo_render(exp)); - ut_asserteq(21230, video_compress_fb(uts, dev, false)); + ut_asserteq(21099, video_compress_fb(uts, dev, false)); abuf_uninit(&buf); abuf_uninit(&logo_copy); From patchwork Fri Jan 30 03:58:41 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 1793 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org Authentication-Results: mail.u-boot.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.a=rsa-sha256 header.s=google header.b=TswcVTf6; dkim-atps=neutral Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 0157F697CE for ; Thu, 29 Jan 2026 20:59:51 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 7awEjNCQ7LNW for ; Thu, 29 Jan 2026 20:59:50 -0700 (MST) Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 75B38697E0 for ; Thu, 29 Jan 2026 20:59:50 -0700 (MST) Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 5AE46697E0 for ; Thu, 29 Jan 2026 20:59:48 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id 0EA-u38DSXWm for ; Thu, 29 Jan 2026 20:59:48 -0700 (MST) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=209.85.160.52; helo=mail-oa1-f52.google.com; envelope-from=sjg@chromium.org; receiver=u-boot.org Received: from mail-oa1-f52.google.com (mail-oa1-f52.google.com [209.85.160.52]) by mail.u-boot.org (Postfix) with ESMTPS id C6989697F3 for ; Thu, 29 Jan 2026 20:59:45 -0700 (MST) Received: by mail-oa1-f52.google.com with SMTP id 586e51a60fabf-409440b98b5so957552fac.2 for ; Thu, 29 Jan 2026 19:59:45 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1769745584; x=1770350384; darn=u-boot.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=XoYCvNg90jj7D6wBFqHg+UDBaMylMrnoBcdo3PnYr48=; b=TswcVTf6nT8KbCmzFb9qelINAdh2/CmH/Q3sDMn7G9wtNvcWP8czfBEqog0aFuCW9l 0X7eAL2NxJJzera65gz7xtF5uAvpDDXROBxWHtw6KQvTaJymn2z9uMPp8dC+MBd1LQyb LrM0FngXsrGtE4fv4c57UTuWL3JHHdz20CscU= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769745584; x=1770350384; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=XoYCvNg90jj7D6wBFqHg+UDBaMylMrnoBcdo3PnYr48=; b=dPCYTYvVQT41cHAolZ2Lzj2Fk4b85f9IeV6aMvbQO1seZamSgHnOHz9wNpnbWFVlUU sM+QDoUWR3c61qe/h5OXI8VeBKonKe1xgF6S0p0AjNptHtWIj24hDUvTJK4BTNGrFIMb rC/thlgst5Zk4UEVhC+oDInp6sGoBtS0N9NtsfAYcPnCiQ0kyoFmdk2YrpzlXQg27fbI 6fUvv4Th7C9TLMm+OC3fvP9/7Dju6rsxqHPrUIRWhHg7+1R9ADCq2CpAR0ekpdbFJsqd QHz05KZkfst8WvoEvd68Omt585j6cUyQyFOdLqeK4BgNjLAXwaPNJ6Z7Y/uN8bkBvF1P 3zQg== X-Gm-Message-State: AOJu0YyDefekjsYq3jNwCFsOWuvJPafCF7zy55lzc/TmfL5L2EiSJEOS Fv3cVt6t5FYAGc1pnkwgWPxlKh0V+xiNTBYlQYVJ8QeqdImDwIIVyI3s1CHIdhzTJGkOKxxS+d2 Oqf8snQ== X-Gm-Gg: AZuq6aKIqb96smH/oqiPHKdYjjiyf3V9QeLXiNuVYdStJLNyJINftP34rO43PHHK40m OS/vQFoIrSXkiNNcwCrvz9yEyg9NQB5/4uL8b2Z2ENEU7ebkDVYXuacFJihiQOoXwr+hoErBzMm PlVfzjrHZRIY7glczHxhuaX5ZH7k1wayUE9OKWAd87AY3N757BN2L9Guzychq1TUJ0plQzJphPp BOxkicu2ha5zxXuAlXz/y44eL15P1DXJ53uUwaY64gXu2rMHfXzQuLO20OA5gEfLeqbQPJRvE1B YLxFdFYjRk+hk3tSuZwmvC24nit6mredkdFuRG+bMp+fkl0QF26Tn8SucuS0QLNZ1ZYLTQpje+K MJo0E6UxiuZGs9R4XUWrKAuyNku6+ako4AiKu+du3Y2zsAPqIc98wB7sYtlJZDbAOjsNHy76hNW HgnJv+Rcrx3QCBTNHC X-Received: by 2002:a05:6820:1f12:b0:662:fe9f:2259 with SMTP id 006d021491bc7-6630f36eabfmr836935eaf.50.1769745584462; Thu, 29 Jan 2026 19:59:44 -0800 (PST) Received: from chromium.org ([73.34.74.121]) by smtp.gmail.com with ESMTPSA id 006d021491bc7-662f9a4e491sm4128687eaf.16.2026.01.29.19.59.42 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Jan 2026 19:59:43 -0800 (PST) From: Simon Glass X-Google-Original-From: Simon Glass To: U-Boot Concept Date: Thu, 29 Jan 2026 20:58:41 -0700 Message-ID: <20260130035849.3580212-19-simon.glass@canonical.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260130035849.3580212-1-simon.glass@canonical.com> References: <20260130035849.3580212-1-simon.glass@canonical.com> MIME-Version: 1.0 Message-ID-Hash: QYUTIXS6HR6EMEVB52DUFRSZ63KRRCZL X-Message-ID-Hash: QYUTIXS6HR6EMEVB52DUFRSZ63KRRCZL X-MailFrom: sjg@chromium.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass , "Claude Opus 4 . 5" X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 18/19] cli: Add Ctrl+Y to yank killed text List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Add emacs-style yank (paste) functionality to the command-line editor. When CONFIG_CMDLINE_UNDO is enabled, Ctrl+Y inserts the last killed text at the cursor position. Text is saved to the yank buffer during kill operations: - Ctrl+K (kill to end of line) - Ctrl+W (kill word backwards) - Ctrl+U (kill entire line) - Ctrl+X (kill entire line) The yank buffer is separate from the undo buffer, allowing both features to work independently. Yanking text also saves the current state to the undo buffer, so it can be undone with Ctrl+Z. Enable CMDLINE_UNDO by default in sandbox for testing. Add test coverage for Ctrl+Y yank functionality. Co-developed-by: Claude Opus 4.5 Signed-off-by: Simon Glass --- cmd/Kconfig | 9 +++ common/Makefile | 1 + common/cli_readline.c | 56 ++++++++++++++++- common/cli_undo.c | 142 ++++++++++++++++++++++++++++++++++++++++++ include/cli.h | 96 ++++++++++++++++++++++++++++ test/boot/expo.c | 10 ++- 6 files changed, 311 insertions(+), 3 deletions(-) create mode 100644 common/cli_undo.c diff --git a/cmd/Kconfig b/cmd/Kconfig index 606a34f8869..9d96c12fd86 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -66,6 +66,15 @@ config CMDLINE_EDITOR - Undo/redo support (Ctrl+Z / Ctrl+Shift+Z) - Yank/paste of killed text (Ctrl+Y) +config CMDLINE_UNDO + bool "Support undo in command-line editing" + depends on CMDLINE_EDITOR + default y + help + Enable an undo buffer for command-line editing. When enabled, + pressing Ctrl+Z restores the previous state of the edit buffer. + This uses additional memory to store the undo state. + config CMDLINE_PS_SUPPORT bool "Enable support for changing the command prompt string at run-time" depends on HUSH_PARSER diff --git a/common/Makefile b/common/Makefile index 125f768ef53..a9d7a516b56 100644 --- a/common/Makefile +++ b/common/Makefile @@ -10,6 +10,7 @@ obj-y += main.o obj-y += memtop.o obj-y += exports.o obj-y += cli_getch.o cli_simple.o cli_readline.o +obj-$(CONFIG_CMDLINE_UNDO) += cli_undo.o obj-$(CONFIG_HUSH_OLD_PARSER) += cli_hush.o obj-$(CONFIG_HUSH_MODERN_PARSER) += cli_hush_modern.o obj-$(CONFIG_AUTOBOOT) += autoboot.o diff --git a/common/cli_readline.c b/common/cli_readline.c index 847b49450b5..d554b7241c6 100644 --- a/common/cli_readline.c +++ b/common/cli_readline.c @@ -358,6 +358,8 @@ static void cread_end_of_line(struct cli_line_state *cls) #define REFRESH_TO_EOL() GOTO_LINE_END(cls->eol_num) #endif +/* undo/yank functions are in cli_undo.c when CMDLINE_UNDO is enabled */ + static void cread_add_char(struct cli_line_state *cls, char ichar, int insert, uint *num, uint *eol_num, char *buf, uint len) { @@ -484,9 +486,21 @@ int cread_line_process_ch(struct cli_line_state *cls, char ichar) cls->eol_num--; } break; - case CTL_CH('k'): + case CTL_CH('k'): { + uint erase_to = cls->eol_num; + + ed = cli_editor(cls); + if (ed && ed->multiline) { + char *nl = strchr(&buf[cls->num], '\n'); + + if (nl) + erase_to = nl - buf; + } + cread_save_undo(cls); + cread_save_yank(cls, &buf[cls->num], erase_to - cls->num); cread_erase_to_eol(cls); break; + } case CTL_CH('e'): REFRESH_TO_EOL(); break; @@ -505,6 +519,8 @@ int cread_line_process_ch(struct cli_line_state *cls, char ichar) /* now delete chars from base to cls->num */ wlen = cls->num - base; + cread_save_undo(cls); + cread_save_yank(cls, &buf[base], wlen); cls->eol_num -= wlen; memmove(&buf[base], &buf[cls->num], cls->eol_num - base + 1); @@ -517,7 +533,24 @@ int cread_line_process_ch(struct cli_line_state *cls, char ichar) } break; case CTL_CH('x'): + if (CONFIG_IS_ENABLED(CMDLINE_UNDO)) { + cread_save_undo(cls); + cread_save_yank(cls, buf, cls->eol_num); + BEGINNING_OF_LINE(); + cread_erase_to_eol(cls); + } + break; + case CTL_CH('y'): +#if CONFIG_IS_ENABLED(CMDLINE_UNDO) + cread_yank(cls); +#endif + break; + case CTL_CH('z'): + cread_restore_undo(cls); + break; case CTL_CH('u'): + cread_save_undo(cls); + cread_save_yank(cls, buf, cls->eol_num); BEGINNING_OF_LINE(); cread_erase_to_eol(cls); break; @@ -630,6 +663,27 @@ void cli_cread_init(struct cli_line_state *cls, char *buf, uint buf_size) cls->len = buf_size; } +void cli_cread_init_undo(struct cli_line_state *cls, char *buf, uint buf_size) +{ + cli_cread_init(cls, buf, buf_size); + if (CONFIG_IS_ENABLED(CMDLINE_UNDO)) { + struct cli_editor_state *ed = cli_editor(cls); + + abuf_init_size(&ed->undo.buf, buf_size); + abuf_init_size(&ed->yank, buf_size); + } +} + +void cli_cread_uninit(struct cli_line_state *cls) +{ + if (CONFIG_IS_ENABLED(CMDLINE_UNDO)) { + struct cli_editor_state *ed = cli_editor(cls); + + abuf_uninit(&ed->undo.buf); + abuf_uninit(&ed->yank); + } +} + void cli_cread_add_initial(struct cli_line_state *cls) { int init_len = strlen(cls->buf); diff --git a/common/cli_undo.c b/common/cli_undo.c new file mode 100644 index 00000000000..4aa9a719ebf --- /dev/null +++ b/common/cli_undo.c @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * CLI undo/yank support + * + * Copyright 2025 Google LLC + * Written by Simon Glass + */ + +#include +#include +#include +#include +#include + +DECLARE_GLOBAL_DATA_PTR; + +#define CTL_BACKSPACE ('\b') + +/** + * cls_putch() - Output a character, using callback if available + * + * @cls: CLI line state + * @ch: Character to output + */ +static void cls_putch(struct cli_line_state *cls, int ch) +{ + struct cli_editor_state *ed = cli_editor(cls); + + if (ed && ed->putch) + ed->putch(cls, ch); + else + putc(ch); +} + +static void cls_putnstr(struct cli_line_state *cls, const char *str, size_t n) +{ + while (n-- > 0) + cls_putch(cls, *str++); +} + +/** + * cls_putchars() - Output a character multiple times + * + * @cls: CLI line state + * @count: Number of times to output the character + * @ch: Character to output + */ +static void cls_putchars(struct cli_line_state *cls, int count, int ch) +{ + int i; + + for (i = 0; i < count; i++) + cls_putch(cls, ch); +} + +#define getcmd_cbeep(cls) cls_putch(cls, '\a') + +void cread_save_undo(struct cli_line_state *cls) +{ + struct cli_editor_state *ed = cli_editor(cls); + struct cli_undo_state *undo = &ed->undo; + + if (abuf_size(&undo->buf)) { + memcpy(abuf_data(&undo->buf), cls->buf, cls->len); + undo->num = cls->num; + undo->eol_num = cls->eol_num; + } +} + +void cread_restore_undo(struct cli_line_state *cls) +{ + struct cli_editor_state *ed = cli_editor(cls); + struct cli_undo_state *undo = &ed->undo; + + if (!abuf_size(&undo->buf)) + return; + + /* go to start of line */ + while (cls->num) { + cls_putch(cls, CTL_BACKSPACE); + cls->num--; + } + + /* erase current content on screen */ + cls_putchars(cls, cls->eol_num, ' '); + cls_putchars(cls, cls->eol_num, CTL_BACKSPACE); + + /* restore from undo buffer */ + memcpy(cls->buf, abuf_data(&undo->buf), cls->len); + cls->eol_num = undo->eol_num; + + /* display restored content */ + cls_putnstr(cls, cls->buf, cls->eol_num); + + /* position cursor */ + cls_putchars(cls, cls->eol_num - undo->num, CTL_BACKSPACE); + cls->num = undo->num; +} + +void cread_save_yank(struct cli_line_state *cls, const char *text, uint len) +{ + struct cli_editor_state *ed = cli_editor(cls); + + if (abuf_size(&ed->yank) && len > 0 && len < cls->len) { + memcpy(abuf_data(&ed->yank), text, len); + ed->yank_len = len; + } +} + +void cread_yank(struct cli_line_state *cls) +{ + struct cli_editor_state *ed = cli_editor(cls); + char *buf = cls->buf; + uint i; + + if (!abuf_size(&ed->yank) || !ed->yank_len) + return; + + /* check if there's room */ + if (cls->eol_num + ed->yank_len > cls->len - 1) { + getcmd_cbeep(cls); + return; + } + + cread_save_undo(cls); + + /* make room for yanked text */ + memmove(&buf[cls->num + ed->yank_len], &buf[cls->num], + cls->eol_num - cls->num + 1); + + /* insert yanked text */ + memcpy(&buf[cls->num], abuf_data(&ed->yank), ed->yank_len); + cls->eol_num += ed->yank_len; + + /* display from cursor to end */ + cls_putnstr(cls, &buf[cls->num], cls->eol_num - cls->num); + + /* move cursor to end of inserted text */ + cls->num += ed->yank_len; + for (i = cls->num; i < cls->eol_num; i++) + cls_putch(cls, CTL_BACKSPACE); +} diff --git a/include/cli.h b/include/cli.h index b6a8a6be1dd..3040342de8e 100644 --- a/include/cli.h +++ b/include/cli.h @@ -7,6 +7,7 @@ #ifndef __CLI_H #define __CLI_H +#include #include #include @@ -27,6 +28,19 @@ struct cli_ch_state { struct cli_line_state; +/** + * struct cli_undo_state - state for undo buffer + * + * @buf: Buffer for saved state + * @num: Saved cursor position + * @eol_num: Saved end-of-line position + */ +struct cli_undo_state { + struct abuf buf; + uint num; + uint eol_num; +}; + /** * struct cli_editor_state - state for enhanced editing features * @@ -36,6 +50,9 @@ struct cli_line_state; * @line_nav: Handle multi-line navigation (Ctrl-P/N) * @multiline: true if input may contain multiple lines (enables * Ctrl-P/N for line navigation instead of history) + * @undo: Undo ring buffer state + * @yank: Buffer for killed text (for Ctrl+Y yank) + * @yank_len: Length of killed text in yank buffer */ struct cli_editor_state { /** @@ -60,6 +77,15 @@ struct cli_editor_state { * Ctrl-P/N for line navigation instead of history) */ bool multiline; + + /** @undo: Undo state (if CONFIG_CMDLINE_UNDO) */ + struct cli_undo_state undo; + + /** @yank: Buffer for killed text (for Ctrl+Y yank) */ + struct abuf yank; + + /** @yank_len: Length of killed text in yank buffer */ + uint yank_len; }; /** @@ -336,6 +362,24 @@ int cread_line_process_ch(struct cli_line_state *cls, char ichar); */ void cli_cread_init(struct cli_line_state *cls, char *buf, uint buf_size); +/** + * cli_cread_init_undo() - Set up a new cread struct with undo support + * + * Like cli_cread_init() but also sets up the undo buffer. + * + * @cls: CLI line state + * @buf: Text buffer containing the initial text + * @buf_size: Buffer size, including nul terminator + */ +void cli_cread_init_undo(struct cli_line_state *cls, char *buf, uint buf_size); + +/** + * cli_cread_uninit() - Free resources allocated by cli_cread_init_undo() + * + * @cls: CLI line state + */ +void cli_cread_uninit(struct cli_line_state *cls); + /** * cli_cread_add_initial() - Output initial buffer contents * @@ -349,4 +393,56 @@ void cli_cread_add_initial(struct cli_line_state *cls); /** cread_print_hist_list() - Print the command-line history list */ void cread_print_hist_list(void); +/* + * Undo/yank functions - implementations in cli_undo.c when CMDLINE_UNDO is enabled + */ +#if CONFIG_IS_ENABLED(CMDLINE_UNDO) +/** + * cread_save_undo() - Save current state for undo + * + * @cls: CLI line state + */ +void cread_save_undo(struct cli_line_state *cls); + +/** + * cread_restore_undo() - Restore previous state from undo buffer + * + * @cls: CLI line state + */ +void cread_restore_undo(struct cli_line_state *cls); + +/** + * cread_save_yank() - Save killed text to yank buffer + * + * @cls: CLI line state + * @text: Text to save + * @len: Length of text + */ +void cread_save_yank(struct cli_line_state *cls, const char *text, uint len); + +/** + * cread_yank() - Insert yanked text at cursor position + * + * @cls: CLI line state + */ +void cread_yank(struct cli_line_state *cls); +#else +static inline void cread_save_undo(struct cli_line_state *cls) +{ +} + +static inline void cread_restore_undo(struct cli_line_state *cls) +{ +} + +static inline void cread_save_yank(struct cli_line_state *cls, const char *text, + uint len) +{ +} + +static inline void cread_yank(struct cli_line_state *cls) +{ +} +#endif + #endif diff --git a/test/boot/expo.c b/test/boot/expo.c index 366183e4a79..5445fed19c1 100644 --- a/test/boot/expo.c +++ b/test/boot/expo.c @@ -1729,6 +1729,12 @@ static int expo_render_textedit(struct unit_test_state *uts) ut_asserteq(86, ted->tin.cls.eol_num); ut_asserteq('\n', ((char *)abuf_data(&ted->tin.buf))[85]); + /* Ctrl+Y yanks back the killed text "latr" */ + ut_assertok(expo_send_key(exp, CTL_CH('y'))); + ut_asserteq(89, ted->tin.cls.num); + ut_asserteq(90, ted->tin.cls.eol_num); + ut_asserteq('\n', ((char *)abuf_data(&ted->tin.buf))[89]); + /* clear multiline mode, close the textedit with Enter (BKEY_SELECT) */ ted->obj.flags &= ~SCENEOF_MULTILINE; ut_assertok(expo_send_key(exp, BKEY_SELECT)); @@ -1740,11 +1746,11 @@ static int expo_render_textedit(struct unit_test_state *uts) /* check the textedit is closed and text is changed */ ut_asserteq(0, ted->obj.flags & SCENEOF_OPEN); ut_asserteq_str("This\ns the initial contents of the text " - "editor but it is ely that more will be added \n", + "editor but it is ely that more will be added latr\n", abuf_data(&ted->tin.buf)); ut_assertok(scene_arrange(scn)); ut_assertok(expo_render(exp)); - ut_asserteq(21099, video_compress_fb(uts, dev, false)); + ut_asserteq(21251, video_compress_fb(uts, dev, false)); abuf_uninit(&buf); abuf_uninit(&logo_copy); From patchwork Fri Jan 30 03:58:42 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Simon Glass X-Patchwork-Id: 1794 Return-Path: X-Original-To: u-boot-concept@u-boot.org Delivered-To: u-boot-concept@u-boot.org Authentication-Results: mail.u-boot.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=chromium.org header.i=@chromium.org header.a=rsa-sha256 header.s=google header.b=jhN1+qho; dkim-atps=neutral Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 0A7F8697DD for ; Thu, 29 Jan 2026 20:59:56 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id XZ1sHlI7bdsR for ; Thu, 29 Jan 2026 20:59:55 -0700 (MST) Received: from mail.u-boot.org (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id ED89F697CE for ; Thu, 29 Jan 2026 20:59:55 -0700 (MST) Received: from localhost (localhost [127.0.0.1]) by mail.u-boot.org (Postfix) with ESMTP id 65C0D69738 for ; Thu, 29 Jan 2026 20:59:53 -0700 (MST) X-Virus-Scanned: Debian amavis at Received: from mail.u-boot.org ([127.0.0.1]) by localhost (mail.u-boot.org [127.0.0.1]) (amavis, port 10024) with ESMTP id aKWPBTrRgtDo for ; Thu, 29 Jan 2026 20:59:53 -0700 (MST) Received-SPF: Pass (mailfrom) identity=mailfrom; client-ip=209.85.167.178; helo=mail-oi1-f178.google.com; envelope-from=sjg@chromium.org; receiver=u-boot.org Received: from mail-oi1-f178.google.com (mail-oi1-f178.google.com [209.85.167.178]) by mail.u-boot.org (Postfix) with ESMTPS id 6E624697F4 for ; Thu, 29 Jan 2026 20:59:49 -0700 (MST) Received: by mail-oi1-f178.google.com with SMTP id 5614622812f47-45c889aba0dso1532770b6e.0 for ; Thu, 29 Jan 2026 19:59:49 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chromium.org; s=google; t=1769745588; x=1770350388; darn=u-boot.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=MmzAgpy6PAj47SMP7j8yFG4cbMHiNJliKbjOLfGGjrQ=; b=jhN1+qhoEZN6PuzNBfGYAvgnyKzdWZ//d76XaCpWQAB1gH/RUoUPfwri6Ot2h+0VEG 3x9FwHVlLMd7KwPY2OMoe32EuT7S/EidtyUfSF+5gPce94BgVkPOJRLMYpwOSmH/dYg9 3AGIrJG4TzX8SVYvC/PN4UrP44b9hW2IIhcdw= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1769745588; x=1770350388; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=MmzAgpy6PAj47SMP7j8yFG4cbMHiNJliKbjOLfGGjrQ=; b=S6jwhpTeR9tPVMObncWmzYtd3GLNHELUvs18jmcnDIL7j9J3r98CsemzqA3zN/670k +Umqa7G6LjhGl2tmd8wxhLHx0txYU8+Thlj9+VyLQvODX4YVZCgAqf57/uFVL9v1kjCc MMh0mxTdyPDLfizdw6MIELPhpYRcK4rQTEia4ZkbVz0oFqvU9yGTVurYXlcWy9hTLmlT yif3bDhfKwMSE2hsQceKHjo2iuNJvCbDVUFAo+cnRbKY+dCTGaJVa4v1Wwh5aPVazhqd t5oU860H5PNQaiLbv7xoub3bOpvXewjoYbdkZem/OZ3JJKf6iq3YnxCamymGn8QlPSQU sKGw== X-Gm-Message-State: AOJu0YxMMS8KH+v+uFljvhu/lK9Rui59/6RpTWp9kK80848jrMCbLiZS FQwPSQ4VWE3rNa+zfZjgSTxcU8hdVL4M8Q/+6yZitpcNxE3nM9mjrNIxnkZP5CEdcxQ1MB2Opbn ZR2I8Hg== X-Gm-Gg: AZuq6aKcussx/elmHqYKFzgCmU/qkFFwCWPYkXhqPmS6On3KfAHlgmiow4QBA3mQetz t/cI5E5hbL7OUVxK/yWgYxCfwFc75Sc1BIvJqSwMmIwIwo7yP/5czRp/KS+Yc/vbTMfVA8VKUjY EwdjHQyYxPUH06jQSZDJAQyxtqxeFzvO7i9KueUXPv7EpiNCCekH8evLko8jWrsPkVgMWQQNYgM d61FmYj9CYx2h+RPEQNm+pmoiXkGzMFK8EgshbApyCWn7jgPPi+zfIcHKEf5GV+bN48wLBotySo SnlEq3jcZidsABqSUIxbvKt/68vENvbPHo+2BGAQf42XvA2Bqm1Cn8ArcHgbQByFwYA0rhP8D5M eDJKKlKaKD0FaD63dqFZILuW1PO6+IPyJylNuenP0CzRZjMZ5w3MgYfuWAJ1BUzyPjpObbSsWNF cgtMJt8N6lgOZxpDtx X-Received: by 2002:a05:6820:2109:b0:659:9a49:8ef4 with SMTP id 006d021491bc7-663103f83f8mr631437eaf.40.1769745587782; Thu, 29 Jan 2026 19:59:47 -0800 (PST) Received: from chromium.org ([73.34.74.121]) by smtp.gmail.com with ESMTPSA id 006d021491bc7-662f9a4e491sm4128687eaf.16.2026.01.29.19.59.44 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 29 Jan 2026 19:59:46 -0800 (PST) From: Simon Glass X-Google-Original-From: Simon Glass To: U-Boot Concept Date: Thu, 29 Jan 2026 20:58:42 -0700 Message-ID: <20260130035849.3580212-20-simon.glass@canonical.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20260130035849.3580212-1-simon.glass@canonical.com> References: <20260130035849.3580212-1-simon.glass@canonical.com> MIME-Version: 1.0 Message-ID-Hash: PTBOPWDKNIR6TZVVEXAQSW26PD4UJRKS X-Message-ID-Hash: PTBOPWDKNIR6TZVVEXAQSW26PD4UJRKS X-MailFrom: sjg@chromium.org X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Simon Glass , "Claude Opus 4 . 5" X-Mailman-Version: 3.3.10 Precedence: list Subject: [Concept] [PATCH 19/19] cli: Add multi-level undo/redo support List-Id: Discussion and patches related to U-Boot Concept Archived-At: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Convert the single-level undo buffer to a ring buffer supporting multiple undo/redo levels. This allows users to undo multiple editing operations and redo them if needed. Key changes: - Replace single undo state with ring buffer (struct cli_undo_state) - Add redo ring buffer for undone states - Add Ctrl+Shift+Z (via Ctrl+G) for redo operation - Track undo/redo counts separately from buffer allocation - Clear redo history on new edits (standard editor behaviour) The number of undo levels is configurable via CONFIG_CMDLINE_UNDO_COUNT (default 64). Each level stores a complete copy of the edit buffer plus cursor position. Also fix scene_txtin_open() which was allocating the yank buffer twice - once in cli_cread_init_undo() and again directly. Remove the duplicate. Co-developed-by: Claude Opus 4.5 Signed-off-by: Simon Glass --- boot/scene_txtin.c | 36 +++++++++++- cmd/Kconfig | 15 +++++ common/cli_getch.c | 19 +++++- common/cli_readline.c | 40 ++++++++++--- common/cli_undo.c | 133 ++++++++++++++++++++++++++++++++++++++---- doc/develop/expo.rst | 14 +++++ doc/usage/cmdline.rst | 68 +++++++++++++++++++++ include/cli.h | 86 ++++++++++++++++++++++++--- test/boot/editenv.c | 62 ++++++++++++++++++-- test/boot/expo.c | 12 +++- 10 files changed, 446 insertions(+), 39 deletions(-) diff --git a/boot/scene_txtin.c b/boot/scene_txtin.c index ab4fd5056a0..101616acc07 100644 --- a/boot/scene_txtin.c +++ b/boot/scene_txtin.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -18,6 +19,12 @@ #include #include "scene_internal.h" +#ifdef CONFIG_CMDLINE_UNDO_COUNT +#define UNDO_COUNT CONFIG_CMDLINE_UNDO_COUNT +#else +#define UNDO_COUNT 64 +#endif + int scene_txtin_init(struct scene_txtin *tin, uint size, uint line_chars) { char *buf; @@ -161,6 +168,10 @@ static void scene_txtin_putch(struct cli_line_state *cls, int ch) void scene_txtin_close(struct scene *scn, struct scene_txtin *tin) { + struct cli_line_state *cls = &tin->cls; + + cli_cread_uninit(cls); + /* cursor is not needed now */ vidconsole_readline_end(scn->expo->cons, tin->ctx); } @@ -265,7 +276,7 @@ int scene_txtin_open(struct scene *scn, struct scene_obj *obj, struct udevice *cons = scn->expo->cons; struct scene_obj_txt *txt; void *ctx; - int ret; + int ret, i; ctx = tin->ctx; if (!ctx) { @@ -286,10 +297,31 @@ int scene_txtin_open(struct scene *scn, struct scene_obj *obj, vidconsole_set_cursor_pos(cons, ctx, txt->obj.bbox.x0, txt->obj.bbox.y0); vidconsole_entry_start(cons, ctx); - cli_cread_init(cls, abuf_data(&tin->buf), abuf_size(&tin->buf)); + cli_cread_init_undo(cls, abuf_data(&tin->buf), abuf_size(&tin->buf)); cls->insert = true; ed->putch = scene_txtin_putch; cls->priv = scn; + + /* Initialise undo ring buffer */ + alist_init_struct(&ed->undo.pos, struct cli_undo_pos); + for (i = 0; i < UNDO_COUNT; i++) { + struct cli_undo_pos *pos; + + pos = alist_ensure(&ed->undo.pos, i, struct cli_undo_pos); + abuf_init_size(&pos->buf, abuf_size(&tin->buf)); + } + + /* Initialise redo ring buffer */ + alist_init_struct(&ed->redo.pos, struct cli_undo_pos); + for (i = 0; i < UNDO_COUNT; i++) { + struct cli_undo_pos *pos; + + pos = alist_ensure(&ed->redo.pos, i, struct cli_undo_pos); + abuf_init_size(&pos->buf, abuf_size(&tin->buf)); + } + + /* yank buffer is initialised by cli_cread_init_undo() above */ + if (obj->type == SCENEOBJT_TEXTEDIT) { ed->multiline = true; ed->line_nav = scene_txtin_line_nav; diff --git a/cmd/Kconfig b/cmd/Kconfig index 9d96c12fd86..03e8014786b 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -66,6 +66,8 @@ config CMDLINE_EDITOR - Undo/redo support (Ctrl+Z / Ctrl+Shift+Z) - Yank/paste of killed text (Ctrl+Y) + This uses additional memory to store the undo, redo, and yank buffers. + config CMDLINE_UNDO bool "Support undo in command-line editing" depends on CMDLINE_EDITOR @@ -75,6 +77,19 @@ config CMDLINE_UNDO pressing Ctrl+Z restores the previous state of the edit buffer. This uses additional memory to store the undo state. +config CMDLINE_UNDO_COUNT + int "Number of undo levels" + depends on CMDLINE_UNDO + default 64 + range 1 64 + help + Number of undo/redo levels to support. Each level requires memory + to store a copy of the edit buffer. With multiple levels, + pressing Ctrl+Z repeatedly undoes progressively older changes, and + Ctrl+Shift+Z redoes them. Set to 1 for single-level undo/redo, or + higher for multi-level. Note that any new edit clears the redo + history. + config CMDLINE_PS_SUPPORT bool "Enable support for changing the command prompt string at run-time" depends on HUSH_PARSER diff --git a/common/cli_getch.c b/common/cli_getch.c index 8df1997911a..810128a9fca 100644 --- a/common/cli_getch.c +++ b/common/cli_getch.c @@ -134,14 +134,20 @@ static int cli_ch_esc(struct cli_ch_state *cch, int ichar, cch->esc_save[3] == ';') act = ESC_SAVE; break; + case '6': + /* Ctrl+Shift+key: ESC [ 1 ; 6 */ + if (CONFIG_IS_ENABLED(CMDLINE_EDITOR) && + cch->esc_save[3] == ';') + act = ESC_SAVE; + break; } break; case 5: if (ichar == '~') { /* bracketed paste */ ichar = 0; act = ESC_CONVERTED; - } else if (CONFIG_IS_ENABLED(CMDLINE_EDITOR) && - cch->esc_save[4] == '5') { + } + if (CONFIG_IS_ENABLED(CMDLINE_EDITOR) && cch->esc_save[4] == '5') { /* Ctrl+arrow: ESC [ 1 ; 5 D/C */ switch (ichar) { case 'D': /* Ctrl+<- key */ @@ -154,6 +160,15 @@ static int cli_ch_esc(struct cli_ch_state *cch, int ichar, break; /* pass to forward-word handler */ } } + if (CONFIG_IS_ENABLED(CMDLINE_EDITOR) && cch->esc_save[4] == '6') { + /* Ctrl+Shift+key: ESC [ 1 ; 6 x */ + switch (ichar) { + case 'z': /* Ctrl+Shift+Z: redo */ + ichar = CTL_CH('g'); + act = ESC_CONVERTED; + break; + } + } } *actp = act; diff --git a/common/cli_readline.c b/common/cli_readline.c index d554b7241c6..fac5080cc07 100644 --- a/common/cli_readline.c +++ b/common/cli_readline.c @@ -113,6 +113,14 @@ static char hist_data[HIST_MAX][HIST_SIZE + 1]; #endif static char *hist_list[HIST_MAX]; +#if CONFIG_IS_ENABLED(CMDLINE_EDITOR) +#ifdef CONFIG_CMDLINE_UNDO_COUNT +#define UNDO_COUNT CONFIG_CMDLINE_UNDO_COUNT +#else +#define UNDO_COUNT 64 +#endif +#endif + #define add_idx_minus_one() ((hist_add_idx == 0) ? hist_max : hist_add_idx-1) /** @@ -358,7 +366,7 @@ static void cread_end_of_line(struct cli_line_state *cls) #define REFRESH_TO_EOL() GOTO_LINE_END(cls->eol_num) #endif -/* undo/yank functions are in cli_undo.c when CMDLINE_UNDO is enabled */ +/* undo/redo/yank functions are in cli_undo.c when CMDLINE_EDITOR is enabled */ static void cread_add_char(struct cli_line_state *cls, char ichar, int insert, uint *num, uint *eol_num, char *buf, uint len) @@ -374,6 +382,9 @@ static void cread_add_char(struct cli_line_state *cls, char ichar, int insert, (*eol_num)++; } + /* new edit invalidates redo history */ + cread_clear_redo(cls); + if (insert) { wlen = *eol_num - *num; if (wlen > 1) @@ -472,6 +483,7 @@ int cread_line_process_ch(struct cli_line_state *cls, char ichar) if (cls->num < cls->eol_num) { uint wlen; + cread_save_undo(cls); wlen = cls->eol_num - cls->num - 1; if (wlen) { memmove(&buf[cls->num], &buf[cls->num + 1], @@ -511,6 +523,7 @@ int cread_line_process_ch(struct cli_line_state *cls, char ichar) if (cls->num) { uint base, wlen; + cread_save_undo(cls); for (base = cls->num - 1; base >= 0 && buf[base] == ' ';) base--; @@ -519,7 +532,6 @@ int cread_line_process_ch(struct cli_line_state *cls, char ichar) /* now delete chars from base to cls->num */ wlen = cls->num - base; - cread_save_undo(cls); cread_save_yank(cls, &buf[base], wlen); cls->eol_num -= wlen; memmove(&buf[base], &buf[cls->num], @@ -541,13 +553,18 @@ int cread_line_process_ch(struct cli_line_state *cls, char ichar) } break; case CTL_CH('y'): -#if CONFIG_IS_ENABLED(CMDLINE_UNDO) +#if CONFIG_IS_ENABLED(CMDLINE_EDITOR) cread_yank(cls); #endif break; case CTL_CH('z'): cread_restore_undo(cls); break; + case CTL_CH('g'): +#if CONFIG_IS_ENABLED(CMDLINE_EDITOR) + cread_redo(cls); +#endif + break; case CTL_CH('u'): cread_save_undo(cls); cread_save_yank(cls, buf, cls->eol_num); @@ -560,6 +577,7 @@ int cread_line_process_ch(struct cli_line_state *cls, char ichar) if (cls->num) { uint wlen; + cread_save_undo(cls); wlen = cls->eol_num - cls->num; cls->num--; memmove(&buf[cls->num], &buf[cls->num + 1], wlen); @@ -605,6 +623,8 @@ int cread_line_process_ch(struct cli_line_state *cls, char ichar) break; } + cread_save_undo(cls); + /* nuke the current line */ /* first, go home */ BEGINNING_OF_LINE(); @@ -632,6 +652,7 @@ int cread_line_process_ch(struct cli_line_state *cls, char ichar) buf[cls->num] = '\0'; col = strlen(cls->prompt) + cls->eol_num; num2 = cls->num; + cread_save_undo(cls); if (cmd_auto_complete(cls->prompt, buf, &num2, &col)) { col = num2 - cls->num; cls->num += col; @@ -669,17 +690,22 @@ void cli_cread_init_undo(struct cli_line_state *cls, char *buf, uint buf_size) if (CONFIG_IS_ENABLED(CMDLINE_UNDO)) { struct cli_editor_state *ed = cli_editor(cls); - abuf_init_size(&ed->undo.buf, buf_size); abuf_init_size(&ed->yank, buf_size); } } void cli_cread_uninit(struct cli_line_state *cls) { - if (CONFIG_IS_ENABLED(CMDLINE_UNDO)) { + if (CONFIG_IS_ENABLED(CMDLINE_EDITOR)) { struct cli_editor_state *ed = cli_editor(cls); - - abuf_uninit(&ed->undo.buf); + struct cli_undo_pos *pos; + + alist_for_each(pos, &ed->undo.pos) + abuf_uninit(&pos->buf); + alist_uninit(&ed->undo.pos); + alist_for_each(pos, &ed->redo.pos) + abuf_uninit(&pos->buf); + alist_uninit(&ed->redo.pos); abuf_uninit(&ed->yank); } } diff --git a/common/cli_undo.c b/common/cli_undo.c index 4aa9a719ebf..43858d4636d 100644 --- a/common/cli_undo.c +++ b/common/cli_undo.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0+ /* - * CLI undo/yank support + * CLI undo/redo/yank support * * Copyright 2025 Google LLC * Written by Simon Glass @@ -55,26 +55,134 @@ static void cls_putchars(struct cli_line_state *cls, int count, int ch) #define getcmd_cbeep(cls) cls_putch(cls, '\a') +void cread_save_redo(struct cli_line_state *cls) +{ + struct cli_editor_state *ed = cli_editor(cls); + struct cli_undo_state *redo = &ed->redo; + struct cli_undo_pos *pos; + uint idx; + + if (!redo->pos.alloc) + return; + + /* save at current head position */ + idx = redo->head; + pos = alist_getw(&redo->pos, idx, struct cli_undo_pos); + memcpy(abuf_data(&pos->buf), cls->buf, cls->len); + pos->num = cls->num; + pos->eol_num = cls->eol_num; + + /* advance head (ring buffer) */ + redo->head = (redo->head + 1) % redo->pos.alloc; + + /* track how many redo levels are available */ + if (redo->count < redo->pos.alloc) + redo->count++; +} + +void cread_clear_redo(struct cli_line_state *cls) +{ + struct cli_editor_state *ed = cli_editor(cls); + + ed->redo.count = 0; + ed->redo.head = 0; +} + void cread_save_undo(struct cli_line_state *cls) { struct cli_editor_state *ed = cli_editor(cls); struct cli_undo_state *undo = &ed->undo; + struct cli_undo_pos *pos; + uint idx; - if (abuf_size(&undo->buf)) { - memcpy(abuf_data(&undo->buf), cls->buf, cls->len); - undo->num = cls->num; - undo->eol_num = cls->eol_num; - } + if (!undo->pos.alloc) + return; + + /* save at current head position */ + idx = undo->head; + pos = alist_getw(&undo->pos, idx, struct cli_undo_pos); + memcpy(abuf_data(&pos->buf), cls->buf, cls->len); + pos->num = cls->num; + pos->eol_num = cls->eol_num; + + /* advance head (ring buffer) */ + undo->head = (undo->head + 1) % undo->pos.alloc; + + /* track how many undo levels are available */ + if (undo->count < undo->pos.alloc) + undo->count++; + + /* new edit invalidates redo history */ + cread_clear_redo(cls); } void cread_restore_undo(struct cli_line_state *cls) +{ + struct cli_undo_state *undo = &cli_editor(cls)->undo; + const struct cli_undo_pos *pos; + uint idx; + + if (!undo->pos.alloc || !undo->count) + return; + + /* save current state to redo buffer before restoring */ + cread_save_redo(cls); + + /* move back to previous undo state */ + undo->head = undo->head ? undo->head - 1 : undo->pos.alloc - 1; + undo->count--; + idx = undo->head; + + /* go to start of line */ + while (cls->num) { + cls_putch(cls, CTL_BACKSPACE); + cls->num--; + } + + /* erase current content on screen */ + cls_putchars(cls, cls->eol_num, ' '); + cls_putchars(cls, cls->eol_num, CTL_BACKSPACE); + + /* restore from undo buffer */ + pos = alist_get(&undo->pos, idx, struct cli_undo_pos); + memcpy(cls->buf, abuf_data(&pos->buf), cls->len); + cls->eol_num = pos->eol_num; + + /* display restored content */ + cls_putnstr(cls, cls->buf, cls->eol_num); + + /* position cursor */ + cls_putchars(cls, cls->eol_num - pos->num, CTL_BACKSPACE); + cls->num = pos->num; +} + +void cread_redo(struct cli_line_state *cls) { struct cli_editor_state *ed = cli_editor(cls); struct cli_undo_state *undo = &ed->undo; + struct cli_undo_state *redo = &ed->redo; + struct cli_undo_pos *pos; + const struct cli_undo_pos *rpos; + uint idx; - if (!abuf_size(&undo->buf)) + if (!redo->pos.alloc || !redo->count) return; + /* save current state to undo buffer */ + idx = undo->head; + pos = alist_getw(&undo->pos, idx, struct cli_undo_pos); + memcpy(abuf_data(&pos->buf), cls->buf, cls->len); + pos->num = cls->num; + pos->eol_num = cls->eol_num; + undo->head = (undo->head + 1) % undo->pos.alloc; + if (undo->count < undo->pos.alloc) + undo->count++; + + /* move back to previous redo state */ + redo->head = redo->head ? redo->head - 1 : redo->pos.alloc - 1; + redo->count--; + idx = redo->head; + /* go to start of line */ while (cls->num) { cls_putch(cls, CTL_BACKSPACE); @@ -85,16 +193,17 @@ void cread_restore_undo(struct cli_line_state *cls) cls_putchars(cls, cls->eol_num, ' '); cls_putchars(cls, cls->eol_num, CTL_BACKSPACE); - /* restore from undo buffer */ - memcpy(cls->buf, abuf_data(&undo->buf), cls->len); - cls->eol_num = undo->eol_num; + /* restore from redo buffer */ + rpos = alist_get(&redo->pos, idx, struct cli_undo_pos); + memcpy(cls->buf, abuf_data(&rpos->buf), cls->len); + cls->eol_num = rpos->eol_num; /* display restored content */ cls_putnstr(cls, cls->buf, cls->eol_num); /* position cursor */ - cls_putchars(cls, cls->eol_num - undo->num, CTL_BACKSPACE); - cls->num = undo->num; + cls_putchars(cls, cls->eol_num - rpos->num, CTL_BACKSPACE); + cls->num = rpos->num; } void cread_save_yank(struct cli_line_state *cls, const char *text, uint len) diff --git a/doc/develop/expo.rst b/doc/develop/expo.rst index 71e227c532d..046b464d9f2 100644 --- a/doc/develop/expo.rst +++ b/doc/develop/expo.rst @@ -366,6 +366,9 @@ type "textline" A line of text which can be edited + "textedit" + A multi-line text editor + "box" A rectangle with a given line width (not filled) @@ -446,6 +449,17 @@ max-chars: Specifies the maximum number of characters permitted to be in the textline. The user will be prevented from adding more. +Textedit nodes have the same properties as textline nodes, with the following +differences: + +- The editor supports multiple lines of text +- Pressing Enter inserts a newline instead of closing the editor +- Home/End move to start/end of the current line +- Ctrl+K kills to end of the current line (not entire buffer) +- Up/Down (Ctrl+P/N) navigate between lines + +See :doc:`../usage/cmdline` for a full list of editing keys. + Box nodes have the following additional properties: width diff --git a/doc/usage/cmdline.rst b/doc/usage/cmdline.rst index 58240c5279c..e1525b12884 100644 --- a/doc/usage/cmdline.rst +++ b/doc/usage/cmdline.rst @@ -91,3 +91,71 @@ convenient:: => i2c speed 0x30000 Setting bus speed to 196608 Hz + +Command-line editing +-------------------- + +U-Boot supports command-line editing when `CONFIG_CMDLINE_EDITING` is enabled. +This provides an Emacs-like interface for editing commands before they are +executed. The following key bindings are available: + +Cursor movement +~~~~~~~~~~~~~~~ + +- **Left arrow** or **Ctrl+B**: Move cursor left one character +- **Right arrow** or **Ctrl+F**: Move cursor right one character +- **Ctrl+Left** or **Alt+B**: Move cursor left one word +- **Ctrl+Right** or **Alt+F**: Move cursor right one word +- **Home** or **Ctrl+A**: Move to beginning of line +- **End** or **Ctrl+E**: Move to end of line + +Character deletion +~~~~~~~~~~~~~~~~~~ + +- **Backspace** or **Ctrl+H**: Delete character before cursor +- **Delete** or **Ctrl+D**: Delete character at cursor +- **Ctrl+K**: Kill (delete) from cursor to end of line +- **Ctrl+W**: Kill word before cursor +- **Ctrl+U**: Kill entire line +- **Ctrl+X**: Kill entire line (same as Ctrl+U) + +History +~~~~~~~ + +- **Up arrow** or **Ctrl+P**: Previous command in history +- **Down arrow** or **Ctrl+N**: Next command in history + +Undo, redo, and yank +~~~~~~~~~~~~~~~~~~~~ + +When `CONFIG_CMDLINE_UNDO` is enabled, the following features are available: + +- **Ctrl+Z**: Undo the last edit operation +- **Ctrl+Shift+Z**: Redo the last undone operation +- **Ctrl+Y**: Yank (paste) previously killed text + +Text killed by Ctrl+K, Ctrl+W, Ctrl+U, or Ctrl+X is saved to a yank buffer +and can be pasted with Ctrl+Y. + +The number of undo/redo levels can be configured with `CONFIG_CMDLINE_UNDO_COUNT` +(default 1, maximum 64). Each level saves the complete buffer state, +so higher values use more memory. Note that any new edit clears the redo +history. + +Other +~~~~~ + +- **Tab**: Command and argument completion (if `CONFIG_AUTO_COMPLETE` is enabled) +- **Ctrl+C**: Cancel current input +- **Enter**: Execute command + +Multiline editing +~~~~~~~~~~~~~~~~~ + +In multiline mode (used by expo text editors), some keys have modified +behaviour: + +- **Home/End**: Move to start/end of current line (not entire buffer) +- **Ctrl+K**: Kill to end of current line (not entire buffer) +- **Ctrl+P/N** or **Up/Down**: Navigate between lines +- **Enter**: Insert newline (instead of executing) diff --git a/include/cli.h b/include/cli.h index 3040342de8e..1b23caaa078 100644 --- a/include/cli.h +++ b/include/cli.h @@ -8,6 +8,7 @@ #define __CLI_H #include +#include #include #include @@ -29,18 +30,44 @@ struct cli_ch_state { struct cli_line_state; /** - * struct cli_undo_state - state for undo buffer + * struct cli_undo_pos - saved state for a single undo/redo level * - * @buf: Buffer for saved state - * @num: Saved cursor position - * @eol_num: Saved end-of-line position + * Before any editing operation (insert, delete, kill, etc.), the entire + * buffer state is saved so it can be restored on undo. The buffer contents, + * cursor position, and line length are captured together. + * + * @buf: Complete copy of the edit buffer at the time of save + * @num: Cursor position (offset from start of buffer) + * @eol_num: Number of characters in the buffer (end-of-line position) */ -struct cli_undo_state { +struct cli_undo_pos { struct abuf buf; uint num; uint eol_num; }; +/** + * struct cli_undo_state - state for undo/redo ring buffer + * + * This implements a ring buffer for storing undo or redo states. Each state + * consists of a complete copy of the edit buffer plus the cursor position. + * The ring buffer allows multiple levels of undo/redo up to alloc entries. + * + * When saving a new state, it is written at the @head index, then @head + * advances (wrapping at alloc). When restoring, @head moves back and the + * state at that index is restored. The @count tracks how many valid states + * are available for undo/redo. + * + * @pos: List of &struct cli_undo_pos entries + * @head: Index where the next state will be saved (0 to alloc-1) + * @count: Number of valid states available (0 to alloc) + */ +struct cli_undo_state { + struct alist pos; + uint head; + uint count; +}; + /** * struct cli_editor_state - state for enhanced editing features * @@ -51,6 +78,7 @@ struct cli_undo_state { * @multiline: true if input may contain multiple lines (enables * Ctrl-P/N for line navigation instead of history) * @undo: Undo ring buffer state + * @redo: Redo ring buffer state * @yank: Buffer for killed text (for Ctrl+Y yank) * @yank_len: Length of killed text in yank buffer */ @@ -81,6 +109,9 @@ struct cli_editor_state { /** @undo: Undo state (if CONFIG_CMDLINE_UNDO) */ struct cli_undo_state undo; + /** @redo: Redo state (if CONFIG_CMDLINE_UNDO) */ + struct cli_undo_state redo; + /** @yank: Buffer for killed text (for Ctrl+Y yank) */ struct abuf yank; @@ -393,13 +424,14 @@ void cli_cread_add_initial(struct cli_line_state *cls); /** cread_print_hist_list() - Print the command-line history list */ void cread_print_hist_list(void); -/* - * Undo/yank functions - implementations in cli_undo.c when CMDLINE_UNDO is enabled - */ -#if CONFIG_IS_ENABLED(CMDLINE_UNDO) +#if CONFIG_IS_ENABLED(CMDLINE_EDITOR) /** * cread_save_undo() - Save current state for undo * + * Saves the buffer contents and cursor position to the undo ring buffer. + * Each call pushes a new undo state that can be restored with Ctrl+Z. + * Also clears the redo buffer since a new edit invalidates redo history. + * * @cls: CLI line state */ void cread_save_undo(struct cli_line_state *cls); @@ -407,13 +439,29 @@ void cread_save_undo(struct cli_line_state *cls); /** * cread_restore_undo() - Restore previous state from undo buffer * + * Restores the buffer contents and cursor position from the most recent + * undo state. Multiple calls restore progressively older states. The + * current state is saved to the redo buffer before restoring. + * * @cls: CLI line state */ void cread_restore_undo(struct cli_line_state *cls); +/** + * cread_redo() - Redo previously undone change + * + * Restores the buffer contents and cursor position from the redo buffer. + * The current state is saved to the undo buffer before restoring. + * + * @cls: CLI line state + */ +void cread_redo(struct cli_line_state *cls); + /** * cread_save_yank() - Save killed text to yank buffer * + * Saves the specified text so it can be yanked (pasted) later with Ctrl+Y. + * * @cls: CLI line state * @text: Text to save * @len: Length of text @@ -423,9 +471,21 @@ void cread_save_yank(struct cli_line_state *cls, const char *text, uint len); /** * cread_yank() - Insert yanked text at cursor position * + * Inserts the previously killed text at the current cursor position. + * * @cls: CLI line state */ void cread_yank(struct cli_line_state *cls); + +/** + * cread_clear_redo() - Clear the redo buffer + * + * Called when a new edit is made to invalidate the redo history. This should + * be called for any edit operation that modifies the buffer. + * + * @cls: CLI line state + */ +void cread_clear_redo(struct cli_line_state *cls); #else static inline void cread_save_undo(struct cli_line_state *cls) { @@ -435,6 +495,10 @@ static inline void cread_restore_undo(struct cli_line_state *cls) { } +static inline void cread_redo(struct cli_line_state *cls) +{ +} + static inline void cread_save_yank(struct cli_line_state *cls, const char *text, uint len) { @@ -443,6 +507,10 @@ static inline void cread_save_yank(struct cli_line_state *cls, const char *text, static inline void cread_yank(struct cli_line_state *cls) { } + +static inline void cread_clear_redo(struct cli_line_state *cls) +{ +} #endif #endif diff --git a/test/boot/editenv.c b/test/boot/editenv.c index ab3c6648886..9a41d269d17 100644 --- a/test/boot/editenv.c +++ b/test/boot/editenv.c @@ -74,8 +74,8 @@ static int editenv_test_base(struct unit_test_state *uts) int ret; /* - * Type "test" then press Ctrl-S to save - * \x13 is Ctrl-S + * Type "test" then press Ctrl-S to accept (Enter inserts newline in + * multiline mode) */ console_in_puts("test\x13"); ret = expo_editenv("myvar", NULL, buf, sizeof(buf)); @@ -94,7 +94,7 @@ static int editenv_test_initial(struct unit_test_state *uts) /* * Start with "world", go to start with Ctrl-A, type "hello ", then - * press Ctrl-S to save + * press Ctrl-S to accept */ console_in_puts("\x01hello \x13"); ret = expo_editenv("myvar", "world", buf, sizeof(buf)); @@ -190,11 +190,61 @@ static int editenv_test_funcs(struct unit_test_state *uts) ut_assertok(editenv_send(&info, CTL_CH('k'))); ut_asserteq(16033, ut_check_video(uts, "kill")); + /* Test undo - should restore the killed text */ + ut_assertok(editenv_send(&info, CTL_CH('z'))); + ut_asserteq(16877, ut_check_video(uts, "undo")); + + /* Kill again and yank it back - text should be restored */ + ut_assertok(editenv_send(&info, CTL_CH('k'))); + ut_asserteq(16033, ut_check_video(uts, "kill2")); + + ut_assertok(editenv_send(&info, CTL_CH('y'))); + ut_asserteq(16808, ut_check_video(uts, "yank")); + + /* Test Home - should go to start of current line */ + ut_assertok(editenv_send(&info, CTL_CH('a'))); + ut_asserteq(0, info.ted->tin.cls.num); + ut_asserteq(16845, ut_check_video(uts, "home")); + + /* Test End - should go to end of current line */ + ut_assertok(editenv_send(&info, CTL_CH('e'))); + ut_asserteq(16808, ut_check_video(uts, "end")); + + /* Go left two words with Ctrl+R */ + ut_assertok(editenv_send(&info, CTL_CH('r'))); + ut_asserteq(16838, ut_check_video(uts, "left1")); + + ut_assertok(editenv_send(&info, CTL_CH('r'))); + ut_asserteq(16812, ut_check_video(uts, "left2")); + + /* Delete three words with Ctrl+W */ + ut_assertok(editenv_send(&info, CTL_CH('w'))); + ut_asserteq(16691, ut_check_video(uts, "delw1")); + + ut_assertok(editenv_send(&info, CTL_CH('w'))); + ut_asserteq(16445, ut_check_video(uts, "delw2")); + + ut_assertok(editenv_send(&info, CTL_CH('w'))); + ut_asserteq(16118, ut_check_video(uts, "delw3")); + + /* Undo to restore one deleted word */ + ut_assertok(editenv_send(&info, CTL_CH('z'))); + ut_asserteq(16445, ut_check_video(uts, "undo1")); + + /* Type a character - this clears the redo buffer */ + ut_assertok(editenv_send(&info, '!')); + ut_asserteq(16469, ut_check_video(uts, "type")); + + /* Redo (Ctrl+G) should do nothing since typing cleared the redo buffer */ + ut_assertok(editenv_send(&info, CTL_CH('g'))); + ut_asserteq(16469, ut_check_video(uts, "redo")); + + /* Press Ctrl-S to save */ ut_asserteq(1, editenv_send(&info, BKEY_SAVE)); - /* The '*' is inserted after "tes", Ctrl-K killed "ted properly." */ - ut_assert(strstr(expo_editenv_result(&info), "tes*\n")); - ut_asserteq(16033, ut_check_video(uts, "save")); + /* The '*' and '!' are inserted; redo did nothing since it was cleared */ + ut_assert(strstr(expo_editenv_result(&info), "tes*ted")); + ut_asserteq(16469, ut_check_video(uts, "save")); expo_editenv_uninit(&info); diff --git a/test/boot/expo.c b/test/boot/expo.c index 5445fed19c1..7a3285535b5 100644 --- a/test/boot/expo.c +++ b/test/boot/expo.c @@ -1735,6 +1735,16 @@ static int expo_render_textedit(struct unit_test_state *uts) ut_asserteq(90, ted->tin.cls.eol_num); ut_asserteq('\n', ((char *)abuf_data(&ted->tin.buf))[89]); + /* Ctrl+Z undoes the yank */ + ut_assertok(expo_send_key(exp, CTL_CH('z'))); + ut_asserteq(85, ted->tin.cls.num); + ut_asserteq(86, ted->tin.cls.eol_num); + + /* Ctrl+Shift+Z (internal code 'g') redoes the yank */ + ut_assertok(expo_send_key(exp, CTL_CH('g'))); + ut_asserteq(89, ted->tin.cls.num); + ut_asserteq(90, ted->tin.cls.eol_num); + /* clear multiline mode, close the textedit with Enter (BKEY_SELECT) */ ted->obj.flags &= ~SCENEOF_MULTILINE; ut_assertok(expo_send_key(exp, BKEY_SELECT)); @@ -1743,7 +1753,7 @@ static int expo_render_textedit(struct unit_test_state *uts) ut_asserteq(OBJ_TEXTED, act.select.id); ut_assertok(scene_set_open(scn, act.select.id, false)); - /* check the textedit is closed and text is changed */ + /* check the textedit is closed and text is changed (redo restored latr) */ ut_asserteq(0, ted->obj.flags & SCENEOF_OPEN); ut_asserteq_str("This\ns the initial contents of the text " "editor but it is ely that more will be added latr\n",