@adecore/editor-react 0.0.1 → 0.17.0-beta.2
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.
- package/LICENSE +110 -0
- package/README.md +60 -0
- package/dist/AnchoredPopup.d.ts +12 -0
- package/dist/AnchoredPopup.js +27 -0
- package/dist/AttributionCard.d.ts +14 -0
- package/dist/AttributionCard.js +5 -0
- package/dist/BaselineStatus.d.ts +4 -0
- package/dist/BaselineStatus.js +13 -0
- package/dist/ChangeReview.d.ts +12 -0
- package/dist/ChangeReview.js +27 -0
- package/dist/CodeAuthorsCard.d.ts +6 -0
- package/dist/CodeAuthorsCard.js +22 -0
- package/dist/CompletionPopup.d.ts +8 -0
- package/dist/CompletionPopup.js +103 -0
- package/dist/EditorContextMenu.d.ts +8 -0
- package/dist/EditorContextMenu.js +23 -0
- package/dist/EditorView.d.ts +16 -0
- package/dist/EditorView.js +27 -0
- package/dist/FindReplace.d.ts +6 -0
- package/dist/FindReplace.js +44 -0
- package/dist/HoverCard.d.ts +11 -0
- package/dist/HoverCard.js +38 -0
- package/dist/HoverSections.d.ts +21 -0
- package/dist/HoverSections.js +82 -0
- package/dist/LanguagePopups.d.ts +6 -0
- package/dist/LanguagePopups.js +38 -0
- package/dist/Markdown.d.ts +3 -0
- package/dist/Markdown.js +7 -0
- package/dist/PathText.d.ts +4 -0
- package/dist/PathText.js +12 -0
- package/dist/PeekPanel.d.ts +6 -0
- package/dist/PeekPanel.js +111 -0
- package/dist/PickPopup.d.ts +8 -0
- package/dist/PickPopup.js +33 -0
- package/dist/RenameCard.d.ts +9 -0
- package/dist/RenameCard.js +108 -0
- package/dist/SignatureCard.d.ts +4 -0
- package/dist/SignatureCard.js +8 -0
- package/dist/SymbolPicker.d.ts +6 -0
- package/dist/SymbolPicker.js +108 -0
- package/dist/code-actions-model.d.ts +23 -0
- package/dist/code-actions-model.js +114 -0
- package/dist/code-actions.d.ts +30 -0
- package/dist/code-actions.js +326 -0
- package/dist/code-authors.d.ts +18 -0
- package/dist/code-authors.js +79 -0
- package/dist/code-vision-model.d.ts +21 -0
- package/dist/code-vision-model.js +183 -0
- package/dist/code-vision.d.ts +48 -0
- package/dist/code-vision.js +236 -0
- package/dist/completion-call.d.ts +24 -0
- package/dist/completion-call.js +110 -0
- package/dist/completion-model.d.ts +51 -0
- package/dist/completion-model.js +353 -0
- package/dist/completion.d.ts +43 -0
- package/dist/completion.js +463 -0
- package/dist/conflict-model.d.ts +15 -0
- package/dist/conflict-model.js +36 -0
- package/dist/context-menu.d.ts +10 -0
- package/dist/context-menu.js +55 -0
- package/dist/definition-link.d.ts +14 -0
- package/dist/definition-link.js +99 -0
- package/dist/diagnostics-model.d.ts +15 -0
- package/dist/diagnostics-model.js +83 -0
- package/dist/diagnostics.d.ts +23 -0
- package/dist/diagnostics.js +95 -0
- package/dist/editor-language.d.ts +65 -0
- package/dist/editor-language.js +170 -0
- package/dist/editor-react.css +43 -0
- package/dist/find-query.d.ts +26 -0
- package/dist/find-query.js +51 -0
- package/dist/folding.d.ts +16 -0
- package/dist/folding.js +95 -0
- package/dist/highlight-layers.d.ts +10 -0
- package/dist/highlight-layers.js +30 -0
- package/dist/highlights.d.ts +16 -0
- package/dist/highlights.js +102 -0
- package/dist/history.d.ts +8 -0
- package/dist/history.js +46 -0
- package/dist/holder.d.ts +5 -0
- package/dist/holder.js +19 -0
- package/dist/host-types.d.ts +45 -0
- package/dist/host-types.js +1 -0
- package/dist/hover-content.d.ts +45 -0
- package/dist/hover-content.js +198 -0
- package/dist/hover.d.ts +32 -0
- package/dist/hover.js +260 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +28 -0
- package/dist/inlay-hints.d.ts +13 -0
- package/dist/inlay-hints.js +71 -0
- package/dist/language-ids.d.ts +3 -0
- package/dist/language-ids.js +49 -0
- package/dist/line-edits.d.ts +8 -0
- package/dist/line-edits.js +39 -0
- package/dist/locales/en.json +171 -0
- package/dist/locales/nl.json +171 -0
- package/dist/models.d.ts +20 -0
- package/dist/models.js +20 -0
- package/dist/navigation-history.d.ts +16 -0
- package/dist/navigation-history.js +53 -0
- package/dist/navigation.d.ts +23 -0
- package/dist/navigation.js +155 -0
- package/dist/paths.d.ts +1 -0
- package/dist/paths.js +3 -0
- package/dist/peek-model.d.ts +28 -0
- package/dist/peek-model.js +104 -0
- package/dist/peek.d.ts +30 -0
- package/dist/peek.js +215 -0
- package/dist/pick.d.ts +34 -0
- package/dist/pick.js +166 -0
- package/dist/popup-placement.d.ts +23 -0
- package/dist/popup-placement.js +34 -0
- package/dist/popups.d.ts +133 -0
- package/dist/popups.js +14 -0
- package/dist/project-language.d.ts +36 -0
- package/dist/project-language.js +190 -0
- package/dist/project-problems.d.ts +24 -0
- package/dist/project-problems.js +63 -0
- package/dist/proposal-model.d.ts +36 -0
- package/dist/proposal-model.js +197 -0
- package/dist/recent-choices.d.ts +10 -0
- package/dist/recent-choices.js +39 -0
- package/dist/refresher.d.ts +13 -0
- package/dist/refresher.js +50 -0
- package/dist/relative-time.d.ts +1 -0
- package/dist/relative-time.js +9 -0
- package/dist/rename-model.d.ts +25 -0
- package/dist/rename-model.js +93 -0
- package/dist/rename.d.ts +21 -0
- package/dist/rename.js +257 -0
- package/dist/rendering-context.d.ts +6 -0
- package/dist/rendering-context.js +5 -0
- package/dist/rendering.d.ts +6 -0
- package/dist/rendering.js +5 -0
- package/dist/review-group.d.ts +16 -0
- package/dist/review-group.js +54 -0
- package/dist/row-host.d.ts +42 -0
- package/dist/row-host.js +120 -0
- package/dist/selection-ranges.d.ts +7 -0
- package/dist/selection-ranges.js +26 -0
- package/dist/semantic-model.d.ts +4 -0
- package/dist/semantic-model.js +76 -0
- package/dist/semantic-tokens.d.ts +6 -0
- package/dist/semantic-tokens.js +38 -0
- package/dist/shortcut-keys.d.ts +91 -0
- package/dist/shortcut-keys.js +12 -0
- package/dist/signature-model.d.ts +18 -0
- package/dist/signature-model.js +33 -0
- package/dist/signature.d.ts +22 -0
- package/dist/signature.js +150 -0
- package/dist/snippet-session.d.ts +17 -0
- package/dist/snippet-session.js +110 -0
- package/dist/snippet.d.ts +11 -0
- package/dist/snippet.js +134 -0
- package/dist/symbol-links.d.ts +6 -0
- package/dist/symbol-links.js +50 -0
- package/dist/symbol-picker-model.d.ts +44 -0
- package/dist/symbol-picker-model.js +146 -0
- package/dist/symbol-picker.d.ts +12 -0
- package/dist/symbol-picker.js +76 -0
- package/dist/symbols.d.ts +11 -0
- package/dist/symbols.js +95 -0
- package/dist/testing.d.ts +57 -0
- package/dist/testing.js +156 -0
- package/dist/timers.d.ts +14 -0
- package/dist/timers.js +34 -0
- package/dist/use-problem-counts.d.ts +3 -0
- package/dist/use-problem-counts.js +20 -0
- package/dist/view-state.d.ts +21 -0
- package/dist/view-state.js +63 -0
- package/dist/word-diff.d.ts +1 -0
- package/dist/word-diff.js +32 -0
- package/dist/workspace-edit.d.ts +21 -0
- package/dist/workspace-edit.js +147 -0
- package/package.json +74 -2
- package/src/AnchoredPopup.tsx +51 -0
- package/src/AttributionCard.tsx +36 -0
- package/src/BaselineStatus.tsx +20 -0
- package/src/ChangeReview.tsx +43 -0
- package/src/CodeAuthorsCard.tsx +81 -0
- package/src/CompletionPopup.tsx +196 -0
- package/src/EditorContextMenu.tsx +67 -0
- package/src/EditorView.tsx +47 -0
- package/src/FindReplace.tsx +89 -0
- package/src/HoverCard.tsx +121 -0
- package/src/HoverSections.tsx +146 -0
- package/src/LanguagePopups.tsx +65 -0
- package/src/Markdown.tsx +11 -0
- package/src/PathText.tsx +18 -0
- package/src/PeekPanel.tsx +222 -0
- package/src/PickPopup.tsx +102 -0
- package/src/RenameCard.tsx +219 -0
- package/src/SignatureCard.tsx +42 -0
- package/src/SymbolPicker.tsx +219 -0
- package/src/code-actions-model.ts +146 -0
- package/src/code-actions.ts +384 -0
- package/src/code-authors.ts +104 -0
- package/src/code-vision-model.ts +230 -0
- package/src/code-vision.ts +269 -0
- package/src/completion-call.ts +139 -0
- package/src/completion-model.ts +440 -0
- package/src/completion.ts +500 -0
- package/src/conflict-model.ts +56 -0
- package/src/context-menu.ts +63 -0
- package/src/definition-link.ts +106 -0
- package/src/diagnostics-model.ts +102 -0
- package/src/diagnostics.ts +117 -0
- package/src/editor-language.ts +189 -0
- package/src/editor-react.css +43 -0
- package/src/find-query.ts +74 -0
- package/src/folding.ts +110 -0
- package/src/highlight-layers.ts +37 -0
- package/src/highlights.ts +123 -0
- package/src/history.ts +56 -0
- package/src/holder.ts +19 -0
- package/src/host-types.ts +52 -0
- package/src/hover-content.ts +264 -0
- package/src/hover.ts +280 -0
- package/src/index.ts +63 -0
- package/src/inlay-hints.ts +85 -0
- package/src/language-ids.ts +54 -0
- package/src/line-edits.ts +48 -0
- package/src/locales/en.json +171 -0
- package/src/locales/nl.json +171 -0
- package/src/models.ts +157 -0
- package/src/navigation-history.ts +69 -0
- package/src/navigation.ts +168 -0
- package/src/paths.ts +3 -0
- package/src/peek-model.ts +147 -0
- package/src/peek.ts +247 -0
- package/src/pick.ts +197 -0
- package/src/popup-placement.ts +64 -0
- package/src/popups.ts +204 -0
- package/src/project-language.ts +223 -0
- package/src/project-problems.ts +87 -0
- package/src/proposal-model.ts +259 -0
- package/src/recent-choices.ts +48 -0
- package/src/refresher.ts +56 -0
- package/src/relative-time.ts +10 -0
- package/src/rename-model.ts +130 -0
- package/src/rename.ts +290 -0
- package/src/rendering-context.ts +12 -0
- package/src/rendering.tsx +6 -0
- package/src/review-group.ts +70 -0
- package/src/row-host.ts +158 -0
- package/src/selection-ranges.ts +32 -0
- package/src/semantic-model.ts +80 -0
- package/src/semantic-tokens.ts +46 -0
- package/src/shortcut-keys.ts +21 -0
- package/src/signature-model.ts +49 -0
- package/src/signature.ts +164 -0
- package/src/snippet-session.ts +122 -0
- package/src/snippet.ts +147 -0
- package/src/symbol-links.ts +72 -0
- package/src/symbol-picker-model.ts +198 -0
- package/src/symbol-picker.ts +85 -0
- package/src/symbols.ts +110 -0
- package/src/testing.ts +240 -0
- package/src/timers.ts +45 -0
- package/src/use-problem-counts.ts +25 -0
- package/src/view-state.ts +88 -0
- package/src/word-diff.ts +35 -0
- package/src/workspace-edit.ts +194 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Functional Source License, Version 1.1, MIT Future License
|
|
2
|
+
|
|
3
|
+
## Abbreviation
|
|
4
|
+
|
|
5
|
+
FSL-1.1-MIT
|
|
6
|
+
|
|
7
|
+
## Notice
|
|
8
|
+
|
|
9
|
+
Copyright 2026 - present Bas Milius
|
|
10
|
+
|
|
11
|
+
## Terms and Conditions
|
|
12
|
+
|
|
13
|
+
### Licensor ("We")
|
|
14
|
+
|
|
15
|
+
The party offering the Software under these Terms and Conditions.
|
|
16
|
+
|
|
17
|
+
### The Software
|
|
18
|
+
|
|
19
|
+
The "Software" is each version of the software that we make available under
|
|
20
|
+
these Terms and Conditions, as indicated by our inclusion of these Terms and
|
|
21
|
+
Conditions with the Software.
|
|
22
|
+
|
|
23
|
+
### License Grant
|
|
24
|
+
|
|
25
|
+
Subject to your compliance with this License Grant and the Patents,
|
|
26
|
+
Redistribution and Trademark clauses below, we hereby grant you the right to
|
|
27
|
+
use, copy, modify, create derivative works, publicly perform, publicly display
|
|
28
|
+
and redistribute the Software for any Permitted Purpose identified below.
|
|
29
|
+
|
|
30
|
+
### Permitted Purpose
|
|
31
|
+
|
|
32
|
+
A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
|
|
33
|
+
means making the Software available to others in a commercial product or
|
|
34
|
+
service that:
|
|
35
|
+
|
|
36
|
+
1. substitutes for the Software;
|
|
37
|
+
|
|
38
|
+
2. substitutes for any other product or service we offer using the Software
|
|
39
|
+
that exists as of the date we make the Software available; or
|
|
40
|
+
|
|
41
|
+
3. offers the same or substantially similar functionality as the Software.
|
|
42
|
+
|
|
43
|
+
Permitted Purposes specifically include using the Software:
|
|
44
|
+
|
|
45
|
+
1. for your internal use and access;
|
|
46
|
+
|
|
47
|
+
2. for non-commercial education;
|
|
48
|
+
|
|
49
|
+
3. for non-commercial research; and
|
|
50
|
+
|
|
51
|
+
4. in connection with professional services that you provide to a licensee
|
|
52
|
+
using the Software in accordance with these Terms and Conditions.
|
|
53
|
+
|
|
54
|
+
### Patents
|
|
55
|
+
|
|
56
|
+
To the extent your use for a Permitted Purpose would necessarily infringe our
|
|
57
|
+
patents, the license grant above includes a license under our patents. If you
|
|
58
|
+
make a claim against any party that the Software infringes or contributes to
|
|
59
|
+
the infringement of any patent, then your patent license to the Software ends
|
|
60
|
+
immediately.
|
|
61
|
+
|
|
62
|
+
### Redistribution
|
|
63
|
+
|
|
64
|
+
The Terms and Conditions apply to all copies, modifications and derivatives of
|
|
65
|
+
the Software.
|
|
66
|
+
|
|
67
|
+
If you redistribute any copies, modifications or derivatives of the Software,
|
|
68
|
+
you must include a copy of or a link to these Terms and Conditions and not
|
|
69
|
+
remove any copyright notices provided in or with the Software.
|
|
70
|
+
|
|
71
|
+
### Disclaimer
|
|
72
|
+
|
|
73
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
|
|
74
|
+
IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
|
|
75
|
+
PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
|
|
76
|
+
|
|
77
|
+
IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
|
|
78
|
+
SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
|
|
79
|
+
EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
|
|
80
|
+
|
|
81
|
+
### Trademarks
|
|
82
|
+
|
|
83
|
+
Except for displaying the License Details and identifying us as the origin of
|
|
84
|
+
the Software, you have no right under these Terms and Conditions to use our
|
|
85
|
+
trademarks, trade names, service marks or product names.
|
|
86
|
+
|
|
87
|
+
## Grant of Future License
|
|
88
|
+
|
|
89
|
+
We hereby irrevocably grant you an additional license to use the Software under
|
|
90
|
+
the MIT license that is effective on the second anniversary of the date we make
|
|
91
|
+
the Software available. On or after that date, you may use the Software under
|
|
92
|
+
the MIT license, in which case the following will apply:
|
|
93
|
+
|
|
94
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
95
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
96
|
+
the Software without restriction, including without limitation the rights to
|
|
97
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
98
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
|
99
|
+
so, subject to the following conditions:
|
|
100
|
+
|
|
101
|
+
The above copyright notice and this permission notice shall be included in all
|
|
102
|
+
copies or substantial portions of the Software.
|
|
103
|
+
|
|
104
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
105
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
106
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
107
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
108
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
109
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
110
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# @adecore/editor-react
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@adecore/editor-react)
|
|
4
|
+
[](https://adecore.dev/editor-react/)
|
|
5
|
+
|
|
6
|
+
Language features for [`@adecore/editor`](https://adecore.dev/editor/) over a `LanguageService` of [`@adecore/lsp`](https://adecore.dev/lsp/): suggestions and snippets, hover, signature help, problems, navigation, peek, rename, code actions, symbols, semantic colors, inlay hints, folds and code vision, with the React cards that show them. It starts no language server and touches no file; the app hands it a service and the file operations it allows.
|
|
7
|
+
|
|
8
|
+
**[Documentation with live demos](https://adecore.dev/editor-react/)**
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
bun add @adecore/editor-react
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`@adecore/ui`, React 19, `react-dom`, `i18next` and `react-i18next` are peer dependencies.
|
|
17
|
+
|
|
18
|
+
## Set up
|
|
19
|
+
|
|
20
|
+
```css
|
|
21
|
+
@import '@adecore/ui/theme.css';
|
|
22
|
+
@import '@adecore/editor/editor.css';
|
|
23
|
+
@import '@adecore/editor-react/editor-react.css';
|
|
24
|
+
@source "../node_modules/@adecore/editor-react/dist";
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Add `@adecore/editor-react/locales/en.json` and `nl.json` to i18next as the `editor` namespace, then:
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { EditorView, ProjectLanguage } from '@adecore/editor-react';
|
|
31
|
+
|
|
32
|
+
const project = new ProjectLanguage(service, { folder, keymap, openPlace, files, notify });
|
|
33
|
+
|
|
34
|
+
<EditorView engine={engine} options={options} project={project} uri={uri} languageId="typescript" className="h-96" />;
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Entry points
|
|
38
|
+
|
|
39
|
+
| Import | What it holds |
|
|
40
|
+
|---|---|
|
|
41
|
+
| `@adecore/editor-react` | The components, `ProjectLanguage`, `EditorLanguage` and every model |
|
|
42
|
+
| `@adecore/editor-react/models` | The pure models, without React |
|
|
43
|
+
| `@adecore/editor-react/testing` | `FakeLanguageService` and `ManualTimers` |
|
|
44
|
+
| `@adecore/editor-react/editor-react.css` | The styles of the cards |
|
|
45
|
+
| `@adecore/editor-react/locales/*.json` | The words, in English and Dutch |
|
|
46
|
+
|
|
47
|
+
## Documentation
|
|
48
|
+
|
|
49
|
+
| Page | What it covers |
|
|
50
|
+
|---|---|
|
|
51
|
+
| [Getting started](https://adecore.dev/editor-react/getting-started) | Install, styles, words and a first editor |
|
|
52
|
+
| [Projects and documents](https://adecore.dev/editor-react/project) | `ProjectLanguage`, the host, shared documents and workspace edits |
|
|
53
|
+
| [EditorView](https://adecore.dev/editor-react/editor-view) | The component, `EditorLanguage` and the popups |
|
|
54
|
+
| [Views](https://adecore.dev/editor-react/completion) | A page per feature with a live demo, in the sidebar |
|
|
55
|
+
| [Models](https://adecore.dev/editor-react/models) | The pure functions behind the features |
|
|
56
|
+
| [Testing](https://adecore.dev/editor-react/testing) | `FakeLanguageService` and `ManualTimers` |
|
|
57
|
+
|
|
58
|
+
## License
|
|
59
|
+
|
|
60
|
+
FSL-1.1-MIT
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { EditorRect } from '@adecore/editor';
|
|
3
|
+
import { type PlaceOptions } from './popup-placement.ts';
|
|
4
|
+
export interface AnchoredPopupProps {
|
|
5
|
+
rect: EditorRect;
|
|
6
|
+
className?: string;
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
placement?: PlaceOptions;
|
|
9
|
+
onPointerEnter?(): void;
|
|
10
|
+
onPointerLeave?(): void;
|
|
11
|
+
}
|
|
12
|
+
export declare function AnchoredPopup({ rect, className, children, placement, onPointerEnter, onPointerLeave }: AnchoredPopupProps): import("react").ReactPortal;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useLayoutEffect, useRef } from 'react';
|
|
3
|
+
import { createPortal } from 'react-dom';
|
|
4
|
+
import { placePopup } from './popup-placement.js';
|
|
5
|
+
/*
|
|
6
|
+
* A card next to a character of the editor, in a layer of the page itself: a canvas puts a scale on its
|
|
7
|
+
* nodes, and a card inside one would be drawn at that scale and cut off by the node's edge. It is
|
|
8
|
+
* placed from the character's screen rectangle, so it is right at any zoom, and it never leaves the window.
|
|
9
|
+
*/
|
|
10
|
+
export function AnchoredPopup({ rect, className, children, placement, onPointerEnter, onPointerLeave }) {
|
|
11
|
+
const element = useRef(null);
|
|
12
|
+
// Every render, since the character moves when the editor scrolls and the card's own size follows its content.
|
|
13
|
+
useLayoutEffect(() => {
|
|
14
|
+
const popup = element.current;
|
|
15
|
+
if (popup === null) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
popup.style.maxHeight = '';
|
|
19
|
+
const size = { width: popup.offsetWidth, height: popup.offsetHeight };
|
|
20
|
+
const placed = placePopup(rect, size, { width: window.innerWidth, height: window.innerHeight }, placement);
|
|
21
|
+
popup.style.left = `${Math.round(placed.left)}px`;
|
|
22
|
+
popup.style.top = `${Math.round(placed.top)}px`;
|
|
23
|
+
popup.style.maxHeight = `${Math.floor(placed.maxHeight)}px`;
|
|
24
|
+
popup.style.visibility = 'visible';
|
|
25
|
+
});
|
|
26
|
+
return createPortal(_jsx("div", { ref: element, className: `fixed top-0 left-0 z-(--z-popup) overflow-y-auto rounded-lg border border-border bg-surface-raised text-text shadow-(--float-shadow) ${className ?? ''}`, style: { visibility: 'hidden' }, onPointerEnter: onPointerEnter, onPointerLeave: onPointerLeave, children: children }), document.body);
|
|
27
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { EditorMarkColor, EditorRect } from '@adecore/editor';
|
|
3
|
+
export interface AttributionCardProps {
|
|
4
|
+
rect: EditorRect;
|
|
5
|
+
title: string;
|
|
6
|
+
subtitle?: string;
|
|
7
|
+
metadata?: ReactNode;
|
|
8
|
+
prompt?: string;
|
|
9
|
+
color?: EditorMarkColor;
|
|
10
|
+
icon?: ReactNode;
|
|
11
|
+
actions?: ReactNode;
|
|
12
|
+
onHold?(inside: boolean): void;
|
|
13
|
+
}
|
|
14
|
+
export declare function AttributionCard({ rect, title, subtitle, metadata, prompt, color, icon, actions, onHold }: AttributionCardProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { AnchoredPopup } from './AnchoredPopup.js';
|
|
3
|
+
export function AttributionCard({ rect, title, subtitle, metadata, prompt, color, icon, actions, onHold }) {
|
|
4
|
+
return (_jsxs(AnchoredPopup, { rect: rect, className: "flex w-105 flex-col text-xs", onPointerEnter: () => onHold?.(true), onPointerLeave: () => onHold?.(false), children: [_jsxs("div", { className: "flex flex-col gap-2 px-3 pt-3 pb-2.5", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { className: "flex shrink-0 items-center", style: { color: color?.startsWith('--') ? `var(${color})` : color }, children: icon ?? _jsx("span", { className: "size-2 rounded-full bg-current" }) }), _jsx("span", { className: "shrink-0 text-sm font-semibold text-text", children: title }), subtitle !== undefined && _jsx("span", { className: "min-w-0 truncate text-sm text-text-muted", children: subtitle })] }), metadata !== undefined && _jsx("div", { className: "text-text-muted", children: metadata }), prompt !== undefined && prompt !== '' && (_jsx("q", { className: "line-clamp-4 rounded-md bg-surface-hover px-2.5 py-2 text-sm leading-4.5 text-text select-text", children: prompt }))] }), actions !== undefined && _jsx("div", { className: "flex gap-1 border-t border-border px-1.5 py-1.25", children: actions })] }));
|
|
5
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { CircleCheck, Sparkles, TriangleAlert } from 'lucide-react';
|
|
3
|
+
import { Icon } from '@adecore/ui';
|
|
4
|
+
const BASELINE_MARKS = {
|
|
5
|
+
widely: { icon: CircleCheck, color: 'text-status-idle' },
|
|
6
|
+
newly: { icon: Sparkles, color: 'text-status-running' },
|
|
7
|
+
limited: { icon: TriangleAlert, color: 'text-status-needs-you' }
|
|
8
|
+
};
|
|
9
|
+
/* Where a web feature stands across browsers, as one line under a CSS property. */
|
|
10
|
+
export function BaselineStatus({ baseline }) {
|
|
11
|
+
const { icon, color } = BASELINE_MARKS[baseline.level];
|
|
12
|
+
return (_jsxs("div", { className: "flex items-start gap-1.5 text-xs/[18px] text-text-muted select-text", children: [_jsx(Icon, { icon: icon, size: 14, className: `mt-px shrink-0 ${color}` }), _jsx("span", { className: "min-w-0", children: baseline.text })] }));
|
|
13
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { Editor } from '@adecore/editor';
|
|
3
|
+
export interface ChangeReviewProps {
|
|
4
|
+
editor: Editor;
|
|
5
|
+
selected: string;
|
|
6
|
+
proposal: string;
|
|
7
|
+
startLine?: number;
|
|
8
|
+
actions?: ReactNode;
|
|
9
|
+
className?: string;
|
|
10
|
+
label: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function ChangeReview({ editor, selected, proposal, startLine, actions, className, label }: ChangeReviewProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useLayoutEffect, useRef } from 'react';
|
|
3
|
+
import { diffSegments, emphasisOf } from './proposal-model.js';
|
|
4
|
+
export function ChangeReview({ editor, selected, proposal, startLine = 1, actions, className, label }) {
|
|
5
|
+
const code = useRef(null);
|
|
6
|
+
useLayoutEffect(() => {
|
|
7
|
+
const element = code.current;
|
|
8
|
+
if (element === null) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
element.replaceChildren();
|
|
12
|
+
for (const segment of diffSegments(selected, proposal, startLine)) {
|
|
13
|
+
const row = element.ownerDocument.createElement('div');
|
|
14
|
+
element.append(row);
|
|
15
|
+
editor.renderCode(row, segment.text, {
|
|
16
|
+
firstLine: segment.firstLine,
|
|
17
|
+
...(segment.kind === 'same'
|
|
18
|
+
? {}
|
|
19
|
+
: { sign: segment.kind === 'removed' ? '-' : '+', color: segment.kind === 'removed' ? '--editor-deleted' : '--editor-added' }),
|
|
20
|
+
faded: segment.kind === 'removed',
|
|
21
|
+
emphasis: emphasisOf(segment)
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
return () => element.replaceChildren();
|
|
25
|
+
}, [editor, selected, proposal, startLine]);
|
|
26
|
+
return (_jsxs("section", { className: className, "aria-label": label, children: [_jsx("div", { ref: code }), actions] }));
|
|
27
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import { GitCommitHorizontal, User, Users } from 'lucide-react';
|
|
5
|
+
import { Button, Icon, Popover, Separator, Tooltip, useNow } from '@adecore/ui';
|
|
6
|
+
import { formatNumber } from '@adecore/ui/format';
|
|
7
|
+
import { relativeTime } from './relative-time.js';
|
|
8
|
+
const MINUTE_MS = 60_000;
|
|
9
|
+
/*
|
|
10
|
+
* Who wrote a declaration, by lines, and the newest commit that touched it. It opens beside the entry that
|
|
11
|
+
* was pressed and closes on Escape or a press anywhere else; Escape hands the keyboard back to the editor.
|
|
12
|
+
*/
|
|
13
|
+
export function CodeAuthorsCard({ language, view }) {
|
|
14
|
+
const { t } = useTranslation('editor');
|
|
15
|
+
const now = Math.floor(useNow(MINUTE_MS) / 1000);
|
|
16
|
+
const { authors, uncommittedLines, latest } = view.authorship;
|
|
17
|
+
const anchor = useMemo(() => {
|
|
18
|
+
const { left, top, right, bottom } = view.anchor;
|
|
19
|
+
return { getBoundingClientRect: () => new DOMRect(left, top, right - left, bottom - top) };
|
|
20
|
+
}, [view.anchor]);
|
|
21
|
+
return (_jsx(Popover.Root, { open: true, onOpenChange: (open, details) => !open && language.codeVision.closeAuthors(details.reason === 'escape-key'), children: _jsxs(Popover.Popup, { anchor: anchor, side: "bottom", align: "start", sideOffset: 4, className: "flex w-72 flex-col gap-2 p-3 text-xs", children: [_jsxs(Popover.Title, { className: "flex items-center gap-1.5 font-medium text-text", children: [_jsx(Icon, { icon: authors.length > 1 ? Users : User, size: 14, className: "shrink-0 text-text-muted" }), _jsx("span", { className: "min-w-0 truncate", children: view.name })] }), _jsxs("ul", { className: "flex flex-col gap-1", children: [authors.map((author) => (_jsxs("li", { className: "flex items-baseline gap-2", children: [_jsx(Tooltip, { label: author.email, children: _jsx("span", { className: "min-w-0 grow truncate text-text", children: author.name }) }), _jsx("span", { className: "shrink-0 text-text-muted", children: t('language.codeVision.lines', { count: author.lines, formatted: formatNumber(author.lines) }) })] }, author.name))), uncommittedLines > 0 && (_jsxs("li", { className: "flex items-baseline gap-2 text-text-muted", children: [_jsx("span", { className: "min-w-0 grow truncate", children: t('language.codeVision.uncommitted') }), _jsx("span", { className: "shrink-0", children: t('language.codeVision.lines', { count: uncommittedLines, formatted: formatNumber(uncommittedLines) }) })] }))] }), latest !== null && (_jsxs(_Fragment, { children: [_jsx(Separator, {}), _jsxs("div", { className: "flex flex-col gap-1", children: [_jsx("span", { className: "text-text-muted", children: t('language.codeVision.latest') }), _jsx("span", { className: "text-text select-text", children: latest.summary }), _jsxs("span", { className: "flex items-center gap-1.5 text-text-muted", children: [_jsx("span", { className: "font-mono select-text", children: latest.shortHash }), _jsx("span", { className: "min-w-0 truncate", children: latest.author }), _jsx("span", { className: "shrink-0", children: relativeTime(latest.at, now) })] })] }), view.openCommit !== null && (_jsx("div", { className: "flex justify-end", children: _jsx(Popover.Close, { render: _jsxs(Button, { size: "xs", variant: "secondary", onClick: view.openCommit, children: [_jsx(Icon, { icon: GitCommitHorizontal, size: 14 }), t('language.codeVision.showCommit')] }) }) }))] }))] }) }));
|
|
22
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { EditorRect } from '@adecore/editor';
|
|
2
|
+
import type { EditorLanguage } from './editor-language.ts';
|
|
3
|
+
import type { CompletionView } from './popups.ts';
|
|
4
|
+
export declare function CompletionPopup({ language, view, rect }: {
|
|
5
|
+
language: EditorLanguage;
|
|
6
|
+
view: CompletionView;
|
|
7
|
+
rect: EditorRect;
|
|
8
|
+
}): import("react").ReactPortal;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useLayoutEffect, useRef, useState } from 'react';
|
|
3
|
+
import { createPortal } from 'react-dom';
|
|
4
|
+
import { useTranslation } from 'react-i18next';
|
|
5
|
+
import { kindLetterOf, kindToneOf, rowWindow } from './completion-model.js';
|
|
6
|
+
import { SourceLine, SymbolSections } from './HoverSections.js';
|
|
7
|
+
import { PathText } from './PathText.js';
|
|
8
|
+
import { placeBeside, placePopup } from './popup-placement.js';
|
|
9
|
+
const TONES = {
|
|
10
|
+
callable: 'bg-accent-soft text-accent',
|
|
11
|
+
value: 'bg-status-running/15 text-status-running',
|
|
12
|
+
type: 'bg-status-idle/15 text-status-idle',
|
|
13
|
+
other: 'bg-surface-hover text-text-muted'
|
|
14
|
+
};
|
|
15
|
+
/* The label with the characters that match what was typed set apart. */
|
|
16
|
+
function MarkedLabel({ label, matches }) {
|
|
17
|
+
if (matches.length === 0) {
|
|
18
|
+
return _jsx(_Fragment, { children: label });
|
|
19
|
+
}
|
|
20
|
+
const marked = new Set(matches);
|
|
21
|
+
const runs = [];
|
|
22
|
+
for (let at = 0; at < label.length; at++) {
|
|
23
|
+
const isMarked = marked.has(at);
|
|
24
|
+
const last = runs[runs.length - 1];
|
|
25
|
+
if (last !== undefined && last.marked === isMarked) {
|
|
26
|
+
last.text += label[at];
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
runs.push({ text: label[at], marked: isMarked });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return (_jsx(_Fragment, { children: runs.map((run, index) => run.marked ? (_jsx("span", { className: "font-semibold text-accent", children: run.text }, index)) : (run.text)) }));
|
|
33
|
+
}
|
|
34
|
+
const CARD = 'fixed top-0 left-0 z-(--z-popup) overflow-hidden rounded-lg border border-border bg-surface-raised text-text shadow-(--float-shadow)';
|
|
35
|
+
const ROW_HEIGHT = 24;
|
|
36
|
+
const VISIBLE_ROWS = 8;
|
|
37
|
+
/* Rows drawn past the visible ones on each side, so a wheel turn lands on rows that exist. */
|
|
38
|
+
const OVERSCAN_ROWS = 6;
|
|
39
|
+
/* The padding the listbox has around its rows. */
|
|
40
|
+
const LIST_PADDING = 4;
|
|
41
|
+
/* What a row takes besides its name and namespace: padding, the kind badge and the gaps. */
|
|
42
|
+
const ROW_CHROME = 72;
|
|
43
|
+
/*
|
|
44
|
+
* The suggestions under the word being typed: a narrow list with the parameters of each, and the
|
|
45
|
+
* documentation of the active one beside it, or under it when the window has no room at its side. A
|
|
46
|
+
* press on a row never takes the focus from the editor, so typing goes on while the list is open.
|
|
47
|
+
*/
|
|
48
|
+
export function CompletionPopup({ language, view, rect }) {
|
|
49
|
+
const { t } = useTranslation('editor');
|
|
50
|
+
const list = useRef(null);
|
|
51
|
+
const docs = useRef(null);
|
|
52
|
+
const activeRow = useRef(null);
|
|
53
|
+
// The widest row so far in characters, which only grows, so the list never narrows or jumps while it is typed into.
|
|
54
|
+
const [widest, setWidest] = useState(0);
|
|
55
|
+
// Only the rows near the visible ones are drawn: a list of 150 is mostly out of sight, and every keystroke makes it again.
|
|
56
|
+
const [listTop, setListTop] = useState(0);
|
|
57
|
+
const need = Math.max(0, ...view.rows.slice(0, VISIBLE_ROWS * 2).map((row) => row.label.length + 2 + row.description.length));
|
|
58
|
+
if (need > widest) {
|
|
59
|
+
setWidest(need);
|
|
60
|
+
}
|
|
61
|
+
// Every render: the word moves when the editor scrolls, and the list and the documentation change size with what they hold.
|
|
62
|
+
useLayoutEffect(() => {
|
|
63
|
+
const card = list.current;
|
|
64
|
+
if (card === null) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const viewport = {
|
|
68
|
+
width: window.innerWidth,
|
|
69
|
+
height: window.innerHeight
|
|
70
|
+
};
|
|
71
|
+
const side = docs.current;
|
|
72
|
+
// Every size is read before the first write, so the page is laid out once and not after each of them.
|
|
73
|
+
const size = { width: card.offsetWidth, height: card.offsetHeight };
|
|
74
|
+
const sideSize = side === null ? null : { width: side.offsetWidth, height: side.offsetHeight };
|
|
75
|
+
const placed = placePopup(rect, size, viewport, { gap: 2 });
|
|
76
|
+
card.style.left = `${Math.round(placed.left)}px`;
|
|
77
|
+
card.style.top = `${Math.round(placed.top)}px`;
|
|
78
|
+
card.style.visibility = 'visible';
|
|
79
|
+
if (side !== null && sideSize !== null) {
|
|
80
|
+
const box = {
|
|
81
|
+
left: Math.round(placed.left),
|
|
82
|
+
top: Math.round(placed.top),
|
|
83
|
+
...size
|
|
84
|
+
};
|
|
85
|
+
const beside = placeBeside(box, sideSize, viewport);
|
|
86
|
+
side.style.left = `${Math.round(beside.left)}px`;
|
|
87
|
+
side.style.top = `${Math.round(beside.top)}px`;
|
|
88
|
+
side.style.maxHeight = `${Math.floor(beside.maxHeight)}px`;
|
|
89
|
+
side.style.visibility = 'visible';
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
useLayoutEffect(() => {
|
|
93
|
+
activeRow.current?.scrollIntoView({ block: 'nearest' });
|
|
94
|
+
}, [view.active, view.rows]);
|
|
95
|
+
const total = view.rows.length;
|
|
96
|
+
const { first, last } = rowWindow(listTop, view.active, total, ROW_HEIGHT, VISIBLE_ROWS, OVERSCAN_ROWS, LIST_PADDING);
|
|
97
|
+
return createPortal(_jsxs(_Fragment, { children: [_jsxs("div", { ref: list, className: `${CARD} flex w-max max-w-[min(560px,calc(100vw-16px))] min-w-[340px] flex-col`, style: { visibility: 'hidden' }, onPointerDown: (event) => event.preventDefault(), children: [_jsx("div", { "aria-hidden": true, className: "h-0 overflow-hidden font-mono text-code", style: {
|
|
98
|
+
width: `calc(${Math.max(widest, need)}ch + ${ROW_CHROME}px)`
|
|
99
|
+
} }), _jsxs("div", { role: "listbox", className: "w-0 min-w-full overflow-y-auto p-1", style: { maxHeight: ROW_HEIGHT * VISIBLE_ROWS + 8 }, onScroll: (event) => setListTop(event.currentTarget.scrollTop), children: [_jsx("div", { "aria-hidden": true, style: { height: first * ROW_HEIGHT } }), view.rows.slice(first, last).map((row, offset) => {
|
|
100
|
+
const index = first + offset;
|
|
101
|
+
return (_jsxs("button", { ref: index === view.active ? activeRow : undefined, type: "button", role: "option", "aria-selected": index === view.active, "aria-setsize": total, "aria-posinset": index + 1, "data-active": index === view.active, className: "flex h-6 w-full items-center gap-2 overflow-hidden rounded-md px-1.5 text-left text-xs cursor-row", onClick: () => void language.completion.accept(false, index), children: [_jsx("span", { className: `grid size-4 shrink-0 place-items-center rounded-sm font-mono text-xs leading-none font-semibold ${TONES[kindToneOf(row.kind)]}`, children: kindLetterOf(row.kind) }), _jsx("span", { className: `max-w-full shrink-0 truncate font-mono text-code ${row.deprecated ? 'line-through' : ''}`, children: _jsx(MarkedLabel, { label: row.label, matches: row.matches }) }), _jsx("span", { className: "min-w-0 grow truncate font-mono text-text-faint", children: row.detail }), _jsx(PathText, { path: row.description, className: "max-w-[60%] font-mono text-text-faint" })] }, `${index}:${row.label}`));
|
|
102
|
+
}), _jsx("div", { "aria-hidden": true, style: { height: (total - last) * ROW_HEIGHT } })] }), _jsxs("div", { className: "flex items-center gap-3 border-t border-border px-2 py-1 text-xs whitespace-nowrap text-text-faint", children: [_jsxs("span", { children: ["\u21B5 ", t('language.completion.insert')] }), _jsxs("span", { children: ["\u21E5 ", t('language.completion.replace')] }), _jsxs("span", { children: ["^Space ", t('language.completion.details')] }), _jsx("span", { className: "ml-auto", children: view.server })] })] }), view.detailsOpen && view.docs !== null && (_jsxs("div", { ref: docs, className: `${CARD} flex w-[360px] flex-col divide-y divide-border overflow-y-auto select-text`, style: { visibility: 'hidden' }, onPointerDown: (event) => event.preventDefault(), children: [view.docs.source !== '' && (_jsx("div", { className: "px-3 py-2", children: _jsx(SourceLine, { text: view.docs.source, language: view.highlightLanguage }) })), _jsx(SymbolSections, { text: view.docs.text })] }))] }), document.body);
|
|
103
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import type { EditorLanguage } from './editor-language.ts';
|
|
3
|
+
import type { MenuView } from './popups.ts';
|
|
4
|
+
export declare function EditorContextMenu({ language, view, children }: {
|
|
5
|
+
language: EditorLanguage;
|
|
6
|
+
view: MenuView;
|
|
7
|
+
children?: ReactNode;
|
|
8
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment, useEffect, useRef } from 'react';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import { ContextMenu, Kbd } from '@adecore/ui';
|
|
5
|
+
export function EditorContextMenu({ language, view, children }) {
|
|
6
|
+
const { t } = useTranslation('editor');
|
|
7
|
+
const trigger = useRef(null);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
trigger.current?.dispatchEvent(new MouseEvent('contextmenu', { bubbles: true, clientX: view.x, clientY: view.y }));
|
|
10
|
+
}, [view.x, view.y]);
|
|
11
|
+
const { service } = language.project;
|
|
12
|
+
const rows = [
|
|
13
|
+
{ id: 'goToDefinition', method: 'textDocument/definition', run: () => void language.navigation.go('definition') },
|
|
14
|
+
{ id: 'goToTypeDefinition', method: 'textDocument/typeDefinition', run: () => void language.navigation.go('typeDefinition') },
|
|
15
|
+
{ id: 'goToImplementation', method: 'textDocument/implementation', run: () => void language.navigation.go('implementation') },
|
|
16
|
+
{ id: 'peekReferences', method: 'textDocument/references', run: () => void language.peek.open() },
|
|
17
|
+
{ id: 'renameSymbol', method: 'textDocument/rename', run: () => void language.rename.start() },
|
|
18
|
+
{ id: 'codeActions', method: 'textDocument/codeAction', run: () => void language.codeActions.open() }
|
|
19
|
+
];
|
|
20
|
+
return (_jsxs(ContextMenu.Root, { onOpenChange: (open) => !open && language.contextMenu.close(), children: [_jsx(ContextMenu.Trigger, { ref: trigger, className: "fixed top-0 left-0 h-0 w-0", "aria-hidden": true }), _jsxs(ContextMenu.Popup, { children: [rows
|
|
21
|
+
.filter((row) => service.supports(row.method, language.uri))
|
|
22
|
+
.map((row) => (_jsxs(ContextMenu.Item, { onClick: row.run, children: [t(`commands.${row.id}`), language.shortcuts[row.id] !== null && _jsx(Kbd, { shortcut: language.shortcuts[row.id] })] }, row.id))), view.refactors.length > 0 && (_jsxs(ContextMenu.SubmenuRoot, { children: [_jsx(ContextMenu.SubmenuTrigger, { children: t('language.context.refactor') }), _jsx(ContextMenu.Popup, { children: view.refactors.map((entry, index) => (_jsxs(Fragment, { children: [index > 0 && view.refactors[index - 1].group !== entry.group && _jsx(ContextMenu.Separator, {}), _jsx(ContextMenu.Item, { onClick: () => void language.codeActions.apply(entry), children: entry.action.title })] }, entry.id))) })] })), children, _jsx(ContextMenu.Separator, {}), ['cut', 'copy', 'paste'].map((action) => (_jsx(ContextMenu.Item, { onClick: () => language.contextMenu.clipboard(action), children: t(`language.context.${action}`) }, action))), service.supports('textDocument/formatting', language.uri) && (_jsxs(_Fragment, { children: [_jsx(ContextMenu.Separator, {}), _jsxs(ContextMenu.Item, { onClick: () => void language.codeActions.formatDocument(), children: [t('commands.formatDocument'), language.shortcuts.formatDocument !== null && _jsx(Kbd, { shortcut: language.shortcuts.formatDocument })] })] }))] })] }));
|
|
23
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ReactNode, type Ref } from 'react';
|
|
2
|
+
import type { Editor, EditorEngine, EditorOptions } from '@adecore/editor';
|
|
3
|
+
import { EditorLanguage } from './editor-language.ts';
|
|
4
|
+
import type { ProjectLanguage } from './project-language.ts';
|
|
5
|
+
export interface EditorViewProps {
|
|
6
|
+
engine: EditorEngine;
|
|
7
|
+
options: EditorOptions;
|
|
8
|
+
project?: ProjectLanguage;
|
|
9
|
+
uri?: string;
|
|
10
|
+
languageId?: string;
|
|
11
|
+
className?: string;
|
|
12
|
+
ref?: Ref<HTMLDivElement>;
|
|
13
|
+
onMount?(editor: Editor, language: EditorLanguage | null): void | (() => void);
|
|
14
|
+
contextMenuItems?(language: EditorLanguage): ReactNode;
|
|
15
|
+
}
|
|
16
|
+
export declare function EditorView({ engine, options, project, uri, languageId, className, ref, onMount, contextMenuItems }: EditorViewProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { createHolder } from './holder.js';
|
|
3
|
+
import { useLayoutEffect, useMemo, useRef, useSyncExternalStore } from 'react';
|
|
4
|
+
import { EditorLanguage } from './editor-language.js';
|
|
5
|
+
import { LanguagePopups } from './LanguagePopups.js';
|
|
6
|
+
export function EditorView({ engine, options, project, uri, languageId, className, ref, onMount, contextMenuItems }) {
|
|
7
|
+
const element = useRef(null);
|
|
8
|
+
const holder = useMemo(() => createHolder(), []);
|
|
9
|
+
const language = useSyncExternalStore(holder.subscribe, holder.get, holder.get);
|
|
10
|
+
useLayoutEffect(() => {
|
|
11
|
+
const host = element.current;
|
|
12
|
+
if (host === null) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
const editor = engine.mount(host, options);
|
|
16
|
+
const attached = project === undefined || uri === undefined || languageId === undefined ? null : new EditorLanguage(project, editor, uri, languageId);
|
|
17
|
+
holder.set(attached);
|
|
18
|
+
const stop = onMount?.(editor, attached);
|
|
19
|
+
return () => {
|
|
20
|
+
holder.set(null);
|
|
21
|
+
stop?.();
|
|
22
|
+
attached?.dispose();
|
|
23
|
+
editor.dispose();
|
|
24
|
+
};
|
|
25
|
+
}, [engine, options, project, uri, languageId, holder, onMount]);
|
|
26
|
+
return (_jsxs("div", { className: className, ref: ref, style: { position: 'relative', minHeight: 0 }, children: [_jsx("div", { ref: element, style: { position: 'absolute', inset: 0 } }), language !== null && _jsx(LanguagePopups, { language: language, contextMenuItems: contextMenuItems })] }));
|
|
27
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
4
|
+
import { Button } from '@adecore/ui';
|
|
5
|
+
import { formatNumber } from '@adecore/ui/format';
|
|
6
|
+
import { compileFind, EMPTY_FIND_QUERY } from './find-query.js';
|
|
7
|
+
export function FindReplace({ editor, className, onClose }) {
|
|
8
|
+
const { t } = useTranslation('editor');
|
|
9
|
+
const [query, setQuery] = useState(EMPTY_FIND_QUERY);
|
|
10
|
+
const [replacement, setReplacement] = useState('');
|
|
11
|
+
const [preserveCase, setPreserveCase] = useState(false);
|
|
12
|
+
const [state, setState] = useState({ count: 0, current: null });
|
|
13
|
+
const compiled = useMemo(() => compileFind(query), [query]);
|
|
14
|
+
useEffect(() => editor.onFind(setState), [editor]);
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
editor.find(compiled.kind === 'pattern' ? query : null);
|
|
17
|
+
}, [editor, query, compiled]);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
editor.setReplacePreview(compiled.kind === 'pattern' ? replacement : null, { preserveCase });
|
|
20
|
+
}, [editor, compiled, replacement, preserveCase]);
|
|
21
|
+
useEffect(() => () => {
|
|
22
|
+
editor.endFind();
|
|
23
|
+
editor.setReplacePreview(null);
|
|
24
|
+
}, [editor]);
|
|
25
|
+
const label = compiled.kind === 'invalid'
|
|
26
|
+
? t('find.invalid')
|
|
27
|
+
: state.noSelection
|
|
28
|
+
? t('find.noSelection')
|
|
29
|
+
: t('find.count', { total: formatNumber(state.count), current: state.current === null ? '0' : formatNumber(state.current + 1) });
|
|
30
|
+
return (_jsxs("div", { className: `adecore-editor-find ${className ?? ''}`, role: "search", "aria-label": t('find.label'), children: [_jsxs("div", { className: "adecore-editor-find-row", children: [_jsx("input", { type: "text", "aria-label": t('find.label'), value: query.text, onChange: (event) => setQuery({ ...query, text: event.target.value }), onKeyDown: (event) => {
|
|
31
|
+
if (event.nativeEvent.isComposing) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (event.key === 'Enter') {
|
|
35
|
+
event.preventDefault();
|
|
36
|
+
editor.findStep(event.shiftKey ? -1 : 1);
|
|
37
|
+
}
|
|
38
|
+
if (event.key === 'Escape') {
|
|
39
|
+
event.preventDefault();
|
|
40
|
+
onClose?.();
|
|
41
|
+
editor.focus();
|
|
42
|
+
}
|
|
43
|
+
} }), _jsx("output", { "aria-live": "polite", children: label }), _jsx(Button, { size: "xs", onClick: () => editor.findStep(-1), children: t('find.previous') }), _jsx(Button, { size: "xs", onClick: () => editor.findStep(1), children: t('find.next') })] }), _jsx("div", { className: "adecore-editor-find-row", children: ['caseSensitive', 'wholeWord', 'regex', 'inSelection'].map((option) => (_jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: query[option] === true, onChange: (event) => setQuery({ ...query, [option]: event.target.checked }) }), t(`find.${option}`)] }, option))) }), _jsxs("div", { className: "adecore-editor-find-row", children: [_jsx("input", { type: "text", "aria-label": t('find.replace.label'), value: replacement, onChange: (event) => setReplacement(event.target.value) }), _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: preserveCase, onChange: (event) => setPreserveCase(event.target.checked) }), t('find.replace.preserveCase')] }), _jsx(Button, { size: "xs", onClick: () => editor.replace(replacement, { preserveCase }), children: t('find.replace.one') }), _jsx(Button, { size: "xs", onClick: () => editor.replaceAll(replacement, { preserveCase }), children: t('find.replace.allLabel') })] })] }));
|
|
44
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { EditorPosition } from '@adecore/editor';
|
|
2
|
+
import type { EditorLanguage } from './editor-language.ts';
|
|
3
|
+
import { type Problem } from './diagnostics-model.ts';
|
|
4
|
+
import type { HoverInfo } from './popups.ts';
|
|
5
|
+
export declare function HoverCard({ language, problems, info, anchor, position }: {
|
|
6
|
+
language: EditorLanguage;
|
|
7
|
+
problems: readonly Problem[];
|
|
8
|
+
info: HoverInfo | null;
|
|
9
|
+
anchor: EditorPosition;
|
|
10
|
+
position: EditorPosition;
|
|
11
|
+
}): import("react").JSX.Element;
|