feat(ui): Implement visual Slice Editor with colored overlays and click-drag
This commit is contained in:
+72
-17
@@ -245,6 +245,8 @@ class App:
|
||||
self._cached_ast_file_path = ''
|
||||
self._cached_ast_file_lines = []
|
||||
self.ui_editing_slices_file = None
|
||||
self._slice_sel_start = -1
|
||||
self._slice_sel_end = -1
|
||||
self.context_files = []
|
||||
"""UI-level wrapper for approving a pending tool execution ask."""
|
||||
self._handle_approve_ask()
|
||||
@@ -1423,31 +1425,48 @@ class App:
|
||||
self.show_text_viewer = bool(opened)
|
||||
if not opened:
|
||||
self.ui_editing_slices_file = None
|
||||
self._slice_sel_start = -1
|
||||
self._slice_sel_end = -1
|
||||
|
||||
if expanded:
|
||||
if self.ui_editing_slices_file is not None:
|
||||
imgui.text("Slice Management")
|
||||
imgui.text_colored(C_IN, "Slice Management (Click-drag lines to select range)")
|
||||
if imgui.button("Add Selection as Slice"):
|
||||
selected = self._text_viewer_editor.get_selected_text()
|
||||
if selected:
|
||||
# Find line range
|
||||
full = self.text_viewer_content
|
||||
start_idx = full.find(selected)
|
||||
if start_idx != -1:
|
||||
end_idx = start_idx + len(selected)
|
||||
s_line = full.count('\n', 0, start_idx) + 1
|
||||
e_line = full.count('\n', 0, end_idx) + 1
|
||||
from src.fuzzy_anchor import FuzzyAnchor
|
||||
slice_data = FuzzyAnchor.create_slice(full, s_line, e_line)
|
||||
self.ui_editing_slices_file.custom_slices.append(slice_data)
|
||||
if self._slice_sel_start != -1 and self._slice_sel_end != -1:
|
||||
s_line = min(self._slice_sel_start, self._slice_sel_end)
|
||||
e_line = max(self._slice_sel_start, self._slice_sel_end)
|
||||
from src.fuzzy_anchor import FuzzyAnchor
|
||||
slice_data = FuzzyAnchor.create_slice(self.text_viewer_content, s_line, e_line)
|
||||
slice_data['tag'] = ""
|
||||
slice_data['comment'] = ""
|
||||
self.ui_editing_slices_file.custom_slices.append(slice_data)
|
||||
self._slice_sel_start = -1
|
||||
self._slice_sel_end = -1
|
||||
imgui.same_line()
|
||||
if imgui.button("Clear Selection"):
|
||||
self._slice_sel_start = -1
|
||||
self._slice_sel_end = -1
|
||||
|
||||
# Render existing slices
|
||||
# Render existing slices (Tasks 3.4)
|
||||
to_remove = -1
|
||||
for idx, slc in enumerate(self.ui_editing_slices_file.custom_slices):
|
||||
imgui.text(f"Slice {idx+1}: Lines {slc['start_line']}-{slc['end_line']}")
|
||||
imgui.push_id(f"slc_row_{idx}")
|
||||
imgui.text(f"Slice {idx+1}: {slc['start_line']}-{slc['end_line']}")
|
||||
imgui.same_line()
|
||||
if imgui.button(f"Remove##slc{idx}"):
|
||||
|
||||
imgui.set_next_item_width(100)
|
||||
changed_tag, new_tag = imgui.input_text("Tag", slc.get('tag', ''))
|
||||
if changed_tag: slc['tag'] = new_tag
|
||||
|
||||
imgui.same_line()
|
||||
imgui.set_next_item_width(200)
|
||||
changed_comm, new_comm = imgui.input_text("Comment", slc.get('comment', ''))
|
||||
if changed_comm: slc['comment'] = new_comm
|
||||
|
||||
imgui.same_line()
|
||||
if imgui.button("Remove"):
|
||||
to_remove = idx
|
||||
imgui.pop_id()
|
||||
if to_remove != -1:
|
||||
self.ui_editing_slices_file.custom_slices.pop(to_remove)
|
||||
imgui.separator()
|
||||
@@ -1466,7 +1485,43 @@ class App:
|
||||
imgui.begin_child("tv_md_scroll", imgui.ImVec2(-1, -1), True)
|
||||
markdown_helper.render(self.text_viewer_content, context_id='text_viewer')
|
||||
imgui.end_child()
|
||||
elif tv_type in renderer._lang_map or self.ui_editing_slices_file is not None:
|
||||
elif self.ui_editing_slices_file is not None:
|
||||
# Manual renderer for slice editing (Tasks 3.1, 3.2, 3.3)
|
||||
imgui.begin_child("slice_editor_content", imgui.ImVec2(-1, -1), True)
|
||||
lines = self.text_viewer_content.splitlines()
|
||||
draw_list = imgui.get_window_draw_list()
|
||||
|
||||
for i, line_text in enumerate(lines):
|
||||
line_num = i + 1
|
||||
pos = imgui.get_cursor_screen_pos()
|
||||
line_height = imgui.get_text_line_height()
|
||||
|
||||
# Check if part of any slice (Task 3.2)
|
||||
is_sliced = False
|
||||
for slc in self.ui_editing_slices_file.custom_slices:
|
||||
if slc['start_line'] <= line_num <= slc['end_line']:
|
||||
is_sliced = True
|
||||
break
|
||||
|
||||
if is_sliced:
|
||||
# Orange alpha 0.2
|
||||
draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + imgui.get_content_region_avail().x, pos.y + line_height), imgui.get_color_u32(vec4(255, 165, 0, 0.2)))
|
||||
|
||||
# Selection highlight (Task 3.3)
|
||||
if self._slice_sel_start != -1 and self._slice_sel_end != -1:
|
||||
s = min(self._slice_sel_start, self._slice_sel_end)
|
||||
e = max(self._slice_sel_start, self._slice_sel_end)
|
||||
if s <= line_num <= e:
|
||||
draw_list.add_rect_filled(pos, imgui.ImVec2(pos.x + imgui.get_content_region_avail().x, pos.y + line_height), imgui.get_color_u32(vec4(100, 100, 255, 0.3)))
|
||||
|
||||
imgui.selectable(f"{line_num:4} | {line_text}##ln{line_num}", False)
|
||||
if imgui.is_item_clicked():
|
||||
self._slice_sel_start = line_num
|
||||
self._slice_sel_end = line_num
|
||||
if imgui.is_item_hovered() and imgui.is_mouse_down(0):
|
||||
self._slice_sel_end = line_num
|
||||
imgui.end_child()
|
||||
elif tv_type in renderer._lang_map:
|
||||
if self._text_viewer_editor is None:
|
||||
self._text_viewer_editor = ced.TextEditor()
|
||||
self._text_viewer_editor.set_read_only_enabled(True)
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import pytest
|
||||
from src.models import FileItem
|
||||
from src.fuzzy_anchor import FuzzyAnchor
|
||||
|
||||
def test_add_slice_with_annotations():
|
||||
f_item = FileItem(path="test.py")
|
||||
content = "line1\nline2\nline3\nline4\nline5"
|
||||
|
||||
# Simulate adding a slice from GUI
|
||||
s_line, e_line = 2, 4
|
||||
slice_data = FuzzyAnchor.create_slice(content, s_line, e_line)
|
||||
slice_data['tag'] = "important"
|
||||
slice_data['comment'] = "this is a test"
|
||||
|
||||
f_item.custom_slices.append(slice_data)
|
||||
|
||||
# Verify it's in the list
|
||||
assert len(f_item.custom_slices) == 1
|
||||
assert f_item.custom_slices[0]['tag'] == "important"
|
||||
assert f_item.custom_slices[0]['comment'] == "this is a test"
|
||||
|
||||
# Verify serialization
|
||||
d = f_item.to_dict()
|
||||
assert d['custom_slices'][0]['tag'] == "important"
|
||||
|
||||
# Verify deserialization
|
||||
f_item_2 = FileItem.from_dict(d)
|
||||
assert f_item_2.custom_slices[0]['comment'] == "this is a test"
|
||||
Reference in New Issue
Block a user