Skip to content

Add TextCursor tool for precise caret-level text control#351

Open
JezaChen wants to merge 5 commits into
CursorTouch:mainfrom
JezaChen:feat/text-cursor-tool
Open

Add TextCursor tool for precise caret-level text control#351
JezaChen wants to merge 5 commits into
CursorTouch:mainfrom
JezaChen:feat/text-cursor-tool

Conversation

@JezaChen

Copy link
Copy Markdown
Collaborator

As we discussed earlier, I've been working on implementing a new tool over the past few days, TextCursor, that inspects and manipulates the caret/selection of the focused Windows text control directly through UI Automation (IUIAutomationTextPattern).

Until now, working with text meant simulating mouse and keyboard input, which is imprecise for anything finer than whole-field edits — you cannot reliably place the caret or select an exact span. TextCursor operates on the control's own text range, so positioning and selection are exact and format-preserving.

What it does

Seven modes, all against the focused control:

  • get_info — read the current caret/selection (offsets, surrounding text, bounds)
  • move_relative / move_absolute — move the caret by a signed delta or to an absolute character offset
  • select_relative / select_absolute — select a text range
  • select_all
  • collapse_selection — collapse a selection to its start or end edge

Offsets use provider-defined UIA TextUnit_Character steps from the document start, and returned offsets can be fed straight back into absolute actions. Write operations are verified by reading the applied range back. Requires a control that exposes the UIA TextPattern.

Limitations

  • TextPattern-only. TextCursor works only with controls that expose the UIA TextPattern. Most modern Windows text inputs provide it, but some do not yet — for example apps built on Java UI frameworks (PyCharm and other JetBrains IDEs) and Notepad++.
  • No multi-selection. Only a single caret/selection is supported for now; disjoint multi-range selection can be added later.
  • Provider-dependent accuracy. TextCursor's behavior partly depends on the TextPattern provider. Providers with a buggy implementation may cause the caret/selection to land at the wrong position.

Demo

Using TextCursor to replace the 2nd-last and 3rd-last occurrences of the word is before the caret with was, while leaving the surrounding rich-text formatting intact:

textcursor-demo4.mp4

JezaChen added 4 commits July 22, 2026 21:21
…n facade

Break the ~1300-line text_cursor/__init__.py into cohesive modules
(constants, errors, models, uia, worker, discovery, ranges, snapshots,
operations, service) and reduce __init__.py to a small public facade.

- Single test seam: collaborators are called by module-level name and
  patched on their owning module; drop the dependency-injection params
  that duplicated that seam.
- Break the uia<->worker cycle: move find_caret_provider and
  try_get_caret_on_element into discovery.py so uia.py is a pure leaf.
- Trim the facade to the intended public API (I/O + action models,
  errors, run_tool); stop re-exporting internal plumbing.
- Remove dead code: the unused UIA_TEXT_PATTERN2_ID constant and
  worker.co_uninitialize.
- Minor cleanups: drop a redundant int() wrap and document
  SelectRelativeAction.origin.

Repoint tests to import internals from their owning submodules. No
behavior change; full suite passes.
Add TextCursor to the README tool list and the manifest.json tools
array, and update the README limitation about in-paragraph text
selection now that TextCursor covers it for controls that expose the
UIA TextPattern.
Copilot AI review requested due to automatic review settings July 25, 2026 04:43
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add TextCursor tool for caret/selection control via UI Automation

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add TextCursor MCP tool to inspect and manipulate focused control caret/selection via UIA
 TextPattern.
• Implement COM worker-thread UIA plumbing, range operations, snapshots, and verification on writes.
• Register the tool, document it, add manifest metadata, and expand test coverage for
 errors/contract behavior.
Diagram

graph TD
client{{"MCP client"}} --> tool["TextCursor tool"] --> svc["text_cursor.service"] --> exec(("COM worker")) --> uia["discovery/uia wrappers"] --> win{{"Windows UI Automation"}} --> ctrl["Focused text control"]
svc --> payload["CursorSnapshot/Result"] --> tool
subgraph Legend
direction LR
_ext{{"External"}} ~~~ _mod["Module"] ~~~ _thr(("Thread"))
end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use TextPattern2.GetCaretRange when available
  • ➕ Can identify the active caret endpoint even when a non-empty selection exists
  • ➕ Potentially simpler caret discovery for providers that implement it correctly
  • ➖ Does not represent full selection range; still need GetSelection for range operations
  • ➖ Provider support/behavior can be inconsistent; adds branching and test matrix complexity
2. Allow targeting an explicit UIA element instead of only focused control
  • ➕ More deterministic than focus-based discovery; reduces flakiness in multi-window flows
  • ➕ Enables operating on non-focused elements in the future
  • ➖ Requires a stable element identity/handle strategy exposed through MCP
  • ➖ More validation and security considerations for cross-process UIA access

Recommendation: The PR’s approach (TextPattern.GetSelection on the focused element with ancestor-walk fallback, plus read-back verification) is a good default for precise caret/selection control while keeping the public API focused. Consider adding optional TextPattern2-based caret-endpoint enrichment later (as a non-breaking enhancement) for providers where it’s reliable, and consider an explicit-target mode only if real-world focus flakiness becomes a recurring issue.

Files changed (17) +1948 / -1

Enhancement (13) +1382 / -0
__init__.pyDefine public TextCursor facade API +59/-0

Define public TextCursor facade API

• Introduces the text_cursor package entrypoint with module docs and a constrained public surface (actions/models, errors, and run_tool). Avoids re-exporting internal plumbing.

src/windows_mcp/text_cursor/init.py

constants.pyAdd UIA/COM constants and payload caps +13/-0

Add UIA/COM constants and payload caps

• Defines UIA pattern IDs, UIAutomation CLSIDs, maximum move bounds, and a cap for selected-text serialization to limit MCP payload size.

src/windows_mcp/text_cursor/constants.py

discovery.pyImplement focused TextPattern discovery with parent walk +78/-0

Implement focused TextPattern discovery with parent walk

• Finds the caret/selection provider by starting at the focused element and walking RawView parents to locate a TextPattern. Uses GetSelection as the single discovery primitive and returns metadata about degeneracy and selection count.

src/windows_mcp/text_cursor/discovery.py

errors.pyAdd TextCursor error types +9/-0

Add TextCursor error types

• Defines a base TextCursorError and a specific TextCursorVerificationError raised when a write cannot be verified via read-back.

src/windows_mcp/text_cursor/errors.py

models.pyAdd pydantic action/result models for TextCursor +241/-0

Add pydantic action/result models for TextCursor

• Defines discriminated action models for all modes (get/move/select/collapse) with validation and bounds. Adds CursorSnapshot and CursorToolResult payloads including offsets, context, bounding rects, and warnings.

src/windows_mcp/text_cursor/models.py

operations.pyImplement write-mode operations against UIA ranges +139/-0

Implement write-mode operations against UIA ranges

• Implements move/select/select_all/collapse operations by cloning/manipulating UIA TextRanges and applying them via Select(). Centralizes dispatch via apply_write and returns target metadata plus optional verification outcome.

src/windows_mcp/text_cursor/operations.py

ranges.pyAdd range construction, origin resolution, and verification helpers +98/-0

Add range construction, origin resolution, and verification helpers

• Provides helpers to pick move/select origins from a caret vs selection, build absolute positions from DocumentRange, construct ranges from endpoints, apply changes, and verify provider-applied results via read-back selection equality.

src/windows_mcp/text_cursor/ranges.py

service.pyOrchestrate TextCursor execution on COM worker thread +116/-0

Orchestrate TextCursor execution on COM worker thread

• Implements run_tool async entrypoint with optional delay, executing on a single-thread executor. Captures before/after snapshots, separates computed target from read-back actual, and raises TextCursorVerificationError on mismatches.

src/windows_mcp/text_cursor/service.py

snapshots.pyCreate serializable caret/selection snapshots with warnings +138/-0

Create serializable caret/selection snapshots with warnings

• Computes caret/selection offsets from document start, reads selected text/context defensively, truncates overly large selections, and emits warnings for multi-selection and unknown active caret endpoint. Provides helper to extract only position fields for reporting.

src/windows_mcp/text_cursor/snapshots.py

uia.pyAdd low-level UIA TextPattern/TextRange wrappers +373/-0

Add low-level UIA TextPattern/TextRange wrappers

• Wraps UIA COM interactions (create automation client, focused element, TextPattern selections, TextRange operations). Adds helpers for bounding rectangles and computing start offsets via large backward Move(), while handling COM failures conservatively.

src/windows_mcp/text_cursor/uia.py

worker.pyAdd single-thread COM worker and cached UIA client +69/-0

Add single-thread COM worker and cached UIA client

• Introduces a single-worker ThreadPoolExecutor, initializes COM MTA once per thread, and caches the IUIAutomation client for reuse across calls. Validates apartment compatibility and errors on initialization failures.

src/windows_mcp/text_cursor/worker.py

__init__.pyRegister TextCursor tool module +2/-0

Register TextCursor tool module

• Adds text_cursor to the tools subpackage imports and the register_all module list so the server exposes the new tool.

src/windows_mcp/tools/init.py

text_cursor.pyAdd FastMCP TextCursor tool wrapper with analytics +47/-0

Add FastMCP TextCursor tool wrapper with analytics

• Defines the TextCursor tool registration, tool description, and annotations. Wraps the async implementation with with_analytics and includes a Context parameter to enable client metadata capture.

src/windows_mcp/tools/text_cursor.py

Tests (2) +560 / -0
test_iserror_compliance.pyAdd TextCursor ToolError compliance and verification-failure test +66/-0

Add TextCursor ToolError compliance and verification-failure test

• Adds a test ensuring TextCursor failures surface as ToolError through the MCP interface. Adds a unit test ensuring write verification failures raise TextCursorVerificationError rather than returning a successful payload.

tests/test_iserror_compliance.py

test_text_cursor.pyAdd comprehensive unit tests for TextCursor models, snapshots, and service +494/-0

Add comprehensive unit tests for TextCursor models, snapshots, and service

• Adds extensive tests covering pydantic validation bounds, offset round-tripping, snapshot behavior under COM errors, truncation logic, cancellation semantics for delayed actions, and service reporting of target vs actual positions.

tests/test_text_cursor.py

Documentation (1) +2 / -1
README.mdDocument TextCursor and update limitations +2/-1

Document TextCursor and update limitations

• Adds TextCursor to the tool list with a short capability/requirement summary. Updates the limitations section to note that in-paragraph selection is supported when controls expose UIA TextPattern.

README.md

Other (1) +4 / -0
manifest.jsonExpose TextCursor in tool manifest metadata +4/-0

Expose TextCursor in tool manifest metadata

• Registers the new TextCursor tool in the manifest tools array with a detailed mode/offset description and TextPattern requirement.

manifest.json

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new TextCursor MCP tool and supporting windows_mcp.text_cursor package to inspect and manipulate the focused Windows text control’s caret/selection via UI Automation TextPattern, enabling precise caret-level moves and exact range selection (without simulated mouse/keyboard input).

Changes:

  • Added a full windows_mcp.text_cursor implementation (UIA wrappers, discovery, range ops, snapshots, worker-thread execution, and models).
  • Registered the new TextCursor tool and integrated it into tool registration, manifest, and README documentation.
  • Added comprehensive unit tests for snapshots/range math, cancellation behavior, and error propagation/verification failures.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/test_text_cursor.py New unit tests covering snapshot/range behavior and edge cases.
tests/test_iserror_compliance.py Ensures TextCursor errors surface as ToolError and verification failures raise.
src/windows_mcp/tools/text_cursor.py New MCP tool wrapper/registration and tool description.
src/windows_mcp/tools/init.py Adds TextCursor to tool import list and _MODULES registration.
src/windows_mcp/text_cursor/init.py Package entrypoint exporting models/errors and run_tool.
src/windows_mcp/text_cursor/constants.py UIA constants and payload caps (e.g., max selected-text length).
src/windows_mcp/text_cursor/errors.py Defines TextCursorError and TextCursorVerificationError.
src/windows_mcp/text_cursor/worker.py Dedicated single-thread executor and per-thread COM/UIA client caching.
src/windows_mcp/text_cursor/uia.py Low-level UIA TextPattern/TextRange wrappers and offset helpers.
src/windows_mcp/text_cursor/discovery.py Focused-element discovery and parent-walk to locate TextPattern provider.
src/windows_mcp/text_cursor/ranges.py Range construction, document positioning, apply/select, and verification helpers.
src/windows_mcp/text_cursor/operations.py Implements write operations for all supported modes.
src/windows_mcp/text_cursor/snapshots.py Builds serializable caret/selection snapshots (context, bounds, warnings).
src/windows_mcp/text_cursor/service.py Orchestrates get/write actions on the COM worker thread; verifies writes.
src/windows_mcp/text_cursor/models.py Pydantic models for actions, snapshots, and tool result payloads.
README.md Documents the new TextCursor tool and updates limitations section accordingly.
manifest.json Adds TextCursor to the published tool manifest.
Comments suppressed due to low confidence (2)

src/windows_mcp/text_cursor/models.py:194

  • exclude_if is not a valid keyword for pydantic.Field in the repo's Pydantic version (2.13.x), so these field declarations will fail at import time. Remove exclude_if from the snapshot fields.
    selection_start_units: int | None = Field(
        default=None,
        exclude_if=_ignore_none,
        description=(
            "Selection start in provider-defined UIA TextUnit_Character steps "

src/windows_mcp/text_cursor/models.py:209

  • exclude_if is not supported by pydantic.Field in Pydantic 2.13.x; these usages will break imports. Remove exclude_if from these optional fields.
    selected_text: str | None = Field(default=None, exclude_if=_ignore_none)
    text_before: str | None = Field(default=None, exclude_if=_ignore_none)
    text_after: str | None = Field(default=None, exclude_if=_ignore_none)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/windows_mcp/text_cursor/models.py
@qodo-code-review

qodo-code-review Bot commented Jul 25, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (5) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 15 rules

Grey Divider


Remediation recommended

1. Overlong TextCursor description line 📘 Rule violation ✧ Quality
Description
New lines exceed the 100-character maximum in manifest.json and README.md, which reduces
readability and makes diffs harder to review. These long, single-line descriptions should be wrapped
or shortened to meet the 100-character limit.
Code

manifest.json[153]

+      "description": "Inspects or manipulates the caret/selection of the focused Windows text control through UI Automation. Modes: 'get_info' (read caret/selection info), 'move_relative' and 'move_absolute' (move the caret by a signed delta or to an absolute character offset), 'select_relative' and 'select_absolute' (select a text range), 'select_all', 'collapse_selection' (collapse a selection to its start or end edge). Offsets use provider-defined UIA TextUnit_Character steps from the document start. Requires a control that exposes the UIA TextPattern."
Relevance

⭐⭐⭐ High

Team has accepted fixing >100-char lines before; will likely wrap manifest/README lines too.

PR-#307
PR-#329

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 222796 requires no non-comment line to exceed 100 characters. The added
TextCursor description in manifest.json is a single very long line, and the added README.md
TextCursor bullet and limitations bullet are also over 100 characters.

Rule 222796: Enforce maximum line length of 100 characters
manifest.json[153-153]
README.md[732-732]
README.md[797-797]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Several newly added lines exceed the 100-character maximum line length (notably the new `TextCursor` descriptions).

## Issue Context
The repo compliance checklist enforces a 100-character max for non-whitespace, non-comment lines.

## Fix Focus Areas
- manifest.json[153-153]
- README.md[732-732]
- README.md[797-797]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Single-quoted strings in discovery.py 📘 Rule violation ✧ Quality
Description
The new f-string in find_caret_provider() uses single-quoted string literals ('' and `f'"{...}"
'`) which violates the double-quote-only string literal style requirement. This creates inconsistent
string quoting style within the new TextCursor implementation.
Code

src/windows_mcp/text_cursor/discovery.py[76]

+        f"The focused element {f'"{element_name}" ' if element_name else ''}and "
Relevance

⭐⭐⭐ High

Double-quote string literal enforcement was accepted previously; single quotes in new f-string
likely must be changed.

PR-#307

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 222799 requires double quotes for string literal delimiters. The new RuntimeError
message construction uses single-quoted string literals ('' and an inner f-string delimited by
single quotes) on the added line.

Rule 222799: Enforce double quotes for all string literals
src/windows_mcp/text_cursor/discovery.py[75-77]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`src/windows_mcp/text_cursor/discovery.py` introduces single-quoted string literals inside a new f-string expression.

## Issue Context
The compliance checklist requires double quotes for all string literals where possible.

## Fix Focus Areas
- src/windows_mcp/text_cursor/discovery.py[75-77]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Missing type hints in new defs 📘 Rule violation ✧ Quality
Description
Several newly added def functions/methods do not fully type-annotate their signatures (parameters
and/or return types), violating the requirement for type hints on all function signatures. This
reduces static-checking coverage for the new TextCursor feature and tests.
Code

src/windows_mcp/tools/text_cursor.py[30]

+def register(mcp, *, get_desktop, get_analytics):
Relevance

⭐⭐ Medium

No close precedent found specifically enforcing full signature type hints; could be required, but
acceptance uncertain.

PR-#329

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 222805 requires all function signatures to include parameter annotations and return
type annotations. The new register function has no parameter/return annotations, multiple new
class __init__ methods omit annotations, apply_change has no return annotation, and new test
functions omit signature annotations.

Rule 222805: Require type hints for all function signatures
src/windows_mcp/tools/text_cursor.py[30-47]
src/windows_mcp/text_cursor/uia.py[20-27]
src/windows_mcp/text_cursor/ranges.py[75-80]
tests/test_iserror_compliance.py[90-115]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Newly added functions/methods are missing required type hints on parameters and/or return types.

## Issue Context
The compliance checklist requires explicit type annotations for every parameter and for the return type of every `def`.

## Fix Focus Areas
- src/windows_mcp/tools/text_cursor.py[30-30]
- src/windows_mcp/text_cursor/uia.py[20-27]
- src/windows_mcp/text_cursor/ranges.py[75-80]
- tests/test_iserror_compliance.py[90-115]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (3)
4. TextCursor tool bypasses Desktop 📘 Rule violation ⌂ Architecture
Description
The MCP entry-point tool implementation for TextCursor does not delegate to the Desktop service
layer (it never uses get_desktop and calls windows_mcp.text_cursor.run_tool directly). This
violates the requirement that MCP entry-point tools remain thin adapters over Desktop services.
Code

src/windows_mcp/tools/text_cursor.py[R30-47]

+def register(mcp, *, get_desktop, get_analytics):
+    @mcp.tool(
+        name="TextCursor",
+        description=_description,
+        annotations=ToolAnnotations(
+            title="TextCursor",
+            readOnlyHint=False,
+            destructiveHint=True,
+            idempotentHint=False,
+            openWorldHint=True,
+        ),
+    )
+    @with_analytics(get_analytics(), "TextCursor-Tool")
+    async def text_cursor(
+        action: CursorAction,
+        ctx: Context = None,
+    ) -> CursorToolResult:
+        return await run_tool(action)
Relevance

⭐⭐ Medium

No analogous precedent found requiring MCP tools to always delegate via Desktop; enforcement
likelihood unclear.

PR-#329

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 222807 requires MCP entry-point tool handlers to delegate to Desktop services
rather than implementing/performing platform behavior directly. The new TextCursor tool registers
an MCP tool and directly returns await run_tool(action) without using get_desktop().

Rule 222807: MCP entry-point tools must delegate to Desktop service layer
src/windows_mcp/tools/text_cursor.py[30-47]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The `TextCursor` MCP tool implementation directly calls the TextCursor implementation (`run_tool`) and does not delegate through the Desktop service layer.

## Issue Context
Entry-point tools should remain thin adapters and call Desktop service methods for domain/platform logic.

## Fix Focus Areas
- src/windows_mcp/tools/text_cursor.py[30-47]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Eager comtypes import regression ✗ Dismissed 🐞 Bug ☼ Reliability
Description
windows_mcp.tools now eagerly imports the TextCursor tool, which imports the TextCursor
implementation and comtypes at module import time, so WINDOWS_MCP_WATCHDOG=off no longer
prevents comtypes from being loaded during startup. This breaks the documented intent that
disabling the watchdog avoids loading comtypes, and can reintroduce startup overhead or failures
in environments that relied on the flag to avoid UIA/COM dependencies.
Code

src/windows_mcp/tools/init.py[R13-17]

    scrape,
    shell,
    snapshot,
+    text_cursor,
)
Relevance

⭐⭐ Medium

Eager-import/startup side-effect concerns are plausible, but no close repo precedent on
comtypes/watchdog import behavior.

PR-#214
PR-#328

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The tools package now imports the TextCursor tool module during startup; that tool module imports
the TextCursor implementation, which imports UIA wrappers that import comtypes at module import
time. This contradicts the existing startup comment/intent that disabling the watchdog avoids
loading comtypes.

src/windows_mcp/tools/init.py[3-33]
src/windows_mcp/tools/text_cursor.py[6-10]
src/windows_mcp/text_cursor/uia.py[9-16]
src/windows_mcp/main.py[202-205]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The server startup path imports `windows_mcp.tools`, which now imports the TextCursor tool module and (transitively) imports `comtypes` at module import time. This contradicts the existing documented intent in `__main__.py` that disabling the watchdog avoids loading `comtypes`, and increases startup coupling to UIA/COM even when TextCursor is unused.

## Issue Context
- `windows_mcp.__main__` explicitly notes the watchdog is imported lazily so that a disabled watchdog "never loads comtypes".
- After this PR, importing `windows_mcp.tools` eagerly imports `windows_mcp.tools.text_cursor`, which eagerly imports `windows_mcp.text_cursor`, which imports UIA wrappers that import `comtypes` at module import time.

## Fix Focus Areas
- Defer `comtypes` imports in the TextCursor stack until the tool is actually invoked (or, alternatively, adjust the `__main__.py` comment/guarantee to reflect the new behavior).
- Practical approaches:
 - Move `import comtypes.client` / `from comtypes import COMError` into the specific functions that need them (e.g., `create_automation`, parent-walk calls), so merely registering tools doesn’t import `comtypes`.
 - Optionally, also avoid importing `windows_mcp.text_cursor` in `windows_mcp.tools.text_cursor` at module import time (use `TYPE_CHECKING` + runtime lazy import).

### Code pointers
- src/windows_mcp/tools/__init__.py[3-33]
- src/windows_mcp/tools/text_cursor.py[6-10]
- src/windows_mcp/text_cursor/uia.py[9-16]
- src/windows_mcp/text_cursor/discovery.py[10-16]
- src/windows_mcp/__main__.py[202-205]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. run_tool docstring not Google-style 📘 Rule violation ✧ Quality
Description
The new public run_tool() docstring does not follow Google-style structure (missing explicit
Args:/Returns: sections), and register() has no docstring at all. This violates the
requirement for Google-style docstrings on public functions/classes in changed files.
Code

src/windows_mcp/text_cursor/service.py[R90-107]

+async def run_tool(action: CursorAction) -> CursorToolResult:
+    """
+    Inspect or manipulate the focused Windows text control through UIA.
+    Modes:
+    - get_info
+    - move_relative
+    - move_absolute
+    - select_relative
+    - select_absolute
+    - select_all
+    - collapse_selection
+    Every mode accepts `delay`, expressed in seconds. The delay occurs before
+    the focused UIA element is located, so the caller can focus the target
+    control during that interval.
+    Absolute move/select inputs and returned offsets both use provider-defined
+    UIA TextUnit_Character steps from DocumentRange start. Returned offsets can
+    be passed directly to absolute move/select actions.
+    """
Relevance

⭐⭐ Medium

No close precedent on enforcing Google-style docstrings for new public functions; may be requested
but uncertain.

PR-#196

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
PR Compliance ID 222802 requires Google-style docstrings on public functions and classes. The new
run_tool() docstring is a free-form block without Google-style Args:/Returns: sections, and
the new public register() function in the tool module has no docstring.

Rule 222802: Require Google-style docstrings on public functions and classes
src/windows_mcp/text_cursor/service.py[90-107]
src/windows_mcp/tools/text_cursor.py[30-47]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Public functions introduced/modified in this PR are missing Google-style docstrings (and some are missing docstrings entirely).

## Issue Context
Google-style docstrings should include a one-line summary and structured `Args:`/`Returns:` sections for public functions.

## Fix Focus Areas
- src/windows_mcp/text_cursor/service.py[90-107]
- src/windows_mcp/tools/text_cursor.py[30-47]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread manifest.json
},
{
"name": "TextCursor",
"description": "Inspects or manipulates the caret/selection of the focused Windows text control through UI Automation. Modes: 'get_info' (read caret/selection info), 'move_relative' and 'move_absolute' (move the caret by a signed delta or to an absolute character offset), 'select_relative' and 'select_absolute' (select a text range), 'select_all', 'collapse_selection' (collapse a selection to its start or end edge). Offsets use provider-defined UIA TextUnit_Character steps from the document start. Requires a control that exposes the UIA TextPattern."

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. Overlong textcursor description line 📘 Rule violation ✧ Quality

New lines exceed the 100-character maximum in manifest.json and README.md, which reduces
readability and makes diffs harder to review. These long, single-line descriptions should be wrapped
or shortened to meet the 100-character limit.
Agent Prompt
## Issue description
Several newly added lines exceed the 100-character maximum line length (notably the new `TextCursor` descriptions).

## Issue Context
The repo compliance checklist enforces a 100-character max for non-whitespace, non-comment lines.

## Fix Focus Areas
- manifest.json[153-153]
- README.md[732-732]
- README.md[797-797]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

element = None

raise RuntimeError(
f"The focused element {f'"{element_name}" ' if element_name else ''}and "

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

2. Single-quoted strings in discovery.py 📘 Rule violation ✧ Quality

The new f-string in find_caret_provider() uses single-quoted string literals ('' and `f'"{...}"
'`) which violates the double-quote-only string literal style requirement. This creates inconsistent
string quoting style within the new TextCursor implementation.
Agent Prompt
## Issue description
`src/windows_mcp/text_cursor/discovery.py` introduces single-quoted string literals inside a new f-string expression.

## Issue Context
The compliance checklist requires double quotes for all string literals where possible.

## Fix Focus Areas
- src/windows_mcp/text_cursor/discovery.py[75-77]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +30 to +47
def register(mcp, *, get_desktop, get_analytics):
@mcp.tool(
name="TextCursor",
description=_description,
annotations=ToolAnnotations(
title="TextCursor",
readOnlyHint=False,
destructiveHint=True,
idempotentHint=False,
openWorldHint=True,
),
)
@with_analytics(get_analytics(), "TextCursor-Tool")
async def text_cursor(
action: CursorAction,
ctx: Context = None,
) -> CursorToolResult:
return await run_tool(action)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

3. textcursor tool bypasses desktop 📘 Rule violation ⌂ Architecture

The MCP entry-point tool implementation for TextCursor does not delegate to the Desktop service
layer (it never uses get_desktop and calls windows_mcp.text_cursor.run_tool directly). This
violates the requirement that MCP entry-point tools remain thin adapters over Desktop services.
Agent Prompt
## Issue description
The `TextCursor` MCP tool implementation directly calls the TextCursor implementation (`run_tool`) and does not delegate through the Desktop service layer.

## Issue Context
Entry-point tools should remain thin adapters and call Desktop service methods for domain/platform logic.

## Fix Focus Areas
- src/windows_mcp/tools/text_cursor.py[30-47]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

"""


def register(mcp, *, get_desktop, get_analytics):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

4. Missing type hints in new defs 📘 Rule violation ✧ Quality

Several newly added def functions/methods do not fully type-annotate their signatures (parameters
and/or return types), violating the requirement for type hints on all function signatures. This
reduces static-checking coverage for the new TextCursor feature and tests.
Agent Prompt
## Issue description
Newly added functions/methods are missing required type hints on parameters and/or return types.

## Issue Context
The compliance checklist requires explicit type annotations for every parameter and for the return type of every `def`.

## Fix Focus Areas
- src/windows_mcp/tools/text_cursor.py[30-30]
- src/windows_mcp/text_cursor/uia.py[20-27]
- src/windows_mcp/text_cursor/ranges.py[75-80]
- tests/test_iserror_compliance.py[90-115]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +90 to +107
async def run_tool(action: CursorAction) -> CursorToolResult:
"""
Inspect or manipulate the focused Windows text control through UIA.
Modes:
- get_info
- move_relative
- move_absolute
- select_relative
- select_absolute
- select_all
- collapse_selection
Every mode accepts `delay`, expressed in seconds. The delay occurs before
the focused UIA element is located, so the caller can focus the target
control during that interval.
Absolute move/select inputs and returned offsets both use provider-defined
UIA TextUnit_Character steps from DocumentRange start. Returned offsets can
be passed directly to absolute move/select actions.
"""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

5. run_tool docstring not google-style 📘 Rule violation ✧ Quality

The new public run_tool() docstring does not follow Google-style structure (missing explicit
Args:/Returns: sections), and register() has no docstring at all. This violates the
requirement for Google-style docstrings on public functions/classes in changed files.
Agent Prompt
## Issue description
Public functions introduced/modified in this PR are missing Google-style docstrings (and some are missing docstrings entirely).

## Issue Context
Google-style docstrings should include a one-line summary and structured `Args:`/`Returns:` sections for public functions.

## Fix Focus Areas
- src/windows_mcp/text_cursor/service.py[90-107]
- src/windows_mcp/tools/text_cursor.py[30-47]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread src/windows_mcp/tools/__init__.py
@Jeomon

Jeomon commented Jul 26, 2026

Copy link
Copy Markdown
Member

Awesome, work
Like, is it possible to simplify this? Recently, I made a change in the uia module to access it using a text range from the pattern.
Ig its my last commit, I was busy, which is why I couldn't work on it further.

Please check
ac4f0d5

@JezaChen

Copy link
Copy Markdown
Collaborator Author

Awesome, work Like, is it possible to simplify this? Recently, I made a change in the uia module to access it using a text range from the pattern. Ig its my last commit, I was busy, which is why I couldn't work on it further.

Please check ac4f0d5

Yeah, definitely. This is just my first-step commit — I originally built TextCursor as a standalone MCP tool and then ported it into Windows-MCP, which is why it currently carries its own thin UIA/TextPattern wrappers (text_cursor/uia.py) that duplicate what src/windows_mcp/uia already provides.

Next step is to consolidate: drop the parallel layer and move the UIA/text-range logic onto the shared uia module — reusing the TextPattern/TextRange abstractions in patterns.py (and the text-range helpers you added in ac4f0d5).

…M layer

TextCursor previously reimplemented UIA text ranges/patterns and ran on a
dedicated MTA worker with its own COM client. Consolidate onto the shared
windows_mcp.uia:

- Promote the reusable range helpers onto uia.TextRange/TextPattern
  (IsDegenerate, Collapse, GetStartOffset, GetTextBefore/After,
  GetFirstSelection) and normalize TextRange.GetText None -> "".
- Delete text_cursor's TextRange/TextPattern/UIAElement wrappers, the
  dedicated MTA worker, and create_automation; run on the main-thread STA
  and reuse the shared client via GetFocusedControl / Control, like the
  other UIA tools.
- Inline the one-shot execute_sync dispatch into run_tool (kept async so
  the delay stays non-blocking and cancellable).
Copilot AI review requested due to automatic review settings July 26, 2026 16:16

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 3 comments.

Comment on lines +22 to +28
marker = caret_info.text_range.Clone()
marker.Collapse(toEnd=(endpoint == TextPatternRangeEndpoint.End))

try:
return marker.GetStartOffset()
except (COMError, AttributeError, TypeError):
return None
Comment on lines +33 to +34
base.Collapse(toEnd=(origin == "selection_end"))
return base
Comment on lines +108 to +110
target = caret_info.text_range.Clone()
target.Collapse(toEnd=(action.edge == "end"))

@JezaChen

JezaChen commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator Author

I checked ac4f0d5 and simplified the implementation (see 100ec45) to reuse windows_mcp.uia as much as reasonably possible.

TextCursor now uses the shared Control, TextPattern, and TextRange implementations directly. The remaining code is essentially a higher-level layer over TextPattern, rather than a separate UIA implementation.

UIA does not expose the caret as a first-class concept—TextPattern only exposes text ranges and selections. Therefore, TextCursor still needs some orchestration to interpret a degenerate selection as a caret, manipulate range endpoints, apply changes through Select(), and verify the resulting selection.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants