microsoft/qdk

Public

mirrored fromhttps://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.23.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

source/language_service/src/completion/text_edits.rs

134lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use crate::{
5 compilation::{Compilation, CompilationKind},
6 qsc_utils::into_range,
7};
8use qsc::{
9 ast::visit::{Visitor, walk_namespace},
10 line_column::{Encoding, Range},
11};
12
13/// Provides information about where auto-imports should be inserted
14/// in the document based on the cursor offset.
15pub(super) struct TextEditRange {
16 /// Location to insert any auto-import text edits at.
17 pub insert_import_at: Option<Range>,
18 /// The indentation level for the auto-import text edits.
19 pub indent: String,
20}
21
22impl TextEditRange {
23 pub fn init(offset: u32, compilation: &Compilation, position_encoding: Encoding) -> Self {
24 let mut finder = StartOfNamespace {
25 offset,
26 start_of_namespace: None,
27 };
28 finder.visit_package(&compilation.user_unit().ast.package);
29
30 let insert_open_at = match &compilation.kind {
31 CompilationKind::OpenProject { .. } => finder.start_of_namespace,
32 // Since notebooks don't typically contain namespace declarations,
33 // open statements should just get before the first non-whitespace
34 // character (i.e. at the top of the cell)
35 CompilationKind::Notebook { .. } => Some(Self::get_first_non_whitespace_in_source(
36 compilation,
37 offset,
38 )),
39 CompilationKind::OpenQASM { .. } => {
40 unreachable!("OpenQASM should not be used for Q# text edits")
41 }
42 };
43
44 let indent = match insert_open_at {
45 Some(start) => Self::get_indent(compilation, start),
46 None => String::new(),
47 };
48
49 let insert_open_range = insert_open_at.map(|o| {
50 into_range(
51 position_encoding,
52 qsc::Span { lo: o, hi: o },
53 &compilation.user_unit().sources,
54 )
55 });
56
57 TextEditRange {
58 insert_import_at: insert_open_range,
59 indent,
60 }
61 }
62
63 fn get_first_non_whitespace_in_source(compilation: &Compilation, package_offset: u32) -> u32 {
64 const QSHARP_MAGIC: &str = "//qsharp";
65 let source = compilation
66 .user_unit()
67 .sources
68 .find_by_offset(package_offset)
69 .expect("source should exist in the user source map");
70
71 // Skip the //qsharp magic if it exists (notebook cells)
72 let start = if let Some(qsharp_magic_start) = source.contents.find(QSHARP_MAGIC) {
73 qsharp_magic_start + QSHARP_MAGIC.len()
74 } else {
75 0
76 };
77
78 let source_after_magic = &source.contents[start..];
79
80 let first = start
81 + source_after_magic
82 .find(|c: char| !c.is_whitespace())
83 .unwrap_or(source_after_magic.len());
84
85 let first = u32::try_from(first).expect("source length should fit into u32");
86
87 source.offset + first
88 }
89
90 fn get_indent(compilation: &Compilation, package_offset: u32) -> String {
91 let source = compilation
92 .user_unit()
93 .sources
94 .find_by_offset(package_offset)
95 .expect("source should exist in the user source map");
96 let source_offset = (package_offset - source.offset)
97 .try_into()
98 .expect("offset can't be converted to uszie");
99 let before_offset = &source.contents[..source_offset];
100 let mut indent = match before_offset.rfind(['{', '\n']) {
101 Some(begin) => {
102 let indent = &before_offset[begin..];
103 indent.strip_prefix('{').unwrap_or(indent)
104 }
105 None => before_offset,
106 }
107 .to_string();
108 if !indent.starts_with('\n') {
109 indent.insert(0, '\n');
110 }
111 indent
112 }
113}
114
115/// Find the start of the namespace that contains the offset.
116struct StartOfNamespace {
117 offset: u32,
118 start_of_namespace: Option<u32>,
119}
120
121impl<'a> Visitor<'a> for StartOfNamespace {
122 fn visit_namespace(&mut self, namespace: &'a qsc::ast::Namespace) {
123 if namespace.span.contains(self.offset) {
124 self.start_of_namespace = None;
125 walk_namespace(self, namespace);
126 }
127 }
128
129 fn visit_item(&mut self, item: &'a qsc::ast::Item) {
130 if self.start_of_namespace.is_none() {
131 self.start_of_namespace = Some(item.span.lo);
132 }
133 }
134}