ad_astra/analysis/write.rs
1////////////////////////////////////////////////////////////////////////////////
2// This file is part of "Ad Astra", an embeddable scripting programming //
3// language platform. //
4// //
5// This work is proprietary software with source-available code. //
6// //
7// To copy, use, distribute, or contribute to this work, you must agree to //
8// the terms of the General License Agreement: //
9// //
10// https://github.com/Eliah-Lakhin/ad-astra/blob/master/EULA.md //
11// //
12// The agreement grants a Basic Commercial License, allowing you to use //
13// this work in non-commercial and limited commercial products with a total //
14// gross revenue cap. To remove this commercial limit for one of your //
15// products, you must acquire a Full Commercial License. //
16// //
17// If you contribute to the source code, documentation, or related materials, //
18// you must grant me an exclusive license to these contributions. //
19// Contributions are governed by the "Contributions" section of the General //
20// License Agreement. //
21// //
22// Copying the work in parts is strictly forbidden, except as permitted //
23// under the General License Agreement. //
24// //
25// If you do not or cannot agree to the terms of this Agreement, //
26// do not use this work. //
27// //
28// This work is provided "as is", without any warranties, express or implied, //
29// except where such disclaimers are legally invalid. //
30// //
31// Copyright (c) 2024 Ilya Lakhin (Илья Александрович Лахин). //
32// All rights reserved. //
33////////////////////////////////////////////////////////////////////////////////
34
35use std::ops::Deref;
36
37use ahash::RandomState;
38use lady_deirdre::{
39 analysis::{ExclusiveTask, MutationAccess, TaskHandle, TriggerHandle},
40 arena::{Id, Identifiable},
41 lexis::{ToSite, ToSpan},
42};
43
44use crate::{
45 analysis::{
46 completions::PROMPT_STRING,
47 read::ModuleReadSealed,
48 Completions,
49 ModuleError,
50 ModuleRead,
51 ModuleResult,
52 ModuleResultEx,
53 },
54 runtime::PackageMeta,
55 syntax::ScriptNode,
56};
57
58/// An object that grants exclusive access to the
59/// [ScriptModule](crate::analysis::ScriptModule) content.
60///
61/// Created by the [write](crate::analysis::ScriptModule::write) and
62/// [try_write](crate::analysis::ScriptModule::try_write) functions.
63///
64/// Implements the [ModuleRead] trait, which provides content read functions,
65/// and the [ModuleWrite] trait, which provides content write functions and
66/// read functions that require exclusive access.
67pub struct ModuleWriteGuard<'a, H: TaskHandle = TriggerHandle> {
68 pub(super) id: Id,
69 pub(super) package: &'static PackageMeta,
70 pub(super) task: ExclusiveTask<'a, ScriptNode, H, RandomState>,
71}
72
73impl<'a, H: TaskHandle> Identifiable for ModuleWriteGuard<'a, H> {
74 #[inline(always)]
75 fn id(&self) -> Id {
76 self.id
77 }
78}
79
80impl<'a, H: TaskHandle> ModuleRead<H> for ModuleWriteGuard<'a, H> {
81 #[inline(always)]
82 fn package(&self) -> &'static PackageMeta {
83 self.package
84 }
85}
86
87impl<'a, H: TaskHandle> ModuleWrite<H> for ModuleWriteGuard<'a, H> {}
88
89impl<'a, H: TaskHandle> ModuleReadSealed<H> for ModuleWriteGuard<'a, H> {
90 type Task = ExclusiveTask<'a, ScriptNode, H, RandomState>;
91
92 #[inline(always)]
93 fn task(&self) -> &Self::Task {
94 &self.task
95 }
96}
97
98impl<'a, H: TaskHandle> ModuleWriteSealed<H> for ModuleWriteGuard<'a, H> {
99 #[inline(always)]
100 fn task_mut(&mut self) -> &mut Self::Task {
101 &mut self.task
102 }
103}
104
105/// A set of write functions and exclusive read functions for the
106/// [ScriptModule](crate::analysis::ScriptModule) content.
107///
108/// This trait is implemented by the [ModuleWriteGuard] object. The trait is
109/// sealed, meaning it cannot be implemented outside of this crate.
110pub trait ModuleWrite<H: TaskHandle>: ModuleRead<H> + ModuleWriteSealed<H>
111where
112 Self::Task: MutationAccess<ScriptNode, H, RandomState>,
113{
114 /// Mutates the source code text of the script module.
115 ///
116 /// The `span` argument specifies the source code range that you want to
117 /// rewrite. It can be an absolute Unicode character range, such as `10..20`,
118 /// a [line-column](lady_deirdre::lexis::Position) range like
119 /// `Position::new(10, 3)..Position::new(12, 4)`, or a
120 /// [ScriptOrigin](crate::runtime::ScriptOrigin) instance.
121 ///
122 /// The `text` argument specifies the string you want to insert in place
123 /// of the spanned range. It can be an empty string if you want to erase
124 /// a fragment of the source code.
125 ///
126 /// The underlying algorithm incrementally patches the internal
127 /// representation of the script module, localized to the spanned fragment.
128 /// In most cases, this process is quite fast, even with large source code.
129 /// Therefore, it is acceptable to call this function on each end-user
130 /// keystroke.
131 ///
132 /// The function returns a [ModuleError::Cursor] error if the provided
133 /// `span` is not [valid](ToSpan::is_valid_span) for this module.
134 fn edit(&mut self, span: impl ToSpan, text: impl AsRef<str>) -> ModuleResult<()> {
135 let id = self.id();
136
137 let span = {
138 let doc_read = self.read_doc();
139
140 match span.to_site_span(doc_read.deref()) {
141 Some(span) => span,
142 None => return Err(ModuleError::Cursor(id)),
143 }
144 };
145
146 self.task_mut()
147 .write_to_doc(id, span, text)
148 .into_module_result(id)
149 }
150
151 /// Returns a [Completions] description object that describes potential
152 /// completions for the script module's source code at the specified
153 /// `site` position.
154 ///
155 /// The `site` argument specifies the location of the end-user cursor. It
156 /// can be an absolute Unicode character offset, such as `10`, or a
157 /// [line-column](lady_deirdre::lexis::Position) offset.
158 ///
159 /// The function returns a [ModuleError::Cursor] error if the provided
160 /// `site` is not [valid](ToSite::is_valid_site) for this module.
161 fn completions(&mut self, site: impl ToSite) -> ModuleResult<Completions> {
162 let id = self.id();
163
164 let site = {
165 let doc_read = self.read_doc();
166
167 match site.to_site(doc_read.deref()) {
168 Some(site) => site,
169 None => return Err(ModuleError::Cursor(id)),
170 }
171 };
172
173 let _ = self
174 .task_mut()
175 .write_to_doc(id, site..site, PROMPT_STRING)
176 .into_module_result(id)?;
177
178 let task = self.task();
179 let result = Completions::analyze(id, site, task).forward();
180
181 let _ = self
182 .task_mut()
183 .write_to_doc(id, site..(site + PROMPT_STRING.len()), "")
184 .into_module_result(id)?;
185
186 result.into_module_result(id)
187 }
188}
189
190pub trait ModuleWriteSealed<H: TaskHandle>: ModuleReadSealed<H>
191where
192 <Self as ModuleReadSealed<H>>::Task: MutationAccess<ScriptNode, H, RandomState>,
193{
194 fn task_mut(&mut self) -> &mut Self::Task;
195}