diff --git a/src/gui_2.py b/src/gui_2.py index ab9417b..2422f0a 100644 --- a/src/gui_2.py +++ b/src/gui_2.py @@ -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) diff --git a/tests/test_slice_editor_behavior.py b/tests/test_slice_editor_behavior.py new file mode 100644 index 0000000..9418e04 --- /dev/null +++ b/tests/test_slice_editor_behavior.py @@ -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"