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}