mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 14:25:55 +02:00
Enable reloading changes to a submodule (#13170)
# Description Fixes: https://github.com/nushell/nushell/issues/12099 Currently if user run `use voice.nu`, and file is unchanged, then run `use voice.nu` again. nushell will use the module directly, even if submodule inside `voice.nu` is changed. After discussed with @kubouch, I think it's ok to re-parse the module file when: 1. It exports sub modules which are defined by a file 2. It uses other modules which are defined by a file ## About the change: To achieve the behavior, we need to add 2 attributes to `Module`: 1. `imported_modules`: it tracks the other modules is imported by the givem `module`, e.g: `use foo.nu` 2. `file`: the path of a module, if a module is defined by a file, it will be `Some(path)`, or else it will be `None`. After the change: use voice.nu always read the file and parse it. use voice will still use the module which is saved in EngineState. # User-Facing Changes use `xxx.nu` will read the file and parse it if it exports submodules or uses submodules # Tests + Formatting Done --------- Co-authored-by: Jakub Žádník <kubouch@gmail.com>
This commit is contained in:
@ -8,7 +8,6 @@ mod parse_keywords;
|
||||
mod parse_patterns;
|
||||
mod parse_shape_specs;
|
||||
mod parser;
|
||||
mod parser_path;
|
||||
mod type_check;
|
||||
|
||||
pub use deparse::{escape_for_script_arg, escape_quote_string};
|
||||
@ -18,8 +17,8 @@ pub use flatten::{
|
||||
pub use known_external::KnownExternal;
|
||||
pub use lex::{lex, lex_signature, Token, TokenContents};
|
||||
pub use lite_parser::{lite_parse, LiteBlock, LiteCommand};
|
||||
pub use nu_protocol::parser_path::*;
|
||||
pub use parse_keywords::*;
|
||||
pub use parser_path::*;
|
||||
|
||||
pub use parser::{
|
||||
is_math_expression_like, parse, parse_block, parse_expression, parse_external_call,
|
||||
|
@ -2,7 +2,6 @@ use crate::{
|
||||
exportable::Exportable,
|
||||
parse_block,
|
||||
parser::{parse_redirection, redirecting_builtin_error},
|
||||
parser_path::ParserPath,
|
||||
type_check::{check_block_input_output, type_compatible},
|
||||
};
|
||||
use itertools::Itertools;
|
||||
@ -15,6 +14,7 @@ use nu_protocol::{
|
||||
},
|
||||
engine::{StateWorkingSet, DEFAULT_OVERLAY_NAME},
|
||||
eval_const::eval_constant,
|
||||
parser_path::ParserPath,
|
||||
Alias, BlockId, DeclId, Module, ModuleId, ParseError, PositionalArg, ResolvedImportPattern,
|
||||
Span, Spanned, SyntaxShape, Type, Value, VarId,
|
||||
};
|
||||
@ -1204,7 +1204,7 @@ pub fn parse_export_in_block(
|
||||
"export alias" => parse_alias(working_set, lite_command, None),
|
||||
"export def" => parse_def(working_set, lite_command, None).0,
|
||||
"export const" => parse_const(working_set, &lite_command.parts[1..]),
|
||||
"export use" => parse_use(working_set, lite_command).0,
|
||||
"export use" => parse_use(working_set, lite_command, None).0,
|
||||
"export module" => parse_module(working_set, lite_command, None).0,
|
||||
"export extern" => parse_extern(working_set, lite_command, None),
|
||||
_ => {
|
||||
@ -1223,6 +1223,7 @@ pub fn parse_export_in_module(
|
||||
working_set: &mut StateWorkingSet,
|
||||
lite_command: &LiteCommand,
|
||||
module_name: &[u8],
|
||||
parent_module: &mut Module,
|
||||
) -> (Pipeline, Vec<Exportable>) {
|
||||
let spans = &lite_command.parts[..];
|
||||
|
||||
@ -1428,7 +1429,8 @@ pub fn parse_export_in_module(
|
||||
pipe: lite_command.pipe,
|
||||
redirection: lite_command.redirection.clone(),
|
||||
};
|
||||
let (pipeline, exportables) = parse_use(working_set, &lite_command);
|
||||
let (pipeline, exportables) =
|
||||
parse_use(working_set, &lite_command, Some(parent_module));
|
||||
|
||||
let export_use_decl_id = if let Some(id) = working_set.find_decl(b"export use") {
|
||||
id
|
||||
@ -1771,7 +1773,7 @@ pub fn parse_module_block(
|
||||
))
|
||||
}
|
||||
b"use" => {
|
||||
let (pipeline, _) = parse_use(working_set, command);
|
||||
let (pipeline, _) = parse_use(working_set, command, Some(&mut module));
|
||||
|
||||
block.pipelines.push(pipeline)
|
||||
}
|
||||
@ -1786,7 +1788,7 @@ pub fn parse_module_block(
|
||||
}
|
||||
b"export" => {
|
||||
let (pipe, exportables) =
|
||||
parse_export_in_module(working_set, command, module_name);
|
||||
parse_export_in_module(working_set, command, module_name, &mut module);
|
||||
|
||||
for exportable in exportables {
|
||||
match exportable {
|
||||
@ -1896,6 +1898,48 @@ pub fn parse_module_block(
|
||||
(block, module, module_comments)
|
||||
}
|
||||
|
||||
fn module_needs_reloading(working_set: &StateWorkingSet, module_id: ModuleId) -> bool {
|
||||
let module = working_set.get_module(module_id);
|
||||
|
||||
fn submodule_need_reloading(working_set: &StateWorkingSet, submodule_id: ModuleId) -> bool {
|
||||
let submodule = working_set.get_module(submodule_id);
|
||||
let submodule_changed = if let Some((file_path, file_id)) = &submodule.file {
|
||||
let existing_contents = working_set.get_contents_of_file(*file_id);
|
||||
let file_contents = file_path.read(working_set);
|
||||
|
||||
if let (Some(existing), Some(new)) = (existing_contents, file_contents) {
|
||||
existing != new
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if submodule_changed {
|
||||
true
|
||||
} else {
|
||||
module_needs_reloading(working_set, submodule_id)
|
||||
}
|
||||
}
|
||||
|
||||
let export_submodule_changed = module
|
||||
.submodules
|
||||
.iter()
|
||||
.any(|(_, submodule_id)| submodule_need_reloading(working_set, *submodule_id));
|
||||
|
||||
if export_submodule_changed {
|
||||
return true;
|
||||
}
|
||||
|
||||
let private_submodule_changed = module
|
||||
.imported_modules
|
||||
.iter()
|
||||
.any(|submodule_id| submodule_need_reloading(working_set, *submodule_id));
|
||||
|
||||
private_submodule_changed
|
||||
}
|
||||
|
||||
/// Parse a module from a file.
|
||||
///
|
||||
/// The module name is inferred from the stem of the file, unless specified in `name_override`.
|
||||
@ -1934,23 +1978,26 @@ fn parse_module_file(
|
||||
|
||||
// Check if we've parsed the module before.
|
||||
if let Some(module_id) = working_set.find_module_by_span(new_span) {
|
||||
return Some(module_id);
|
||||
if !module_needs_reloading(working_set, module_id) {
|
||||
return Some(module_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the file to the stack of files being processed.
|
||||
if let Err(e) = working_set.files.push(path.path_buf(), path_span) {
|
||||
if let Err(e) = working_set.files.push(path.clone().path_buf(), path_span) {
|
||||
working_set.error(e);
|
||||
return None;
|
||||
}
|
||||
|
||||
// Parse the module
|
||||
let (block, module, module_comments) =
|
||||
let (block, mut module, module_comments) =
|
||||
parse_module_block(working_set, new_span, module_name.as_bytes());
|
||||
|
||||
// Remove the file from the stack of files being processed.
|
||||
working_set.files.pop();
|
||||
|
||||
let _ = working_set.add_block(Arc::new(block));
|
||||
module.file = Some((path, file_id));
|
||||
let module_id = working_set.add_module(&module_name, module, module_comments);
|
||||
|
||||
Some(module_id)
|
||||
@ -2240,6 +2287,7 @@ pub fn parse_module(
|
||||
pub fn parse_use(
|
||||
working_set: &mut StateWorkingSet,
|
||||
lite_command: &LiteCommand,
|
||||
parent_module: Option<&mut Module>,
|
||||
) -> (Pipeline, Vec<Exportable>) {
|
||||
let spans = &lite_command.parts;
|
||||
|
||||
@ -2385,12 +2433,14 @@ pub fn parse_use(
|
||||
);
|
||||
};
|
||||
|
||||
let mut imported_modules = vec![];
|
||||
let (definitions, errors) = module.resolve_import_pattern(
|
||||
working_set,
|
||||
module_id,
|
||||
&import_pattern.members,
|
||||
None,
|
||||
name_span,
|
||||
&mut imported_modules,
|
||||
);
|
||||
|
||||
working_set.parse_errors.extend(errors);
|
||||
@ -2432,6 +2482,9 @@ pub fn parse_use(
|
||||
|
||||
import_pattern.constants = constants.iter().map(|(_, id)| *id).collect();
|
||||
|
||||
if let Some(m) = parent_module {
|
||||
m.track_imported_modules(&imported_modules)
|
||||
}
|
||||
// Extend the current scope with the module's exportables
|
||||
working_set.use_decls(definitions.decls);
|
||||
working_set.use_modules(definitions.modules);
|
||||
@ -2865,6 +2918,7 @@ pub fn parse_overlay_use(working_set: &mut StateWorkingSet, call: Box<Call>) ->
|
||||
&[],
|
||||
Some(final_overlay_name.as_bytes()),
|
||||
call.head,
|
||||
&mut vec![],
|
||||
)
|
||||
} else {
|
||||
origin_module.resolve_import_pattern(
|
||||
@ -2875,6 +2929,7 @@ pub fn parse_overlay_use(working_set: &mut StateWorkingSet, call: Box<Call>) ->
|
||||
}],
|
||||
Some(final_overlay_name.as_bytes()),
|
||||
call.head,
|
||||
&mut vec![],
|
||||
)
|
||||
}
|
||||
} else {
|
||||
|
@ -5377,7 +5377,7 @@ pub fn parse_builtin_commands(
|
||||
}
|
||||
b"alias" => parse_alias(working_set, lite_command, None),
|
||||
b"module" => parse_module(working_set, lite_command, None).0,
|
||||
b"use" => parse_use(working_set, lite_command).0,
|
||||
b"use" => parse_use(working_set, lite_command, None).0,
|
||||
b"overlay" => {
|
||||
if let Some(redirection) = lite_command.redirection.as_ref() {
|
||||
working_set.error(redirecting_builtin_error("overlay", redirection));
|
||||
|
@ -1,152 +0,0 @@
|
||||
use nu_protocol::engine::{StateWorkingSet, VirtualPath};
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
/// An abstraction over a PathBuf that can have virtual paths (files and directories). Virtual
|
||||
/// paths always exist and represent a way to ship Nushell code inside the binary without requiring
|
||||
/// paths to be present in the file system.
|
||||
///
|
||||
/// Created from VirtualPath found in the engine state.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum ParserPath {
|
||||
RealPath(PathBuf),
|
||||
VirtualFile(PathBuf, usize),
|
||||
VirtualDir(PathBuf, Vec<ParserPath>),
|
||||
}
|
||||
|
||||
impl ParserPath {
|
||||
pub fn is_dir(&self) -> bool {
|
||||
match self {
|
||||
ParserPath::RealPath(p) => p.is_dir(),
|
||||
ParserPath::VirtualFile(..) => false,
|
||||
ParserPath::VirtualDir(..) => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_file(&self) -> bool {
|
||||
match self {
|
||||
ParserPath::RealPath(p) => p.is_file(),
|
||||
ParserPath::VirtualFile(..) => true,
|
||||
ParserPath::VirtualDir(..) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn exists(&self) -> bool {
|
||||
match self {
|
||||
ParserPath::RealPath(p) => p.exists(),
|
||||
ParserPath::VirtualFile(..) => true,
|
||||
ParserPath::VirtualDir(..) => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &Path {
|
||||
match self {
|
||||
ParserPath::RealPath(p) => p,
|
||||
ParserPath::VirtualFile(p, _) => p,
|
||||
ParserPath::VirtualDir(p, _) => p,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path_buf(self) -> PathBuf {
|
||||
match self {
|
||||
ParserPath::RealPath(p) => p,
|
||||
ParserPath::VirtualFile(p, _) => p,
|
||||
ParserPath::VirtualDir(p, _) => p,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parent(&self) -> Option<&Path> {
|
||||
match self {
|
||||
ParserPath::RealPath(p) => p.parent(),
|
||||
ParserPath::VirtualFile(p, _) => p.parent(),
|
||||
ParserPath::VirtualDir(p, _) => p.parent(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_dir(&self) -> Option<Vec<ParserPath>> {
|
||||
match self {
|
||||
ParserPath::RealPath(p) => p.read_dir().ok().map(|read_dir| {
|
||||
read_dir
|
||||
.flatten()
|
||||
.map(|dir_entry| ParserPath::RealPath(dir_entry.path()))
|
||||
.collect()
|
||||
}),
|
||||
ParserPath::VirtualFile(..) => None,
|
||||
ParserPath::VirtualDir(_, files) => Some(files.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn file_stem(&self) -> Option<&OsStr> {
|
||||
self.path().file_stem()
|
||||
}
|
||||
|
||||
pub fn extension(&self) -> Option<&OsStr> {
|
||||
self.path().extension()
|
||||
}
|
||||
|
||||
pub fn join(self, path: impl AsRef<Path>) -> ParserPath {
|
||||
match self {
|
||||
ParserPath::RealPath(p) => ParserPath::RealPath(p.join(path)),
|
||||
ParserPath::VirtualFile(p, file_id) => ParserPath::VirtualFile(p.join(path), file_id),
|
||||
ParserPath::VirtualDir(p, entries) => {
|
||||
let new_p = p.join(path);
|
||||
let mut pp = ParserPath::RealPath(new_p.clone());
|
||||
for entry in entries {
|
||||
if new_p == entry.path() {
|
||||
pp = entry.clone();
|
||||
}
|
||||
}
|
||||
pp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open<'a>(
|
||||
&'a self,
|
||||
working_set: &'a StateWorkingSet,
|
||||
) -> std::io::Result<Box<dyn std::io::Read + 'a>> {
|
||||
match self {
|
||||
ParserPath::RealPath(p) => {
|
||||
std::fs::File::open(p).map(|f| Box::new(f) as Box<dyn std::io::Read>)
|
||||
}
|
||||
ParserPath::VirtualFile(_, file_id) => working_set
|
||||
.get_contents_of_file(*file_id)
|
||||
.map(|bytes| Box::new(bytes) as Box<dyn std::io::Read>)
|
||||
.ok_or(std::io::ErrorKind::NotFound.into()),
|
||||
|
||||
ParserPath::VirtualDir(..) => Err(std::io::ErrorKind::NotFound.into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read<'a>(&'a self, working_set: &'a StateWorkingSet) -> Option<Vec<u8>> {
|
||||
self.open(working_set)
|
||||
.and_then(|mut reader| {
|
||||
let mut vec = vec![];
|
||||
reader.read_to_end(&mut vec)?;
|
||||
Ok(vec)
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub fn from_virtual_path(
|
||||
working_set: &StateWorkingSet,
|
||||
name: &str,
|
||||
virtual_path: &VirtualPath,
|
||||
) -> Self {
|
||||
match virtual_path {
|
||||
VirtualPath::File(file_id) => ParserPath::VirtualFile(PathBuf::from(name), *file_id),
|
||||
VirtualPath::Dir(entries) => ParserPath::VirtualDir(
|
||||
PathBuf::from(name),
|
||||
entries
|
||||
.iter()
|
||||
.map(|virtual_path_id| {
|
||||
let (virt_name, virt_path) = working_set.get_virtual_path(*virtual_path_id);
|
||||
ParserPath::from_virtual_path(working_set, virt_name, virt_path)
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user