Browse Source

Tab-complete prefixes and module paths

David Peter 2 years ago
parent
commit
f2e4d8e6b7

+ 1 - 0
Cargo.lock

@@ -831,6 +831,7 @@ dependencies = [
  "thiserror",
  "unicode-ident",
  "unicode-width",
+ "walkdir",
 ]
 
 [[package]]

+ 28 - 28
book/src/list-units.md

@@ -7,8 +7,8 @@ and — where sensible — units allow for [binary prefixes](https://en.wikipedi
 
 | Dimension | Unit name | Identifier(s) |
 | --- | --- | --- |
-| `AbsorbedDose` | [Gray](https://en.wikipedia.org/wiki/Gray_(unit)) | `Gy`, `gray`, `grays` |
-| `Activity` | [Becquerel](https://en.wikipedia.org/wiki/Becquerel) | `Bq`, `becquerel`, `becquerels` |
+| `AbsorbedDose` | [Gray](https://en.wikipedia.org/wiki/Gray_(unit)) | `gray`, `grays`, `Gy` |
+| `Activity` | [Becquerel](https://en.wikipedia.org/wiki/Becquerel) | `becquerel`, `becquerels`, `Bq` |
 | `AmountOfSubstance` | [Mole](https://en.wikipedia.org/wiki/Mole_(unit)) | `mol`, `mole`, `moles` |
 | `Angle` | [Minute of arc](https://en.wikipedia.org/wiki/Minute_and_second_of_arc) | `arcmin`, `arcminute`, `arcminutes` |
 | `Angle` | [Second of arc](https://en.wikipedia.org/wiki/Minute_and_second_of_arc) | `arcsec`, `arcsecond`, `arcseconds` |
@@ -26,7 +26,7 @@ and — where sensible — units allow for [binary prefixes](https://en.wikipedi
 | `CatalyticActivity` | [Katal](https://en.wikipedia.org/wiki/Katal) | `kat`, `katal`, `katals` |
 | `Current` | [Ampere](https://en.wikipedia.org/wiki/Ampere) | `A`, `ampere`, `amperes` |
 | `DigitalInformation` | [Bit](https://en.wikipedia.org/wiki/Bit) | `bit`, `bits` |
-| `DigitalInformation` | [Byte](https://en.wikipedia.org/wiki/Byte) | `B`, `Byte`, `Bytes`, `Octet`, `Octets`, `byte`, `bytes`, `octet`, `octets` |
+| `DigitalInformation` | [Byte](https://en.wikipedia.org/wiki/Byte) | `B`, `byte`, `Byte`, `bytes`, `Bytes`, `octet`, `Octet`, `octets`, `Octets` |
 | `DigitalInformation / Time` | [Bits per second](https://en.wikipedia.org/wiki/Bit_per_second) | `bps` |
 | `Dot` | [Dot](https://en.wikipedia.org/wiki/Dots_per_inch) | `dot`, `dots` |
 | `Dot / Length` | [Dots per inch](https://en.wikipedia.org/wiki/Dots_per_inch) | `dpi` |
@@ -36,13 +36,13 @@ and — where sensible — units allow for [binary prefixes](https://en.wikipedi
 | `ElectricResistance` | [Ohm](https://en.wikipedia.org/wiki/Ohm) | `ohm`, `ohms`, `Ω` |
 | `Energy` | [British thermal unit](https://en.wikipedia.org/wiki/British_thermal_unit) | `BTU`, `Btu` |
 | `Energy` | [Calorie](https://en.wikipedia.org/wiki/Calorie) | `cal`, `calorie`, `calories` |
-| `Energy` | [Electronvolt](https://en.wikipedia.org/wiki/Electronvolt) | `eV`, `electronvolt`, `electronvolts` |
+| `Energy` | [Electronvolt](https://en.wikipedia.org/wiki/Electronvolt) | `electronvolt`, `electronvolts`, `eV` |
 | `Energy` | [Erg](https://en.wikipedia.org/wiki/Erg) | `erg`, `ergs` |
 | `Energy` | [Hartree](https://en.wikipedia.org/wiki/Hartree) | `hartree`, `hartrees` |
 | `Energy` | [Joule](https://en.wikipedia.org/wiki/Joule) | `J`, `joule`, `joules` |
 | `Energy` | [Planck energy](https://en.wikipedia.org/wiki/Planck_energy) | `planck_energy` |
-| `Energy` | [Watt-hour](https://en.wikipedia.org/wiki/Watt_hour) | `Wh`, `watthour` |
-| `EquivalentDose` | [Sievert](https://en.wikipedia.org/wiki/Sievert) | `Sv`, `sievert`, `sieverts` |
+| `Energy` | [Watt-hour](https://en.wikipedia.org/wiki/Watt_hour) | `watthour`, `Wh` |
+| `EquivalentDose` | [Sievert](https://en.wikipedia.org/wiki/Sievert) | `sievert`, `sieverts`, `Sv` |
 | `Force` | [Dyne](https://en.wikipedia.org/wiki/Dyne) | `dyn`, `dyne` |
 | `Force` | [Kilogram-force](https://en.wikipedia.org/wiki/Kilogram-force) | `kgf`, `kilogram_force` |
 | `Force` | [Newton](https://en.wikipedia.org/wiki/Newton_(unit)) | `N`, `newton`, `newtons` |
@@ -51,14 +51,14 @@ and — where sensible — units allow for [binary prefixes](https://en.wikipedi
 | `Force / Volume` | [Mercury](https://en.wikipedia.org/wiki/Mercury_(element)) | `Hg` |
 | `Frame` | [Frame](https://en.wikipedia.org/wiki/Frame_rate) | `frame`, `frames` |
 | `Frame / Time` | [Frames per second](https://en.wikipedia.org/wiki/Frame_rate) | `fps` |
-| `Frequency` | [Hertz](https://en.wikipedia.org/wiki/Hertz) | `Hz`, `hertz` |
-| `Frequency` | [Revolutions per minute](https://en.wikipedia.org/wiki/Revolutions_per_minute) | `RPM`, `rpm` |
+| `Frequency` | [Hertz](https://en.wikipedia.org/wiki/Hertz) | `hertz`, `Hz` |
+| `Frequency` | [Revolutions per minute](https://en.wikipedia.org/wiki/Revolutions_per_minute) | `rpm`, `RPM` |
 | `Illuminance` | [Foot-candle](https://en.wikipedia.org/wiki/Foot-candle) | `fc`, `footcandle`, `footcandles` |
 | `Illuminance` | [Lux](https://en.wikipedia.org/wiki/Lux) | `lux`, `lx` |
 | `Inductance` | [Henry](https://en.wikipedia.org/wiki/Henry_(unit)) | `H`, `henries`, `henry`, `henrys` |
 | `KinematicViscosity` | [Stokes](https://en.wikipedia.org/wiki/Stokes_(unit)) | `St`, `stokes` |
 | `Length` | [Ångström](https://en.wikipedia.org/wiki/Angstrom) | `angstrom`, `angstroms`, `Å` |
-| `Length` | [Astronomical unit](https://en.wikipedia.org/wiki/Astronomical_unit) | `AU`, `astronomicalunit`, `astronomicalunits`, `au` |
+| `Length` | [Astronomical unit](https://en.wikipedia.org/wiki/Astronomical_unit) | `astronomicalunit`, `astronomicalunits`, `au`, `AU` |
 | `Length` | [Bohr](https://en.wikipedia.org/wiki/Hartree_atomic_units) | `bohr` |
 | `Length` | [Fathom](https://en.wikipedia.org/wiki/Fathom) | `fathom`, `fathoms` |
 | `Length` | [Fermi](https://en.wikipedia.org/wiki/Femtometre) | `fermi` |
@@ -70,7 +70,7 @@ and — where sensible — units allow for [binary prefixes](https://en.wikipedi
 | `Length` | [Metre](https://en.wikipedia.org/wiki/Metre) | `m`, `meter`, `meters`, `metre`, `metres` |
 | `Length` | [Micron](https://en.wikipedia.org/wiki/Micrometre) | `micron` |
 | `Length` | [Mile](https://en.wikipedia.org/wiki/Mile) | `mi`, `mile`, `miles` |
-| `Length` | [Nautical Mile](https://en.wikipedia.org/wiki/Nautical_mile) | `NM`, `nautical_mile`, `nautical_miles`, `nmi` |
+| `Length` | [Nautical Mile](https://en.wikipedia.org/wiki/Nautical_mile) | `nautical_mile`, `nautical_miles`, `NM`, `nmi` |
 | `Length` | [Parsec](https://en.wikipedia.org/wiki/Parsec) | `parsec`, `parsecs`, `pc` |
 | `Length` | [Planck length](https://en.wikipedia.org/wiki/Planck_length) | `planck_length` |
 | `Length` | [US rod](https://en.wikipedia.org/wiki/Rod_(unit)) | `perch`, `rod`, `rods` |
@@ -82,7 +82,7 @@ and — where sensible — units allow for [binary prefixes](https://en.wikipedi
 | `LuminousFlux` | [Lumen](https://en.wikipedia.org/wiki/Lumen_(unit)) | `lm`, `lumen`, `lumens` |
 | `LuminousIntensity` | [Candela](https://en.wikipedia.org/wiki/Candela) | `candela`, `candelas`, `cd` |
 | `MagneticFieldStrength` | [Oersted](https://en.wikipedia.org/wiki/Oersted) | `Oe`, `oersted` |
-| `MagneticFlux` | [Maxwell](https://en.wikipedia.org/wiki/Maxwell_(unit)) | `Mx`, `maxwell` |
+| `MagneticFlux` | [Maxwell](https://en.wikipedia.org/wiki/Maxwell_(unit)) | `maxwell`, `Mx` |
 | `MagneticFlux` | [Weber](https://en.wikipedia.org/wiki/Weber_(unit)) | `Wb`, `weber`, `webers` |
 | `MagneticFluxDensity` | [Gauss](https://en.wikipedia.org/wiki/Gauss_(unit)) | `gauss` |
 | `MagneticFluxDensity` | [Tesla](https://en.wikipedia.org/wiki/Tesla_(unit)) | `T`, `tesla`, `teslas` |
@@ -101,33 +101,33 @@ and — where sensible — units allow for [binary prefixes](https://en.wikipedi
 | `Molality` | [Molal](https://en.wikipedia.org/wiki/Molality) | `molal` |
 | `Molarity` | [Molar](https://en.wikipedia.org/wiki/Molar_concentration) | `molar` |
 | `Money` | [Australian dollar](https://en.wikipedia.org/wiki/Australian_dollar) | `A$`, `AUD`, `australian_dollar`, `australian_dollars` |
-| `Money` | [Brazilian real](https://en.wikipedia.org/wiki/Brazilian_real) | `BRL`, `R$`, `brazilian_real`, `brazilian_reals` |
-| `Money` | [Pound sterling](https://en.wikipedia.org/wiki/Pound_sterling) | `GBP`, `british_pound`, `pound_sterling`, `£` |
+| `Money` | [Brazilian real](https://en.wikipedia.org/wiki/Brazilian_real) | `brazilian_real`, `brazilian_reals`, `BRL`, `R$` |
+| `Money` | [Pound sterling](https://en.wikipedia.org/wiki/Pound_sterling) | `british_pound`, `GBP`, `pound_sterling`, `£` |
 | `Money` | [Bulgarian lev](https://en.wikipedia.org/wiki/Bulgarian_lev) | `BGN`, `bulgarian_lev`, `bulgarian_leva` |
 | `Money` | [Canadian dollar](https://en.wikipedia.org/wiki/Canadian_dollar) | `C$`, `CAD`, `canadian_dollar`, `canadian_dollars` |
-| `Money` | [Czech koruna](https://en.wikipedia.org/wiki/Czech_koruna) | `CZK`, `Kč`, `czech_koruna`, `czech_korunas` |
-| `Money` | [Danish krone](https://en.wikipedia.org/wiki/Danish_krone) | `DKK`, `danish_krone`, `danish_kroner` |
-| `Money` | [US dollar](https://en.wikipedia.org/wiki/United_States_dollar) | `$`, `USD`, `dollar`, `dollars` |
+| `Money` | [Czech koruna](https://en.wikipedia.org/wiki/Czech_koruna) | `czech_koruna`, `czech_korunas`, `CZK`, `Kč` |
+| `Money` | [Danish krone](https://en.wikipedia.org/wiki/Danish_krone) | `danish_krone`, `danish_kroner`, `DKK` |
+| `Money` | [US dollar](https://en.wikipedia.org/wiki/United_States_dollar) | `$`, `dollar`, `dollars`, `USD` |
 | `Money` | [Euro](https://en.wikipedia.org/wiki/Euro) | `EUR`, `euro`, `euros`, `€` |
 | `Money` | [Hong Kong dollar](https://en.wikipedia.org/wiki/Hong_Kong_dollar) | `HK$`, `HKD`, `hong_kong_dollar`, `hong_kong_dollars` |
 | `Money` | [Hungarian forint](https://en.wikipedia.org/wiki/Hungarian_forint) | `Ft`, `HUF`, `hungarian_forint`, `hungarian_forints` |
-| `Money` | [Icelandic króna](https://en.wikipedia.org/wiki/Icelandic_króna) | `ISK`, `icelandic_krona`, `icelandic_kronur`, `icelandic_króna`, `icelandic_krónur` |
-| `Money` | [Indian rupee](https://en.wikipedia.org/wiki/Indian_rupee) | `INR`, `indian_rupee`, `indian_rupees`, `₹` |
-| `Money` | [Indonesian rupiah](https://en.wikipedia.org/wiki/Indonesian_rupiah) | `IDR`, `Rp`, `indonesian_rupiah`, `indonesian_rupiahs` |
-| `Money` | [Israeli new shekel](https://en.wikipedia.org/wiki/Israeli_new_shekel) | `ILS`, `NIS`, `israeli_new_shekel`, `israeli_new_shekels`, `₪` |
-| `Money` | [Malaysian ringgit](https://en.wikipedia.org/wiki/Malaysian_ringgit) | `MYR`, `RM`, `malaysian_ringgit`, `malaysian_ringgits` |
-| `Money` | [New Zealand dollar](https://en.wikipedia.org/wiki/New_Zealand_dollar) | `NZ$`, `NZD`, `new_zealand_dollar`, `new_zealand_dollars` |
+| `Money` | [Icelandic króna](https://en.wikipedia.org/wiki/Icelandic_króna) | `icelandic_krona`, `icelandic_kronur`, `icelandic_króna`, `icelandic_krónur`, `ISK` |
+| `Money` | [Indian rupee](https://en.wikipedia.org/wiki/Indian_rupee) | `indian_rupee`, `indian_rupees`, `INR`, `₹` |
+| `Money` | [Indonesian rupiah](https://en.wikipedia.org/wiki/Indonesian_rupiah) | `IDR`, `indonesian_rupiah`, `indonesian_rupiahs`, `Rp` |
+| `Money` | [Israeli new shekel](https://en.wikipedia.org/wiki/Israeli_new_shekel) | `ILS`, `israeli_new_shekel`, `israeli_new_shekels`, `NIS`, `₪` |
+| `Money` | [Malaysian ringgit](https://en.wikipedia.org/wiki/Malaysian_ringgit) | `malaysian_ringgit`, `malaysian_ringgits`, `MYR`, `RM` |
+| `Money` | [New Zealand dollar](https://en.wikipedia.org/wiki/New_Zealand_dollar) | `new_zealand_dollar`, `new_zealand_dollars`, `NZ$`, `NZD` |
 | `Money` | [Norwegian krone](https://en.wikipedia.org/wiki/Norwegian_krone) | `NOK`, `norwegian_krone`, `norwegian_kroner` |
-| `Money` | [Philippine peso](https://en.wikipedia.org/wiki/Philippine_peso) | `PHP`, `philippine_peso`, `philippine_pesos`, `₱` |
+| `Money` | [Philippine peso](https://en.wikipedia.org/wiki/Philippine_peso) | `philippine_peso`, `philippine_pesos`, `PHP`, `₱` |
 | `Money` | [Polish złoty](https://en.wikipedia.org/wiki/Polish_złoty) | `PLN`, `polish_zloty`, `polish_zlotys`, `zł` |
 | `Money` | [Chinese yuan](https://en.wikipedia.org/wiki/Renminbi) | `CNY`, `renminbi`, `元` |
-| `Money` | [Romanian leu](https://en.wikipedia.org/wiki/Romanian_leu) | `RON`, `lei`, `romanian_leu`, `romanian_leus` |
+| `Money` | [Romanian leu](https://en.wikipedia.org/wiki/Romanian_leu) | `lei`, `romanian_leu`, `romanian_leus`, `RON` |
 | `Money` | [Singapore dollar](https://en.wikipedia.org/wiki/Singapore_dollar) | `S$`, `SGD`, `singapore_dollar`, `singapore_dollars` |
-| `Money` | [South African rand](https://en.wikipedia.org/wiki/South_African_rand) | `ZAR`, `south_african_rand` |
+| `Money` | [South African rand](https://en.wikipedia.org/wiki/South_African_rand) | `south_african_rand`, `ZAR` |
 | `Money` | [South Korean won](https://en.wikipedia.org/wiki/South_Korean_won) | `KRW`, `south_korean_won`, `south_korean_wons`, `₩` |
 | `Money` | [Swedish krona](https://en.wikipedia.org/wiki/Swedish_krona) | `SEK`, `swedish_krona`, `swedish_kronor` |
 | `Money` | [Swiss franc](https://en.wikipedia.org/wiki/Swiss_franc) | `CHF`, `swiss_franc`, `swiss_francs` |
-| `Money` | [Thai baht](https://en.wikipedia.org/wiki/Thai_baht) | `THB`, `thai_baht`, `thai_bahts`, `฿` |
+| `Money` | [Thai baht](https://en.wikipedia.org/wiki/Thai_baht) | `thai_baht`, `thai_bahts`, `THB`, `฿` |
 | `Money` | [Turkish lira](https://en.wikipedia.org/wiki/Turkish_lira) | `TRY`, `turkish_lira`, `turkish_liras`, `₺` |
 | `Money` | [Japanese yen](https://en.wikipedia.org/wiki/Japanese_yen) | `JPY`, `yen`, `yens`, `¥`, `円` |
 | `Person` | Person | `capita`, `people`, `person`, `persons` |
@@ -141,7 +141,7 @@ and — where sensible — units allow for [binary prefixes](https://en.wikipedi
 | `Pressure` | [Inch of mercury](https://en.wikipedia.org/wiki/Inch_of_mercury) | `inHg` |
 | `Pressure` | [Millimeter of mercury](https://en.wikipedia.org/wiki/Millimeter_of_mercury) | `mmHg` |
 | `Pressure` | [Pascal](https://en.wikipedia.org/wiki/Pascal_(unit)) | `Pa`, `pascal`, `pascals` |
-| `Pressure` | [Pound-force per square inch](https://en.wikipedia.org/wiki/Pounds_per_square_inch) | `PSI`, `psi` |
+| `Pressure` | [Pound-force per square inch](https://en.wikipedia.org/wiki/Pounds_per_square_inch) | `psi`, `PSI` |
 | `Pressure` | [Torr](https://en.wikipedia.org/wiki/Torr) | `torr` |
 | `Scalar` | [Billion](https://en.wikipedia.org/wiki/Billion) | `billion` |
 | `Scalar` | dozen | `dozen` |
@@ -183,7 +183,7 @@ and — where sensible — units allow for [binary prefixes](https://en.wikipedi
 | `Volume` | [US fluid ounce](https://en.wikipedia.org/wiki/Fluid_ounce) | `floz`, `fluidounce`, `fluidounces` |
 | `Volume` | [US liquid gallon](https://en.wikipedia.org/wiki/Gallon) | `gal`, `gallon`, `gallons` |
 | `Volume` | [US hogshead](https://en.wikipedia.org/wiki/Hogshead) | `hogshead`, `hogsheads` |
-| `Volume` | [Litre](https://en.wikipedia.org/wiki/Litre) | `L`, `l`, `liter`, `liters`, `litre`, `litres` |
+| `Volume` | [Litre](https://en.wikipedia.org/wiki/Litre) | `l`, `L`, `liter`, `liters`, `litre`, `litres` |
 | `Volume` | [US liquid pint](https://en.wikipedia.org/wiki/Pint) | `pint`, `pints` |
 | `Volume` | [Swimming pool](https://en.wikipedia.org/wiki/Olympic-size_swimming_pool) | `swimmingpool` |
 | `Volume` | [US tablespoon](https://en.wikipedia.org/wiki/Tablespoon) | `tablespoon`, `tablespoons`, `tbsp` |

+ 18 - 0
numbat-cli/src/completer.rs

@@ -8,6 +8,7 @@ use rustyline::{
 
 pub struct NumbatCompleter {
     pub context: Arc<Mutex<Context>>,
+    pub modules: Vec<String>,
 }
 
 impl Completer for NumbatCompleter {
@@ -19,6 +20,23 @@ impl Completer for NumbatCompleter {
         pos: usize,
         _: &rustyline::Context<'_>,
     ) -> rustyline::Result<(usize, Vec<Self::Candidate>)> {
+        if line.starts_with("use ") {
+            return Ok((
+                0,
+                self.modules
+                    .iter()
+                    .map(|m| {
+                        let line = format!("use {m}");
+                        Pair {
+                            display: m.to_string(),
+                            replacement: line,
+                        }
+                    })
+                    .filter(|p| p.replacement.starts_with(line))
+                    .collect(),
+            ));
+        }
+
         let (pos_word, word_part) = extract_word(line, pos, None, |c| {
             // TODO: we could use is_identifier_char here potentially
             match c {

+ 1 - 0
numbat-cli/src/main.rs

@@ -251,6 +251,7 @@ impl Cli {
         rl.set_helper(Some(NumbatHelper {
             completer: NumbatCompleter {
                 context: self.context.clone(),
+                modules: self.context.lock().unwrap().list_modules().collect(),
             },
             highlighter: NumbatHighlighter {
                 context: self.context.clone(),

+ 1 - 0
numbat/Cargo.toml

@@ -28,6 +28,7 @@ unicode-width = "0.1.10"
 libc = "0.2.147"
 rust-embed = { version = "8.0.0", features = ["interpolate-folder-path"] }
 num-format = "0.4.4"
+walkdir = "2"
 
 [dev-dependencies]
 approx = "0.5"

+ 2 - 3
numbat/examples/inspect.rs

@@ -14,9 +14,8 @@ fn main() {
         .sorted_by_key(|(u, (_, m))| (m.readable_type.to_string(), u.to_lowercase()))
     {
         let mut names = unit_metadata.aliases;
-        names.sort_by_key(|n| n.to_lowercase());
-        names.sort();
-        let names = names.join("`, `");
+        names.sort_by_key(|(n, _)| n.to_lowercase());
+        let names = names.iter().map(|(n, _)| n).join("`, `");
 
         let url = unit_metadata.url;
         let name = unit_metadata.name.unwrap_or(unit_name.clone());

+ 7 - 4
numbat/src/bytecode_interpreter.rs

@@ -1,6 +1,7 @@
 use std::collections::HashMap;
 
 use crate::ast::ProcedureKind;
+use crate::decorator::Decorator;
 use crate::interpreter::{
     Interpreter, InterpreterResult, InterpreterSettings, Result, RuntimeError,
 };
@@ -247,8 +248,7 @@ impl BytecodeInterpreter {
             }
             Statement::DefineBaseUnit(unit_name, decorators, readable_type, type_) => {
                 let aliases = decorator::name_and_aliases(unit_name, decorators)
-                    .map(|(name, _)| name)
-                    .cloned()
+                    .map(|(name, ap)| (name.clone(), ap))
                     .collect();
 
                 self.vm
@@ -261,6 +261,8 @@ impl BytecodeInterpreter {
                             aliases,
                             name: decorator::name(decorators),
                             url: decorator::url(decorators),
+                            binary_prefixes: decorators.contains(&Decorator::BinaryPrefixes),
+                            metric_prefixes: decorators.contains(&Decorator::MetricPrefixes),
                         },
                     )
                     .map_err(RuntimeError::UnitRegistryError)?;
@@ -276,8 +278,7 @@ impl BytecodeInterpreter {
             }
             Statement::DefineDerivedUnit(unit_name, expr, decorators, readable_type, type_) => {
                 let aliases = decorator::name_and_aliases(unit_name, decorators)
-                    .map(|(name, _)| name)
-                    .cloned()
+                    .map(|(name, ap)| (name.clone(), ap))
                     .collect();
 
                 let constant_idx = self
@@ -295,6 +296,8 @@ impl BytecodeInterpreter {
                         aliases,
                         name: decorator::name(decorators),
                         url: decorator::url(decorators),
+                        binary_prefixes: decorators.contains(&Decorator::BinaryPrefixes),
+                        metric_prefixes: decorators.contains(&Decorator::MetricPrefixes),
                     },
                 ); // TODO: there is some asymmetry here because we do not introduce identifiers for base units
 

+ 3 - 0
numbat/src/keywords.rs

@@ -25,8 +25,11 @@ pub const KEYWORDS: &[&str] = &[
     "metric_prefixes",
     "binary_prefixes",
     "aliases",
+    "name",
+    "url",
     // procedures
     "print(",
     "assert_eq(",
+    "assert(",
     "type(",
 ];

+ 30 - 2
numbat/src/lib.rs

@@ -175,6 +175,18 @@ impl Context {
     }
 
     pub fn get_completions_for<'a>(&self, word_part: &'a str) -> impl Iterator<Item = String> + 'a {
+        const COMMON_METRIC_PREFIXES: &[&str] = &[
+            "pico", "nano", "micro", "milli", "centi", "kilo", "mega", "giga", "tera",
+        ];
+
+        let metric_prefixes: Vec<_> = COMMON_METRIC_PREFIXES
+            .iter()
+            .filter(|prefix| {
+                word_part.starts_with(*prefix)
+                    || (!word_part.is_empty() && prefix.starts_with(word_part))
+            })
+            .collect();
+
         let mut words: Vec<_> = KEYWORDS.iter().map(|k| k.to_string()).collect();
 
         {
@@ -190,9 +202,20 @@ impl Context {
                 words.push(dimension.clone());
             }
 
-            for unit_names in self.unit_names() {
-                for unit in unit_names {
+            for (_, (_, meta)) in self.unit_representations() {
+                for (unit, accepts_prefix) in meta.aliases {
                     words.push(unit.clone());
+
+                    // Add some of the common long prefixes for units that accept them.
+                    // We do not add all possible prefixes here in order to keep the
+                    // number of completions to a reasonable size. Also, we do not add
+                    // short prefixes for units that accept them, as that leads to lots
+                    // and lots of 2-3 character words.
+                    if accepts_prefix.long && meta.metric_prefixes {
+                        for prefix in &metric_prefixes {
+                            words.push(format!("{prefix}{unit}"));
+                        }
+                    }
                 }
             }
         }
@@ -203,6 +226,11 @@ impl Context {
         words.into_iter().filter(move |w| w.starts_with(word_part))
     }
 
+    pub fn list_modules(&self) -> impl Iterator<Item = String> {
+        let modules = self.resolver.get_importer().list_modules();
+        modules.into_iter().map(|m| m.0.join("::"))
+    }
+
     pub fn dimension_registry(&self) -> &DimensionRegistry {
         self.typechecker.registry()
     }

+ 62 - 0
numbat/src/module_importer.rs

@@ -1,4 +1,5 @@
 use std::{
+    ffi::OsStr,
     fs,
     path::{Path, PathBuf},
 };
@@ -9,6 +10,7 @@ use crate::resolver::ModulePath;
 
 pub trait ModuleImporter: Send + Sync {
     fn import(&self, path: &ModulePath) -> Option<(String, Option<PathBuf>)>;
+    fn list_modules(&self) -> Vec<ModulePath>;
 }
 
 #[derive(Debug, Clone, Default)]
@@ -18,6 +20,10 @@ impl ModuleImporter for NullImporter {
     fn import(&self, _: &ModulePath) -> Option<(String, Option<PathBuf>)> {
         None
     }
+
+    fn list_modules(&self) -> Vec<ModulePath> {
+        vec![]
+    }
 }
 
 #[derive(Debug, Clone, Default)]
@@ -48,6 +54,39 @@ impl ModuleImporter for FileSystemImporter {
 
         None
     }
+
+    fn list_modules(&self) -> Vec<ModulePath> {
+        use walkdir::WalkDir;
+        let mut modules = vec![];
+        for root_path in &self.root_paths {
+            for entry in WalkDir::new(root_path)
+                .follow_links(true)
+                .follow_root_links(false)
+                .into_iter()
+            {
+                if let Ok(entry) = entry {
+                    let path = entry.path();
+                    if path.is_file() && path.extension() == Some(OsStr::new("nbt")) {
+                        if let Ok(relative_path) = path.strip_prefix(root_path) {
+                            let components: Vec<String> = relative_path
+                                .components()
+                                .into_iter()
+                                .map(|c| {
+                                    c.as_os_str()
+                                        .to_string_lossy()
+                                        .trim_end_matches(".nbt")
+                                        .to_string()
+                                })
+                                .collect();
+
+                            modules.push(ModulePath(components));
+                        }
+                    }
+                }
+            }
+        }
+        modules
+    }
 }
 
 #[derive(RustEmbed)]
@@ -77,6 +116,19 @@ impl ModuleImporter for BuiltinModuleImporter {
                 (content, Some(user_facing_path))
             })
     }
+
+    fn list_modules(&self) -> Vec<ModulePath> {
+        BuiltinAssets::iter()
+            .map(|path| {
+                ModulePath(
+                    path.trim_end_matches(".nbt")
+                        .split("/")
+                        .map(|s| s.to_string())
+                        .collect(),
+                )
+            })
+            .collect()
+    }
 }
 
 pub struct ChainedImporter {
@@ -98,4 +150,14 @@ impl ModuleImporter for ChainedImporter {
             self.fallback.import(path)
         }
     }
+
+    fn list_modules(&self) -> Vec<ModulePath> {
+        let mut modules = self.main.list_modules();
+        modules.extend(self.fallback.list_modules());
+
+        modules.sort();
+        modules.dedup();
+
+        modules
+    }
 }

+ 9 - 1
numbat/src/resolver.rs

@@ -7,7 +7,7 @@ use crate::{
 use codespan_reporting::files::SimpleFiles;
 use thiserror::Error;
 
-#[derive(Debug, Clone, PartialEq, Eq)]
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
 pub struct ModulePath(pub Vec<String>);
 
 impl std::fmt::Display for ModulePath {
@@ -128,6 +128,10 @@ impl Resolver {
 
         self.inlining_pass(&statements)
     }
+
+    pub fn get_importer(&self) -> &dyn ModuleImporter {
+        self.importer.as_ref()
+    }
 }
 
 #[cfg(test)]
@@ -156,6 +160,10 @@ mod tests {
                 _ => None,
             }
         }
+
+        fn list_modules(&self) -> Vec<ModulePath> {
+            unimplemented!()
+        }
     }
 
     #[test]

+ 4 - 1
numbat/src/unit_registry.rs

@@ -1,4 +1,5 @@
 use crate::markup::Markup;
+use crate::prefix_parser::AcceptsPrefix;
 use crate::registry::{BaseRepresentation, BaseRepresentationFactor, Registry, RegistryError};
 use crate::typed_ast::Type;
 use crate::unit::Unit;
@@ -17,9 +18,11 @@ pub type Result<T> = std::result::Result<T, UnitRegistryError>;
 pub struct UnitMetadata {
     pub type_: Type,
     pub readable_type: Markup,
-    pub aliases: Vec<String>,
+    pub aliases: Vec<(String, AcceptsPrefix)>,
     pub name: Option<String>,
     pub url: Option<String>,
+    pub binary_prefixes: bool,
+    pub metric_prefixes: bool,
 }
 
 #[derive(Clone)]