]> git.lizzy.rs Git - rust.git/commitdiff
Move type inlay hint truncation to language server
authorEmil Lauridsen <mine809@gmail.com>
Mon, 18 Nov 2019 17:02:28 +0000 (18:02 +0100)
committerEmil Lauridsen <mine809@gmail.com>
Tue, 19 Nov 2019 16:23:50 +0000 (17:23 +0100)
This commit implements a general truncation framework for HirFormatter
that keeps track of how much has been output so far. This information
can then be used to perform truncation inside the language server,
instead of relying on the client.

Initial support is implemented for truncating types hints using the
maxInlayHintLength server config option. The existing solution in the
VSCode extension has been removed in favor of letting the server
truncate type hints.

crates/ra_hir/src/ty.rs
crates/ra_hir/src/ty/display.rs
crates/ra_ide_api/src/inlay_hints.rs
crates/ra_ide_api/src/lib.rs
crates/ra_lsp_server/src/config.rs
crates/ra_lsp_server/src/main_loop.rs
crates/ra_lsp_server/src/main_loop/handlers.rs
crates/ra_lsp_server/src/world.rs
editors/code/src/commands/inlay_hints.ts
editors/code/src/server.ts

index b7f50b714f750b24431d7b1a4df53f60c7504988..776613c7ce1a6a4e9ad2be5b42ceb8cabb439ad2 100644 (file)
@@ -800,6 +800,10 @@ fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result {
 
 impl HirDisplay for ApplicationTy {
     fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result {
+        if f.should_truncate() {
+            return write!(f, "…");
+        }
+
         match self.ctor {
             TypeCtor::Bool => write!(f, "bool")?,
             TypeCtor::Char => write!(f, "char")?,
@@ -901,6 +905,10 @@ fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result {
 
 impl HirDisplay for ProjectionTy {
     fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result {
+        if f.should_truncate() {
+            return write!(f, "…");
+        }
+
         let trait_name = self
             .associated_ty
             .parent_trait(f.db)
@@ -919,6 +927,10 @@ fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result {
 
 impl HirDisplay for Ty {
     fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result {
+        if f.should_truncate() {
+            return write!(f, "…");
+        }
+
         match self {
             Ty::Apply(a_ty) => a_ty.hir_fmt(f)?,
             Ty::Projection(p_ty) => p_ty.hir_fmt(f)?,
@@ -1001,6 +1013,10 @@ fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result {
 
 impl TraitRef {
     fn hir_fmt_ext(&self, f: &mut HirFormatter<impl HirDatabase>, use_as: bool) -> fmt::Result {
+        if f.should_truncate() {
+            return write!(f, "…");
+        }
+
         self.substs[0].hir_fmt(f)?;
         if use_as {
             write!(f, " as ")?;
@@ -1031,6 +1047,10 @@ fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result {
 
 impl HirDisplay for GenericPredicate {
     fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result {
+        if f.should_truncate() {
+            return write!(f, "…");
+        }
+
         match self {
             GenericPredicate::Implemented(trait_ref) => trait_ref.hir_fmt(f)?,
             GenericPredicate::Projection(projection_pred) => {
index 7910429d72ff5801c3ae2405c55f1b09af2ffa09..9bb3ece6c8db60e301a885d568e7323ca3ae6d49 100644 (file)
@@ -7,15 +7,30 @@
 pub struct HirFormatter<'a, 'b, DB> {
     pub db: &'a DB,
     fmt: &'a mut fmt::Formatter<'b>,
+    buf: String,
+    curr_size: usize,
+    max_size: Option<usize>,
 }
 
 pub trait HirDisplay {
     fn hir_fmt(&self, f: &mut HirFormatter<impl HirDatabase>) -> fmt::Result;
+
     fn display<'a, DB>(&'a self, db: &'a DB) -> HirDisplayWrapper<'a, DB, Self>
     where
         Self: Sized,
     {
-        HirDisplayWrapper(db, self)
+        HirDisplayWrapper(db, self, None)
+    }
+
+    fn display_truncated<'a, DB>(
+        &'a self,
+        db: &'a DB,
+        max_size: Option<usize>,
+    ) -> HirDisplayWrapper<'a, DB, Self>
+    where
+        Self: Sized,
+    {
+        HirDisplayWrapper(db, self, max_size)
     }
 }
 
@@ -41,11 +56,25 @@ pub fn write_joined<T: HirDisplay>(
 
     /// This allows using the `write!` macro directly with a `HirFormatter`.
     pub fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result {
-        fmt::write(self.fmt, args)
+        // We write to a buffer first to track output size
+        self.buf.clear();
+        fmt::write(&mut self.buf, args)?;
+        self.curr_size += self.buf.len();
+
+        // Then we write to the internal formatter from the buffer
+        self.fmt.write_str(&self.buf)
+    }
+
+    pub fn should_truncate(&self) -> bool {
+        if let Some(max_size) = self.max_size {
+            self.curr_size >= max_size
+        } else {
+            false
+        }
     }
 }
 
-pub struct HirDisplayWrapper<'a, DB, T>(&'a DB, &'a T);
+pub struct HirDisplayWrapper<'a, DB, T>(&'a DB, &'a T, Option<usize>);
 
 impl<'a, DB, T> fmt::Display for HirDisplayWrapper<'a, DB, T>
 where
@@ -53,6 +82,12 @@ impl<'a, DB, T> fmt::Display for HirDisplayWrapper<'a, DB, T>
     T: HirDisplay,
 {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        self.1.hir_fmt(&mut HirFormatter { db: self.0, fmt: f })
+        self.1.hir_fmt(&mut HirFormatter {
+            db: self.0,
+            fmt: f,
+            buf: String::with_capacity(20),
+            curr_size: 0,
+            max_size: self.2,
+        })
     }
 }
index 0cd95984835cf0be9b43e6e9c63fd23cb6285959..6cd492b2a04fd151426f79dce475a82d51e25f4d 100644 (file)
@@ -19,10 +19,15 @@ pub struct InlayHint {
     pub label: SmolStr,
 }
 
-pub(crate) fn inlay_hints(db: &RootDatabase, file_id: FileId, file: &SourceFile) -> Vec<InlayHint> {
+pub(crate) fn inlay_hints(
+    db: &RootDatabase,
+    file_id: FileId,
+    file: &SourceFile,
+    max_inlay_hint_length: Option<usize>,
+) -> Vec<InlayHint> {
     file.syntax()
         .descendants()
-        .map(|node| get_inlay_hints(db, file_id, &node).unwrap_or_default())
+        .map(|node| get_inlay_hints(db, file_id, &node, max_inlay_hint_length).unwrap_or_default())
         .flatten()
         .collect()
 }
@@ -31,6 +36,7 @@ fn get_inlay_hints(
     db: &RootDatabase,
     file_id: FileId,
     node: &SyntaxNode,
+    max_inlay_hint_length: Option<usize>,
 ) -> Option<Vec<InlayHint>> {
     let analyzer = SourceAnalyzer::new(db, hir::Source::new(file_id.into(), node), None);
     match_ast! {
@@ -40,7 +46,7 @@ fn get_inlay_hints(
                     return None;
                 }
                 let pat = it.pat()?;
-                Some(get_pat_type_hints(db, &analyzer, pat, false))
+                Some(get_pat_type_hints(db, &analyzer, pat, false, max_inlay_hint_length))
             },
             ast::LambdaExpr(it) => {
                 it.param_list().map(|param_list| {
@@ -48,22 +54,22 @@ fn get_inlay_hints(
                         .params()
                         .filter(|closure_param| closure_param.ascribed_type().is_none())
                         .filter_map(|closure_param| closure_param.pat())
-                        .map(|root_pat| get_pat_type_hints(db, &analyzer, root_pat, false))
+                        .map(|root_pat| get_pat_type_hints(db, &analyzer, root_pat, false, max_inlay_hint_length))
                         .flatten()
                         .collect()
                 })
             },
             ast::ForExpr(it) => {
                 let pat = it.pat()?;
-                Some(get_pat_type_hints(db, &analyzer, pat, false))
+                Some(get_pat_type_hints(db, &analyzer, pat, false, max_inlay_hint_length))
             },
             ast::IfExpr(it) => {
                 let pat = it.condition()?.pat()?;
-                Some(get_pat_type_hints(db, &analyzer, pat, true))
+                Some(get_pat_type_hints(db, &analyzer, pat, true, max_inlay_hint_length))
             },
             ast::WhileExpr(it) => {
                 let pat = it.condition()?.pat()?;
-                Some(get_pat_type_hints(db, &analyzer, pat, true))
+                Some(get_pat_type_hints(db, &analyzer, pat, true, max_inlay_hint_length))
             },
             ast::MatchArmList(it) => {
                 Some(
@@ -71,7 +77,7 @@ fn get_inlay_hints(
                         .arms()
                         .map(|match_arm| match_arm.pats())
                         .flatten()
-                        .map(|root_pat| get_pat_type_hints(db, &analyzer, root_pat, true))
+                        .map(|root_pat| get_pat_type_hints(db, &analyzer, root_pat, true, max_inlay_hint_length))
                         .flatten()
                         .collect(),
                 )
@@ -86,6 +92,7 @@ fn get_pat_type_hints(
     analyzer: &SourceAnalyzer,
     root_pat: ast::Pat,
     skip_root_pat_hint: bool,
+    max_inlay_hint_length: Option<usize>,
 ) -> Vec<InlayHint> {
     let original_pat = &root_pat.clone();
 
@@ -99,7 +106,7 @@ fn get_pat_type_hints(
         .map(|(range, pat_type)| InlayHint {
             range,
             kind: InlayKind::TypeHint,
-            label: pat_type.display(db).to_string().into(),
+            label: pat_type.display_truncated(db, max_inlay_hint_length).to_string().into(),
         })
         .collect()
 }
@@ -209,7 +216,7 @@ struct InnerStruct {}
 }"#,
         );
 
-        assert_debug_snapshot!(analysis.inlay_hints(file_id).unwrap(), @r###"
+        assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###"
         [
             InlayHint {
                 range: [193; 197),
@@ -278,7 +285,7 @@ fn main() {
 }"#,
         );
 
-        assert_debug_snapshot!(analysis.inlay_hints(file_id).unwrap(), @r###"
+        assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###"
         [
             InlayHint {
                 range: [21; 30),
@@ -307,7 +314,7 @@ fn main() {
 }"#,
         );
 
-        assert_debug_snapshot!(analysis.inlay_hints(file_id).unwrap(), @r###"
+        assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###"
         [
             InlayHint {
                 range: [21; 30),
@@ -355,7 +362,7 @@ fn main() {
 }"#,
         );
 
-        assert_debug_snapshot!(analysis.inlay_hints(file_id).unwrap(), @r###"
+        assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###"
         [
             InlayHint {
                 range: [166; 170),
@@ -418,7 +425,7 @@ fn main() {
 }"#,
         );
 
-        assert_debug_snapshot!(analysis.inlay_hints(file_id).unwrap(), @r###"
+        assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###"
         [
             InlayHint {
                 range: [166; 170),
@@ -481,7 +488,7 @@ fn main() {
 }"#,
         );
 
-        assert_debug_snapshot!(analysis.inlay_hints(file_id).unwrap(), @r###"
+        assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###"
         [
             InlayHint {
                 range: [311; 315),
index 110ddcd6265020d7d753a0f5db40e6f825d7cfcf..fcb3da90e484fa2ab35576b81f490363de11f124 100644 (file)
@@ -338,8 +338,14 @@ pub fn file_structure(&self, file_id: FileId) -> Cancelable<Vec<StructureNode>>
     }
 
     /// Returns a list of the places in the file where type hints can be displayed.
-    pub fn inlay_hints(&self, file_id: FileId) -> Cancelable<Vec<InlayHint>> {
-        self.with_db(|db| inlay_hints::inlay_hints(db, file_id, &db.parse(file_id).tree()))
+    pub fn inlay_hints(
+        &self,
+        file_id: FileId,
+        max_inlay_hint_length: Option<usize>,
+    ) -> Cancelable<Vec<InlayHint>> {
+        self.with_db(|db| {
+            inlay_hints::inlay_hints(db, file_id, &db.parse(file_id).tree(), max_inlay_hint_length)
+        })
     }
 
     /// Returns the set of folding ranges.
index 9871a3b37f3958ece204a6b07248c13041e81e8c..8045f3d60d1cb6f3a3e7247040c35fe8f9973d0b 100644 (file)
@@ -29,6 +29,8 @@ pub struct ServerConfig {
 
     pub lru_capacity: Option<usize>,
 
+    pub max_inlay_hint_length: Option<usize>,
+
     /// For internal usage to make integrated tests faster.
     #[serde(deserialize_with = "nullable_bool_true")]
     pub with_sysroot: bool,
@@ -44,6 +46,7 @@ fn default() -> ServerConfig {
             exclude_globs: Vec::new(),
             use_client_watching: false,
             lru_capacity: None,
+            max_inlay_hint_length: None,
             with_sysroot: true,
             feature_flags: FxHashMap::default(),
         }
index 379dab4384f10eaec459cdcbc51ce5504b16f5ab..3e2ac36830a2b7f326c77169ce5c40698a089371 100644 (file)
@@ -123,6 +123,7 @@ pub fn main_loop(
                     .and_then(|it| it.folding_range.as_ref())
                     .and_then(|it| it.line_folding_only)
                     .unwrap_or(false),
+                max_inlay_hint_length: config.max_inlay_hint_length,
             }
         };
 
index 20f9aee138fb540691b83f8bb09f80dfcedcc378..7347a78c7a7992e40b768912c68fdb35a0968ab8 100644 (file)
@@ -870,7 +870,7 @@ pub fn handle_inlay_hints(
     let analysis = world.analysis();
     let line_index = analysis.file_line_index(file_id)?;
     Ok(analysis
-        .inlay_hints(file_id)?
+        .inlay_hints(file_id, world.options.max_inlay_hint_length)?
         .into_iter()
         .map(|api_type| InlayHint {
             label: api_type.label.to_string(),
index 51824e7a352bda91a6da97d46422f6c632e5f3eb..9bdea70c7457c31499e495d80c4e46e24ad633aa 100644 (file)
@@ -28,6 +28,7 @@ pub struct Options {
     pub publish_decorations: bool,
     pub supports_location_link: bool,
     pub line_folding_only: bool,
+    pub max_inlay_hint_length: Option<usize>,
 }
 
 /// `WorldState` is the primary mutable state of the language server
index ffaaaebcb0583b595a13d674c44ffe42c8c01d47..0dbdd94fbacffb019cbcd0fbd9eb1148cdd7873b 100644 (file)
@@ -87,7 +87,7 @@ export class HintsUpdater {
                 range: hint.range,
                 renderOptions: {
                     after: {
-                        contentText: `: ${this.truncateHint(hint.label)}`
+                        contentText: `: ${hint.label}`
                     }
                 }
             }));
@@ -98,18 +98,6 @@ export class HintsUpdater {
         }
     }
 
-    private truncateHint(label: string): string {
-        if (!Server.config.maxInlayHintLength) {
-            return label;
-        }
-
-        let newLabel = label.substring(0, Server.config.maxInlayHintLength);
-        if (label.length > Server.config.maxInlayHintLength) {
-            newLabel += '…';
-        }
-        return newLabel;
-    }
-
     private async queryHints(documentUri: string): Promise<InlayHint[] | null> {
         const request: InlayHintsParams = {
             textDocument: { uri: documentUri }
index a3ef21a1671949dec48dfe8ff7c409be8a34c1e6..7907b70bc5185d0127be9415c36f2fd3e57dc458 100644 (file)
@@ -43,6 +43,7 @@ export class Server {
             initializationOptions: {
                 publishDecorations: true,
                 lruCapacity: Server.config.lruCapacity,
+                maxInlayHintLength: Server.config.maxInlayHintLength,
                 excludeGlobs: Server.config.excludeGlobs,
                 useClientWatching: Server.config.useClientWatching,
                 featureFlags: Server.config.featureFlags