riched 1.6.3__tar.gz → 1.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. {riched-1.6.3 → riched-1.7.0}/PKG-INFO +18 -14
  2. {riched-1.6.3 → riched-1.7.0}/README.md +17 -13
  3. {riched-1.6.3 → riched-1.7.0}/pyproject.toml +1 -1
  4. {riched-1.6.3 → riched-1.7.0}/src/rich_editor/app.py +21 -0
  5. {riched-1.6.3 → riched-1.7.0}/src/rich_editor/bindings.yaml +3 -2
  6. {riched-1.6.3 → riched-1.7.0}/src/rich_editor/editor.py +7 -0
  7. {riched-1.6.3 → riched-1.7.0}/tests/test_markdown_preview.py +29 -4
  8. riched-1.7.0/tests/test_select_to_copy.py +128 -0
  9. {riched-1.6.3 → riched-1.7.0}/uv.lock +1 -1
  10. {riched-1.6.3 → riched-1.7.0}/.gitignore +0 -0
  11. {riched-1.6.3 → riched-1.7.0}/.python-version +0 -0
  12. {riched-1.6.3 → riched-1.7.0}/AGENTS.md +0 -0
  13. {riched-1.6.3 → riched-1.7.0}/docs/command-palette.svg +0 -0
  14. {riched-1.6.3 → riched-1.7.0}/docs/markdown-preview.svg +0 -0
  15. {riched-1.6.3 → riched-1.7.0}/docs/python-buffer.svg +0 -0
  16. {riched-1.6.3 → riched-1.7.0}/docs/search-replace.svg +0 -0
  17. {riched-1.6.3 → riched-1.7.0}/plans/esc-unified-sidebar-escape.md +0 -0
  18. {riched-1.6.3 → riched-1.7.0}/src/rich_editor/__init__.py +0 -0
  19. {riched-1.6.3 → riched-1.7.0}/src/rich_editor/cli.py +0 -0
  20. {riched-1.6.3 → riched-1.7.0}/src/rich_editor/formatting.py +0 -0
  21. {riched-1.6.3 → riched-1.7.0}/src/rich_editor/keybindings.py +0 -0
  22. {riched-1.6.3 → riched-1.7.0}/src/rich_editor/quick_open.py +0 -0
  23. {riched-1.6.3 → riched-1.7.0}/src/rich_editor/screens.py +0 -0
  24. {riched-1.6.3 → riched-1.7.0}/src/rich_editor/settings.py +0 -0
  25. {riched-1.6.3 → riched-1.7.0}/src/rich_editor/syntax.py +0 -0
  26. {riched-1.6.3 → riched-1.7.0}/tests/__init__.py +0 -0
  27. {riched-1.6.3 → riched-1.7.0}/tests/helpers.py +0 -0
  28. {riched-1.6.3 → riched-1.7.0}/tests/runner.py +0 -0
  29. {riched-1.6.3 → riched-1.7.0}/tests/test_cli.py +0 -0
  30. {riched-1.6.3 → riched-1.7.0}/tests/test_command_palette.py +0 -0
  31. {riched-1.6.3 → riched-1.7.0}/tests/test_create_file.py +0 -0
  32. {riched-1.6.3 → riched-1.7.0}/tests/test_dirty_buffers.py +0 -0
  33. {riched-1.6.3 → riched-1.7.0}/tests/test_editor_shortcuts.py +0 -0
  34. {riched-1.6.3 → riched-1.7.0}/tests/test_file_io.py +0 -0
  35. {riched-1.6.3 → riched-1.7.0}/tests/test_footer.py +0 -0
  36. {riched-1.6.3 → riched-1.7.0}/tests/test_formatting.py +0 -0
  37. {riched-1.6.3 → riched-1.7.0}/tests/test_quick_open.py +0 -0
  38. {riched-1.6.3 → riched-1.7.0}/tests/test_refresh.py +0 -0
  39. {riched-1.6.3 → riched-1.7.0}/tests/test_replace.py +0 -0
  40. {riched-1.6.3 → riched-1.7.0}/tests/test_sidebar.py +0 -0
  41. {riched-1.6.3 → riched-1.7.0}/tests/test_syntax.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: riched
3
- Version: 1.6.3
3
+ Version: 1.7.0
4
4
  Summary: Terminal Editor with VS Code Keybindings
5
5
  Project-URL: Repository, https://github.com/hoishing/riched
6
6
  Requires-Python: >=3.12
@@ -56,6 +56,7 @@ Terminal Editor with VS Code Keybindings Powered by [Rich](https://github.com/Te
56
56
  - project sidebar with create, rename, quick refresh, and move-to-trash support
57
57
  - quick file open with fuzzy search
58
58
  - in-terminal Markdown preview with table of contents toggle
59
+ - mouse select-to-copy in the source editor and Markdown preview
59
60
  - document formatting through external formatter CLIs
60
61
  - multiple built-in theme(atom, drcula, nordic ...etc)
61
62
  - syntax highlighting for major file formats with manual file type override
@@ -82,17 +83,17 @@ Key bindings are generated from the YAML in `src/rich_editor/bindings.yaml`.
82
83
 
83
84
  App shortcuts:
84
85
 
85
- | Shortcut | Action |
86
- | ------------ | ----------------------- |
87
- | `⌘S` | Save |
88
- | `F1` | Command palette |
89
- | `⌘B` / `⌃B` | Toggle sidebar |
90
- | `⌘P` | Quick open |
91
- | `⌃H` | Replace |
92
- | `⌃N` | Create file |
93
- | `⌃⇧V` | Toggle Markdown preview |
94
- | `⌥⇧F` | Format document |
95
- | `⌘R` | Refresh |
86
+ | Shortcut | Action |
87
+ | ------------- | ----------------------- |
88
+ | `⌘S` | Save |
89
+ | `F1` | Command palette |
90
+ | `⌘B` / `⌃B` | Toggle sidebar |
91
+ | `⌘P` | Quick open |
92
+ | `⌃H` | Replace |
93
+ | `⌃N` | Create file |
94
+ | `⌘⇧V` / `⌃⇧V` | Toggle Markdown preview |
95
+ | `⌥⇧F` | Format document |
96
+ | `⌘R` | Refresh |
96
97
 
97
98
  Editor shortcuts:
98
99
 
@@ -137,7 +138,9 @@ Riched persists the selected Textual theme in `settings.yaml` under the user con
137
138
 
138
139
  ## Markdown Preview
139
140
 
140
- Use `⌃⇧V`, the `Preview` button in the footer, or `F1` → `Toggle Markdown preview` to toggle an in-terminal rendered preview for Markdown files. The preview uses the current editor buffer, so unsaved edits are shown without writing them to disk.
141
+ Use `⌘⇧V` (`⌃⇧V` if Ghostty still owns `⌘⇧V`), the `Preview` button in the footer, or `F1` → `Toggle Markdown preview` to toggle an in-terminal rendered preview for Markdown files. The preview uses the current editor buffer, so unsaved edits are shown without writing them to disk.
142
+
143
+ Drag to select text in the preview to copy the rendered text to the clipboard. The same select-to-copy behavior works in the source editor.
141
144
 
142
145
  When Markdown preview is open, the header shows a `☰` button on the right. Click it to show or hide the Markdown table of contents. The button is hidden while editing the text buffer.
143
146
 
@@ -201,7 +204,7 @@ On stock Ghostty, `⌘⌫` is delivered to terminal apps as `⌃U`, so Riched bi
201
204
 
202
205
  ### Ghostty hotkey conflicts
203
206
 
204
- - some hotkeys are bound by Ghostty by default. eg. `⌘Z`, `⌘⇧Z`, `⌘Enter`, `⌘⇧Enter`, `⌘[`, `⌘]`, `⌘W`, `⌘Q`...etc
207
+ - some hotkeys are bound by Ghostty by default. eg. `⌘Z`, `⌘⇧Z`, `⌘⇧V`, `⌘Enter`, `⌘⇧Enter`, `⌘[`, `⌘]`, `⌘W`, `⌘Q`...etc
205
208
  - Riched detects conflicted shortcuts at startup.
206
209
  - Conflicted shortcuts are hidden from the footer until they are unbound in Ghostty config, unless the command has another available alternative.
207
210
  - The key bindings popup marks conflicted shortcuts with `⚠️` and shows a Ghostty config reminder.
@@ -225,6 +228,7 @@ keybind = super+enter=unbind
225
228
  keybind = super+shift+enter=unbind
226
229
  keybind = super+z=unbind
227
230
  keybind = super+shift+z=unbind
231
+ keybind = super+shift+v=unbind
228
232
  ```
229
233
 
230
234
  Then reload Ghostty config with `⌘⇧,` or restart Ghostty. To inspect Ghostty's defaults before changing them:
@@ -41,6 +41,7 @@ Terminal Editor with VS Code Keybindings Powered by [Rich](https://github.com/Te
41
41
  - project sidebar with create, rename, quick refresh, and move-to-trash support
42
42
  - quick file open with fuzzy search
43
43
  - in-terminal Markdown preview with table of contents toggle
44
+ - mouse select-to-copy in the source editor and Markdown preview
44
45
  - document formatting through external formatter CLIs
45
46
  - multiple built-in theme(atom, drcula, nordic ...etc)
46
47
  - syntax highlighting for major file formats with manual file type override
@@ -67,17 +68,17 @@ Key bindings are generated from the YAML in `src/rich_editor/bindings.yaml`.
67
68
 
68
69
  App shortcuts:
69
70
 
70
- | Shortcut | Action |
71
- | ------------ | ----------------------- |
72
- | `⌘S` | Save |
73
- | `F1` | Command palette |
74
- | `⌘B` / `⌃B` | Toggle sidebar |
75
- | `⌘P` | Quick open |
76
- | `⌃H` | Replace |
77
- | `⌃N` | Create file |
78
- | `⌃⇧V` | Toggle Markdown preview |
79
- | `⌥⇧F` | Format document |
80
- | `⌘R` | Refresh |
71
+ | Shortcut | Action |
72
+ | ------------- | ----------------------- |
73
+ | `⌘S` | Save |
74
+ | `F1` | Command palette |
75
+ | `⌘B` / `⌃B` | Toggle sidebar |
76
+ | `⌘P` | Quick open |
77
+ | `⌃H` | Replace |
78
+ | `⌃N` | Create file |
79
+ | `⌘⇧V` / `⌃⇧V` | Toggle Markdown preview |
80
+ | `⌥⇧F` | Format document |
81
+ | `⌘R` | Refresh |
81
82
 
82
83
  Editor shortcuts:
83
84
 
@@ -122,7 +123,9 @@ Riched persists the selected Textual theme in `settings.yaml` under the user con
122
123
 
123
124
  ## Markdown Preview
124
125
 
125
- Use `⌃⇧V`, the `Preview` button in the footer, or `F1` → `Toggle Markdown preview` to toggle an in-terminal rendered preview for Markdown files. The preview uses the current editor buffer, so unsaved edits are shown without writing them to disk.
126
+ Use `⌘⇧V` (`⌃⇧V` if Ghostty still owns `⌘⇧V`), the `Preview` button in the footer, or `F1` → `Toggle Markdown preview` to toggle an in-terminal rendered preview for Markdown files. The preview uses the current editor buffer, so unsaved edits are shown without writing them to disk.
127
+
128
+ Drag to select text in the preview to copy the rendered text to the clipboard. The same select-to-copy behavior works in the source editor.
126
129
 
127
130
  When Markdown preview is open, the header shows a `☰` button on the right. Click it to show or hide the Markdown table of contents. The button is hidden while editing the text buffer.
128
131
 
@@ -186,7 +189,7 @@ On stock Ghostty, `⌘⌫` is delivered to terminal apps as `⌃U`, so Riched bi
186
189
 
187
190
  ### Ghostty hotkey conflicts
188
191
 
189
- - some hotkeys are bound by Ghostty by default. eg. `⌘Z`, `⌘⇧Z`, `⌘Enter`, `⌘⇧Enter`, `⌘[`, `⌘]`, `⌘W`, `⌘Q`...etc
192
+ - some hotkeys are bound by Ghostty by default. eg. `⌘Z`, `⌘⇧Z`, `⌘⇧V`, `⌘Enter`, `⌘⇧Enter`, `⌘[`, `⌘]`, `⌘W`, `⌘Q`...etc
190
193
  - Riched detects conflicted shortcuts at startup.
191
194
  - Conflicted shortcuts are hidden from the footer until they are unbound in Ghostty config, unless the command has another available alternative.
192
195
  - The key bindings popup marks conflicted shortcuts with `⚠️` and shows a Ghostty config reminder.
@@ -210,6 +213,7 @@ keybind = super+enter=unbind
210
213
  keybind = super+shift+enter=unbind
211
214
  keybind = super+z=unbind
212
215
  keybind = super+shift+z=unbind
216
+ keybind = super+shift+v=unbind
213
217
  ```
214
218
 
215
219
  Then reload Ghostty config with `⌘⇧,` or restart Ghostty. To inspect Ghostty's defaults before changing them:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "riched"
3
- version = "1.6.3"
3
+ version = "1.7.0"
4
4
  description = "Terminal Editor with VS Code Keybindings"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -669,6 +669,27 @@ class RichedApp(App):
669
669
  previews = list(self.query("#markdown-preview"))
670
670
  return previews[0] if previews else None
671
671
 
672
+ def _copy_preview_selection(self) -> None:
673
+ preview = self._markdown_preview_or_none()
674
+ if preview is None:
675
+ return
676
+ selected = self.screen.get_selected_text()
677
+ if not selected:
678
+ return
679
+ in_preview = any(
680
+ preview in widget.ancestors_with_self for widget in self.screen.selections
681
+ )
682
+ if not in_preview:
683
+ return
684
+ self.copy_to_clipboard(selected)
685
+
686
+ def on_text_selected(self, event: events.TextSelected) -> None:
687
+ self._copy_preview_selection()
688
+
689
+ def on_click(self, event: events.Click) -> None:
690
+ if event.chain >= 2:
691
+ self._copy_preview_selection()
692
+
672
693
  def _sidebar(self) -> DirectoryTree:
673
694
  return self.query_one("#sidebar", DirectoryTree)
674
695
 
@@ -33,9 +33,10 @@ app:
33
33
  preferred_key: ctrl+n
34
34
  - name: toggle_markdown_preview
35
35
  description: Toggle Markdown preview
36
- key: ctrl+shift+v,shift+ctrl+v,ctrl+V,alt+shift+v,shift+alt+v,alt+shift+V,shift+alt+V,alt+v
37
- preferred_key: ctrl+shift+v
36
+ key: cmd+shift+v,shift+cmd+v,super+shift+v,shift+super+v,cmd+V,shift+cmd+V,super+V,shift+super+V,cmd+shift+V,super+shift+V,ctrl+shift+v,shift+ctrl+v,ctrl+V,alt+shift+v,shift+alt+v,alt+shift+V,shift+alt+V,alt+v
37
+ preferred_key: cmd+shift+v,shift+cmd+v,super+shift+v,shift+super+v
38
38
  display_keys:
39
+ - cmd+shift+v
39
40
  - ctrl+shift+v
40
41
  - name: format_document
41
42
  description: Format document
@@ -53,8 +53,15 @@ class _FormattingApp(Protocol):
53
53
  class RichedTextArea(TextArea):
54
54
  """TextArea with VS Code-style line-edit shortcuts."""
55
55
 
56
+ ALLOW_SELECT = False
56
57
  BINDINGS = build_static_bindings("editor")
57
58
 
59
+ def _end_mouse_selection(self) -> None:
60
+ was_selecting = self._selecting
61
+ super()._end_mouse_selection()
62
+ if was_selecting and self.selected_text:
63
+ self.app.copy_to_clipboard(self.selected_text)
64
+
58
65
  async def _on_key(self, event: events.Key) -> None:
59
66
  if event.key == "escape":
60
67
  from .app import RichedApp
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from textual.widgets import DirectoryTree, Markdown, MarkdownViewer, Static
4
4
 
5
- from .helpers import _editor, _file_app, _key_help_rows, _press
5
+ from .helpers import _directory_app, _editor, _file_app, _key_help_rows, _press
6
6
 
7
7
 
8
8
  async def test_markdown_preview_toggles_from_primary_key() -> None:
@@ -27,8 +27,19 @@ async def test_markdown_preview_toggles_from_primary_key() -> None:
27
27
 
28
28
 
29
29
  async def test_markdown_preview_opens_from_alias_keys() -> None:
30
- for key, name in (("cmd+shift+v", "README.md"), ("super+shift+v", "README.markdown")):
31
- _, _, app = _file_app(name, "# Title", root_is_tmp=True, edit_mode=True)
30
+ keys = (
31
+ "cmd+shift+v",
32
+ "shift+cmd+v",
33
+ "super+shift+v",
34
+ "shift+super+v",
35
+ "cmd+V",
36
+ "super+V",
37
+ "ctrl+shift+v",
38
+ "shift+ctrl+v",
39
+ "ctrl+V",
40
+ )
41
+ for key in keys:
42
+ _, _, app = _file_app("README.md", "# Title", root_is_tmp=True, edit_mode=True)
32
43
  async with app.run_test() as pilot:
33
44
  await pilot.pause()
34
45
  await _press(pilot, key)
@@ -85,6 +96,20 @@ async def test_markdown_preview_toc_button_only_shows_for_preview() -> None:
85
96
  assert toc_button.styles.display == "none"
86
97
 
87
98
 
99
+ async def test_markdown_preview_hotkeys_warn_without_open_buffer() -> None:
100
+ for key in ("cmd+shift+v", "super+shift+v", "ctrl+shift+v"):
101
+ _, app = _directory_app()
102
+ notifications: list[tuple[str, str | None]] = []
103
+ app.notify = lambda message, **kwargs: notifications.append(
104
+ (str(message), kwargs.get("severity"))
105
+ )
106
+ async with app.run_test() as pilot:
107
+ await pilot.pause()
108
+ await _press(pilot, key)
109
+ assert not list(app.query("#markdown-preview")), key
110
+ assert notifications == [("No file open", "warning")], key
111
+
112
+
88
113
  async def test_cmd_shift_v_warns_for_non_markdown_file() -> None:
89
114
  _, _, app = _file_app("notes.txt", "# Plain text", root_is_tmp=True)
90
115
  notifications: list[tuple[str, str | None]] = []
@@ -205,5 +230,5 @@ async def test_keys_help_warns_for_ghostty_conflicted_markdown_preview() -> None
205
230
  assert ("⚠️ ⌘⇧V / ⌃⇧V", "Toggle Markdown preview") in rows
206
231
  assert (
207
232
  app.screen.query_one(".binding-legend", Static).content
208
- == "⚠️ Unbind this shortcut in Ghostty config to use it in Riched."
233
+ == "⚠️ Unbind this shortcut in your terminal config to use it in Riched."
209
234
  )
@@ -0,0 +1,128 @@
1
+ from __future__ import annotations
2
+
3
+ from textual.widgets import MarkdownViewer, TextArea
4
+ from textual.widgets.markdown import MarkdownBlock
5
+
6
+ from .helpers import _editor, _file_app, _press
7
+
8
+ SENTINEL = "__clipboard_sentinel__"
9
+
10
+
11
+ def _editor_content_offset(
12
+ editor: TextArea, column: int, row: int = 0
13
+ ) -> tuple[int, int]:
14
+ return (
15
+ editor.gutter.left + editor.gutter_width + column,
16
+ editor.gutter.top + row,
17
+ )
18
+
19
+
20
+ async def _drag(pilot, widget, start: tuple[int, int], end: tuple[int, int]) -> None:
21
+ await pilot.mouse_down(widget, offset=start)
22
+ await pilot.pause()
23
+ await pilot.hover(widget, offset=end)
24
+ await pilot.pause()
25
+ await pilot.mouse_up(widget, offset=end)
26
+ await pilot.pause()
27
+
28
+
29
+ async def test_editor_mouse_select_copies_source_text() -> None:
30
+ _, _, app = _file_app("hello.txt", "hello world")
31
+ async with app.run_test() as pilot:
32
+ await pilot.pause()
33
+ editor = _editor(app)
34
+ app.copy_to_clipboard(SENTINEL)
35
+
36
+ await _drag(
37
+ pilot,
38
+ editor,
39
+ _editor_content_offset(editor, 0),
40
+ _editor_content_offset(editor, 5),
41
+ )
42
+
43
+ assert editor.selected_text == "hello", repr(editor.selected_text)
44
+ assert app.clipboard == "hello", repr(app.clipboard)
45
+ assert "1" not in app.clipboard
46
+
47
+
48
+ async def test_editor_click_without_drag_does_not_copy() -> None:
49
+ _, _, app = _file_app("hello.txt", "hello world")
50
+ async with app.run_test() as pilot:
51
+ await pilot.pause()
52
+ editor = _editor(app)
53
+ app.copy_to_clipboard(SENTINEL)
54
+ offset = _editor_content_offset(editor, 0)
55
+
56
+ await pilot.mouse_down(editor, offset=offset)
57
+ await pilot.pause()
58
+ await pilot.mouse_up(editor, offset=offset)
59
+ await pilot.pause()
60
+
61
+ assert editor.selection.is_empty
62
+ assert app.clipboard == SENTINEL, repr(app.clipboard)
63
+
64
+
65
+ async def test_editor_keyboard_selection_does_not_copy() -> None:
66
+ _, _, app = _file_app("hello.txt", "hello world")
67
+ async with app.run_test() as pilot:
68
+ await pilot.pause()
69
+ editor = _editor(app)
70
+ app.copy_to_clipboard(SENTINEL)
71
+ editor.move_cursor((0, 0))
72
+ await pilot.pause()
73
+
74
+ await _press(pilot, "shift+right")
75
+
76
+ assert editor.selected_text == "h", repr(editor.selected_text)
77
+ assert app.clipboard == SENTINEL, repr(app.clipboard)
78
+
79
+
80
+ async def test_markdown_preview_mouse_select_copies_rendered_text() -> None:
81
+ _, _, app = _file_app(
82
+ "README.md",
83
+ "# Heading\n\ncopy-me-please\n",
84
+ root_is_tmp=True,
85
+ edit_mode=True,
86
+ )
87
+ async with app.run_test() as pilot:
88
+ await pilot.pause()
89
+ await _press(pilot, "cmd+shift+v")
90
+ preview = app.query_one("#markdown-preview", MarkdownViewer)
91
+ paragraph = next(
92
+ block
93
+ for block in preview.document.query(MarkdownBlock)
94
+ if (block.source or "").find("copy-me-please") >= 0
95
+ )
96
+ app.copy_to_clipboard(SENTINEL)
97
+
98
+ await _drag(pilot, paragraph, (0, 0), (max(1, paragraph.size.width - 1), 0))
99
+
100
+ assert "copy-me-please" in app.clipboard, repr(app.clipboard)
101
+ assert "# Heading" not in app.clipboard
102
+ assert SENTINEL not in app.clipboard
103
+
104
+
105
+ async def test_markdown_preview_click_without_drag_does_not_copy() -> None:
106
+ _, _, app = _file_app(
107
+ "README.md",
108
+ "# Heading\n\ncopy-me-please\n",
109
+ root_is_tmp=True,
110
+ edit_mode=True,
111
+ )
112
+ async with app.run_test() as pilot:
113
+ await pilot.pause()
114
+ await _press(pilot, "cmd+shift+v")
115
+ preview = app.query_one("#markdown-preview", MarkdownViewer)
116
+ paragraph = next(
117
+ block
118
+ for block in preview.document.query(MarkdownBlock)
119
+ if (block.source or "").find("copy-me-please") >= 0
120
+ )
121
+ app.copy_to_clipboard(SENTINEL)
122
+
123
+ await pilot.mouse_down(paragraph, offset=(0, 0))
124
+ await pilot.pause()
125
+ await pilot.mouse_up(paragraph, offset=(0, 0))
126
+ await pilot.pause()
127
+
128
+ assert app.clipboard == SENTINEL, repr(app.clipboard)
@@ -187,7 +187,7 @@ wheels = [
187
187
 
188
188
  [[package]]
189
189
  name = "riched"
190
- version = "1.6.2"
190
+ version = "1.7.0"
191
191
  source = { editable = "." }
192
192
  dependencies = [
193
193
  { name = "pyyaml" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes