Skip to content

Commit ab1878a

Browse files
committed
rune: Make SourceLine and similar APIs public
1 parent 08c5958 commit ab1878a

File tree

6 files changed

+150
-132
lines changed

6 files changed

+150
-132
lines changed

crates/rune-wasm/src/lib.rs

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -201,11 +201,10 @@ async fn inner_compile(
201201
let span = error.span();
202202

203203
let start = WasmPosition::from(
204-
source.pos_to_utf8_linecol(span.start.into_usize()),
205-
);
206-
let end = WasmPosition::from(
207-
source.pos_to_utf8_linecol(span.end.into_usize()),
204+
source.find_line_column(span.start.into_usize()),
208205
);
206+
let end =
207+
WasmPosition::from(source.find_line_column(span.end.into_usize()));
209208

210209
diagnostics.push(WasmDiagnostic {
211210
kind: WasmDiagnosticKind::Error,
@@ -218,10 +217,10 @@ async fn inner_compile(
218217
LinkerError::MissingFunction { hash, spans } => {
219218
for (span, _) in spans {
220219
let start = WasmPosition::from(
221-
source.pos_to_utf8_linecol(span.start.into_usize()),
220+
source.find_line_column(span.start.into_usize()),
222221
);
223222
let end = WasmPosition::from(
224-
source.pos_to_utf8_linecol(span.end.into_usize()),
223+
source.find_line_column(span.end.into_usize()),
225224
);
226225

227226
diagnostics.push(WasmDiagnostic {
@@ -243,8 +242,8 @@ async fn inner_compile(
243242

244243
if let Some(source) = sources.get(warning.source_id()) {
245244
let start =
246-
WasmPosition::from(source.pos_to_utf8_linecol(span.start.into_usize()));
247-
let end = WasmPosition::from(source.pos_to_utf8_linecol(span.end.into_usize()));
245+
WasmPosition::from(source.find_line_column(span.start.into_usize()));
246+
let end = WasmPosition::from(source.find_line_column(span.end.into_usize()));
248247

249248
diagnostics.push(WasmDiagnostic {
250249
kind: WasmDiagnosticKind::Warning,
@@ -323,12 +322,11 @@ async fn inner_compile(
323322
if let Some(inst) = debug.instruction_at(ip) {
324323
if let Some(source) = sources.get(inst.source_id) {
325324
let start = WasmPosition::from(
326-
source.pos_to_utf8_linecol(inst.span.start.into_usize()),
325+
source.find_line_column(inst.span.start.into_usize()),
327326
);
328327

329-
let end = WasmPosition::from(
330-
source.pos_to_utf8_linecol(inst.span.end.into_usize()),
331-
);
328+
let end =
329+
WasmPosition::from(source.find_line_column(inst.span.end.into_usize()));
332330

333331
diagnostics.push(WasmDiagnostic {
334332
kind: WasmDiagnosticKind::Error,

crates/rune/src/diagnostics/emit.rs

Lines changed: 1 addition & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use crate::hash::Hash;
2222
use crate::runtime::DebugInfo;
2323
use crate::runtime::{DebugInst, Protocol, Unit, VmError, VmErrorAt, VmErrorKind};
2424
use crate::Context;
25-
use crate::{Diagnostics, Source, SourceId, Sources};
25+
use crate::{Diagnostics, SourceId, Sources};
2626

2727
struct StackFrame {
2828
source_id: SourceId,
@@ -419,108 +419,6 @@ impl Unit {
419419
}
420420
}
421421

422-
impl Source {
423-
/// Print formatted diagnostics about a source conveniently.
424-
pub fn source_line(&self, span: Span) -> Option<SourceLine<'_>> {
425-
let (count, column, line, span) = line_for(self, span)?;
426-
427-
Some(SourceLine {
428-
name: self.name(),
429-
count,
430-
column,
431-
line,
432-
span,
433-
})
434-
}
435-
}
436-
437-
/// An extracted source line.
438-
pub struct SourceLine<'a> {
439-
name: &'a str,
440-
count: usize,
441-
column: usize,
442-
line: &'a str,
443-
span: Span,
444-
}
445-
446-
impl SourceLine<'_> {
447-
/// Write a source line to the given output.
448-
pub fn write(&self, o: &mut dyn WriteColor) -> io::Result<()> {
449-
let mut highlight = termcolor::ColorSpec::new();
450-
highlight.set_fg(Some(termcolor::Color::Yellow));
451-
452-
let mut new_line = termcolor::ColorSpec::new();
453-
new_line.set_fg(Some(termcolor::Color::Red));
454-
455-
let line = self.line.trim_end();
456-
let end = self.span.end.into_usize().min(line.len());
457-
458-
let before = &line[0..self.span.start.into_usize()].trim_start();
459-
let inner = &line[self.span.start.into_usize()..end];
460-
let after = &line[end..];
461-
462-
{
463-
let name = self.name;
464-
let column = self.count + 1;
465-
let start = self.column + 1;
466-
let end = start + inner.chars().count();
467-
write!(o, "{name}:{column}:{start}-{end}: ")?;
468-
}
469-
470-
write!(o, "{before}")?;
471-
o.set_color(&highlight)?;
472-
write!(o, "{inner}")?;
473-
o.reset()?;
474-
write!(o, "{after}")?;
475-
476-
if self.span.end != end {
477-
o.set_color(&new_line)?;
478-
write!(o, "\\n")?;
479-
o.reset()?;
480-
}
481-
482-
Ok(())
483-
}
484-
}
485-
486-
/// Get the line number and source line for the given source and span.
487-
pub fn line_for(source: &Source, span: Span) -> Option<(usize, usize, &str, Span)> {
488-
let line_starts = source.line_starts();
489-
490-
let line = match line_starts.binary_search(&span.start.into_usize()) {
491-
Ok(n) => n,
492-
Err(n) => n.saturating_sub(1),
493-
};
494-
495-
let start = *line_starts.get(line)?;
496-
let end = line.checked_add(1)?;
497-
498-
let s = if let Some(end) = line_starts.get(end) {
499-
source.get(start..*end)?
500-
} else {
501-
source.get(start..)?
502-
};
503-
504-
let line_end = span.start.into_usize().saturating_sub(start);
505-
let column = s
506-
.get(..line_end)
507-
.into_iter()
508-
.flat_map(|s| s.chars())
509-
.count();
510-
511-
let start = start.try_into().unwrap();
512-
513-
Some((
514-
line,
515-
column,
516-
s,
517-
Span::new(
518-
span.start.saturating_sub(start),
519-
span.end.saturating_sub(start),
520-
),
521-
))
522-
}
523-
524422
/// Helper to emit diagnostics for a warning.
525423
fn warning_diagnostics_emit<O>(
526424
this: &WarningDiagnostic,

crates/rune/src/indexing/indexer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ impl Indexer<'_, '_> {
384384
.q
385385
.sources
386386
.get(self.source_id)
387-
.map(|s| s.pos_to_utf8_linecol(ast.open.span.start.into_usize()))
387+
.map(|s| s.find_line_column(ast.open.span.start.into_usize()))
388388
.unwrap_or_default();
389389

390390
// 1-indexed as that is what most editors will use

crates/rune/src/languageserver/state.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,8 +123,8 @@ impl StateEncoding {
123123
/// Get line column out of source.
124124
pub(super) fn source_position(&self, source: &Source, at: usize) -> Result<lsp::Position> {
125125
let (l, c) = match self {
126-
StateEncoding::Utf16 => source.pos_to_utf16cu_linecol(at),
127-
StateEncoding::Utf8 => source.pos_to_utf8_linecol(at),
126+
StateEncoding::Utf16 => source.find_utf16cu_line_column(at),
127+
StateEncoding::Utf8 => source.find_line_column(at),
128128
};
129129

130130
Ok(lsp::Position {

crates/rune/src/source.rs

Lines changed: 135 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,18 @@ use core::iter;
1515
use core::ops::Range;
1616
use core::slice;
1717

18+
#[cfg(feature = "emit")]
19+
use std::io;
20+
1821
use crate as rune;
1922
#[cfg(feature = "std")]
2023
use crate::alloc::borrow::Cow;
2124
use crate::alloc::path::Path;
2225
use crate::alloc::prelude::*;
2326
use crate::alloc::{self, Box};
24-
25-
#[cfg(feature = "emit")]
2627
use crate::ast::Span;
28+
#[cfg(feature = "emit")]
29+
use crate::termcolor::{self, WriteColor};
2730

2831
/// Error raised when constructing a source.
2932
#[derive(Debug)]
@@ -176,7 +179,6 @@ impl Source {
176179
}
177180

178181
/// Access all line starts in the source.
179-
#[cfg(feature = "emit")]
180182
pub(crate) fn line_starts(&self) -> &[usize] {
181183
&self.line_starts
182184
}
@@ -219,10 +221,27 @@ impl Source {
219221
self.path.as_deref()
220222
}
221223

222-
/// Convert the given offset to a utf-16 line and character.
223-
#[cfg(feature = "languageserver")]
224-
pub(crate) fn pos_to_utf16cu_linecol(&self, offset: usize) -> (usize, usize) {
225-
let (line, offset, rest) = self.position(offset);
224+
/// Convert the given position to a utf-8 line position in code units.
225+
///
226+
/// A position is a character offset into the source in utf-8 characters.
227+
///
228+
/// Note that utf-8 code units is what you'd count when using the
229+
/// [`str::chars()`] iterator.
230+
pub fn find_line_column(&self, position: usize) -> (usize, usize) {
231+
let (line, offset, rest) = self.position(position);
232+
let col = rest.char_indices().take_while(|&(n, _)| n < offset).count();
233+
(line, col)
234+
}
235+
236+
/// Convert the given position to a utf-16 code units line and character.
237+
///
238+
/// A position is a character offset into the source in utf-16 characters.
239+
///
240+
/// Note that utf-16 code units is what you'd count when iterating over the
241+
/// string in terms of characters as-if they would have been encoded with
242+
/// [`char::encode_utf16()`].
243+
pub fn find_utf16cu_line_column(&self, position: usize) -> (usize, usize) {
244+
let (line, offset, rest) = self.position(position);
226245

227246
let col = rest
228247
.char_indices()
@@ -232,11 +251,19 @@ impl Source {
232251
(line, col)
233252
}
234253

235-
/// Convert the given offset to a utf-16 line and character.
236-
pub fn pos_to_utf8_linecol(&self, offset: usize) -> (usize, usize) {
237-
let (line, offset, rest) = self.position(offset);
238-
let col = rest.char_indices().take_while(|&(n, _)| n < offset).count();
239-
(line, col)
254+
/// Fetch [`SourceLine`] information for the given span.
255+
pub fn source_line(&self, span: Span) -> Option<SourceLine<'_>> {
256+
let (line, column, text, _span) = line_for(self, span)?;
257+
258+
Some(SourceLine {
259+
#[cfg(feature = "emit")]
260+
name: self.name(),
261+
line,
262+
column,
263+
text,
264+
#[cfg(feature = "emit")]
265+
span: _span,
266+
})
240267
}
241268

242269
/// Get the line index for the given byte.
@@ -265,7 +292,7 @@ impl Source {
265292
#[cfg(feature = "emit")]
266293
pub(crate) fn line(&self, span: Span) -> Option<(usize, usize, [&str; 3])> {
267294
let from = span.range();
268-
let (lin, col) = self.pos_to_utf8_linecol(from.start);
295+
let (lin, col) = self.find_line_column(from.start);
269296
let line = self.line_range(lin)?;
270297

271298
let start = from.start.checked_sub(line.start)?;
@@ -320,6 +347,61 @@ impl fmt::Debug for Source {
320347
}
321348
}
322349

350+
/// An extracted source line.
351+
pub struct SourceLine<'a> {
352+
#[cfg(feature = "emit")]
353+
name: &'a str,
354+
/// The line number in the source.
355+
pub line: usize,
356+
/// The column number in the source.
357+
pub column: usize,
358+
/// The text of the span.
359+
pub text: &'a str,
360+
#[cfg(feature = "emit")]
361+
span: Span,
362+
}
363+
364+
impl SourceLine<'_> {
365+
/// Pretty write a source line to the given output.
366+
#[cfg(feature = "emit")]
367+
pub(crate) fn write(&self, o: &mut dyn WriteColor) -> io::Result<()> {
368+
let mut highlight = termcolor::ColorSpec::new();
369+
highlight.set_fg(Some(termcolor::Color::Yellow));
370+
371+
let mut new_line = termcolor::ColorSpec::new();
372+
new_line.set_fg(Some(termcolor::Color::Red));
373+
374+
let text = self.text.trim_end();
375+
let end = self.span.end.into_usize().min(text.len());
376+
377+
let before = &text[0..self.span.start.into_usize()].trim_start();
378+
let inner = &text[self.span.start.into_usize()..end];
379+
let after = &text[end..];
380+
381+
{
382+
let name = self.name;
383+
let line = self.line + 1;
384+
let start = self.column + 1;
385+
let end = start + inner.chars().count();
386+
write!(o, "{name}:{line}:{start}-{end}: ")?;
387+
}
388+
389+
write!(o, "{before}")?;
390+
o.set_color(&highlight)?;
391+
write!(o, "{inner}")?;
392+
o.reset()?;
393+
write!(o, "{after}")?;
394+
395+
if self.span.end != end {
396+
o.set_color(&new_line)?;
397+
write!(o, "\\n")?;
398+
o.reset()?;
399+
}
400+
401+
Ok(())
402+
}
403+
}
404+
323405
/// Holder for the name of a source.
324406
#[derive(Default, Debug, TryClone, PartialEq, Eq)]
325407
enum SourceName {
@@ -331,6 +413,46 @@ enum SourceName {
331413
Name(Box<str>),
332414
}
333415

416+
#[inline(always)]
334417
fn line_starts(source: &str) -> impl Iterator<Item = usize> + '_ {
335418
iter::once(0).chain(source.match_indices('\n').map(|(i, _)| i + 1))
336419
}
420+
421+
/// Get the line number and source line for the given source and span.
422+
fn line_for(source: &Source, span: Span) -> Option<(usize, usize, &str, Span)> {
423+
let line_starts = source.line_starts();
424+
425+
let line = match line_starts.binary_search(&span.start.into_usize()) {
426+
Ok(n) => n,
427+
Err(n) => n.saturating_sub(1),
428+
};
429+
430+
let start = *line_starts.get(line)?;
431+
let end = line.checked_add(1)?;
432+
433+
let s = if let Some(end) = line_starts.get(end) {
434+
source.get(start..*end)?
435+
} else {
436+
source.get(start..)?
437+
};
438+
439+
let line_end = span.start.into_usize().saturating_sub(start);
440+
441+
let column = s
442+
.get(..line_end)
443+
.into_iter()
444+
.flat_map(|s| s.chars())
445+
.count();
446+
447+
let start = start.try_into().unwrap();
448+
449+
Some((
450+
line,
451+
column,
452+
s,
453+
Span::new(
454+
span.start.saturating_sub(start),
455+
span.end.saturating_sub(start),
456+
),
457+
))
458+
}

0 commit comments

Comments
 (0)