Skip to content

Commit

Permalink
8274148: can jshell show deprecated classes, methods and fields as st…
Browse files Browse the repository at this point in the history
…rikethrough text?

Reviewed-by: vromero
  • Loading branch information
lahodaj committed Apr 1, 2022
1 parent bab431c commit 9156c0b
Show file tree
Hide file tree
Showing 6 changed files with 654 additions and 53 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2021, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -44,10 +44,15 @@
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
Expand All @@ -63,6 +68,7 @@
import jdk.internal.org.jline.reader.Binding;
import jdk.internal.org.jline.reader.EOFError;
import jdk.internal.org.jline.reader.EndOfFileException;
import jdk.internal.org.jline.reader.Highlighter;
import jdk.internal.org.jline.reader.History;
import jdk.internal.org.jline.reader.LineReader;
import jdk.internal.org.jline.reader.LineReader.Option;
Expand All @@ -79,14 +85,20 @@
import jdk.internal.org.jline.terminal.Size;
import jdk.internal.org.jline.terminal.Terminal;
import jdk.internal.org.jline.terminal.TerminalBuilder;
import jdk.internal.org.jline.utils.AttributedString;
import jdk.internal.org.jline.utils.AttributedStringBuilder;
import jdk.internal.org.jline.utils.AttributedStyle;
import jdk.internal.org.jline.utils.Display;
import jdk.internal.org.jline.utils.NonBlocking;
import jdk.internal.org.jline.utils.NonBlockingInputStreamImpl;
import jdk.internal.org.jline.utils.NonBlockingReader;
import jdk.internal.org.jline.utils.OSUtils;
import jdk.jshell.ExpressionSnippet;
import jdk.jshell.Snippet;
import jdk.jshell.Snippet.SubKind;
import jdk.jshell.SourceCodeAnalysis.Attribute;
import jdk.jshell.SourceCodeAnalysis.CompletionInfo;
import jdk.jshell.SourceCodeAnalysis.Highlight;
import jdk.jshell.VarSnippet;

import static java.nio.charset.StandardCharsets.UTF_8;
Expand All @@ -95,6 +107,7 @@ class ConsoleIOContext extends IOContext {

private static final String HISTORY_LINE_PREFIX = "HISTORY_LINE_";

private final ExecutorService backgroundWork = Executors.newFixedThreadPool(1);
final boolean allowIncompleteInputs;
final JShellTool repl;
final StopDetectingInputStream input;
Expand All @@ -120,79 +133,39 @@ public int readBuffered(byte[] b) throws IOException {
Terminal terminal;
boolean allowIncompleteInputs = Boolean.getBoolean("jshell.test.allow.incomplete.inputs");
Consumer<LineReaderImpl> setupReader = r -> {};
boolean useComplexDeprecationHighlight = false;
if (cmdin != System.in) {
boolean enableHighlighter;
setupReader = r -> {};
if (System.getProperty("test.jdk") != null) {
terminal = new TestTerminal(nonBlockingInput, cmdout);
enableHighlighter = Boolean.getBoolean("test.enable.highlighter");
} else {
Size size = null;
terminal = new ProgrammaticInTerminal(nonBlockingInput, cmdout, interactive,
size);
if (!interactive) {
setupReader = r -> r.unsetOpt(Option.BRACKETED_PASTE);
setupReader = setupReader.andThen(r -> r.unsetOpt(Option.BRACKETED_PASTE));
allowIncompleteInputs = true;
}
enableHighlighter = interactive;
}
setupReader = setupReader.andThen(r -> r.option(Option.DISABLE_HIGHLIGHTER, !enableHighlighter));
input.setInputStream(cmdin);
} else {
terminal = TerminalBuilder.builder().inputStreamWrapper(in -> {
input.setInputStream(in);
return nonBlockingInput;
}).build();
useComplexDeprecationHighlight = !OSUtils.IS_WINDOWS;
}
this.allowIncompleteInputs = allowIncompleteInputs;
originalAttributes = terminal.getAttributes();
Attributes noIntr = new Attributes(originalAttributes);
noIntr.setControlChar(ControlChar.VINTR, 0);
terminal.setAttributes(noIntr);
terminal.enterRawMode();
LineReaderImpl reader = new LineReaderImpl(terminal, "jshell", variables) {
{
//jline can handle the CONT signal on its own, but (currently) requires sun.misc for it
try {
Signal.handle(new Signal("CONT"), new Handler() {
@Override public void handle(Signal sig) {
try {
handleSignal(jdk.internal.org.jline.terminal.Terminal.Signal.CONT);
} catch (Exception ex) {
ex.printStackTrace();
}
}
});
} catch (IllegalArgumentException ignored) {
//the CONT signal does not exist on this platform
}
}
CompletionState completionState = new CompletionState();
@Override
protected boolean doComplete(CompletionType lst, boolean useMenu, boolean prefix) {
return ConsoleIOContext.this.complete(completionState);
}
@Override
public Binding readBinding(KeyMap<Binding> keys, KeyMap<Binding> local) {
completionState.actionCount++;
return super.readBinding(keys, local);
}
@Override
protected boolean insertCloseParen() {
Object oldIndent = getVariable(INDENTATION);
try {
setVariable(INDENTATION, 0);
return super.insertCloseParen();
} finally {
setVariable(INDENTATION, oldIndent);
}
}
@Override
protected boolean insertCloseSquare() {
Object oldIndent = getVariable(INDENTATION);
try {
setVariable(INDENTATION, 0);
return super.insertCloseSquare();
} finally {
setVariable(INDENTATION, oldIndent);
}
}
};
LineReaderImpl reader = new JShellLineReader(terminal, "jshell", variables);

setupReader.accept(reader);
reader.setOpt(Option.DISABLE_EVENT_EXPANSION);
Expand All @@ -205,6 +178,7 @@ protected boolean insertCloseSquare() {
return new ArgumentLine(line, cursor);
});

reader.setHighlighter(new HighlighterImpl(useComplexDeprecationHighlight));
reader.getKeyMaps().get(LineReader.MAIN)
.bind((Widget) () -> fixes(), FIXES_SHORTCUT);
reader.getKeyMaps().get(LineReader.MAIN)
Expand Down Expand Up @@ -296,6 +270,7 @@ public void close() throws IOException {
throw new IOException(ex);
}
input.shutdown();
backgroundWork.shutdown();
}

private Stream<String> toSplitEntries(String entry) {
Expand Down Expand Up @@ -1366,4 +1341,149 @@ private static final class CompletionState {
public List<CompletionTask> todo = Collections.emptyList();
}

private class HighlighterImpl implements Highlighter {

private final boolean useComplexDeprecationHighlight;
private List<UIHighlight> highlights;
private String prevBuffer;

public HighlighterImpl(boolean useComplexDeprecationHighlight) {
this.useComplexDeprecationHighlight = useComplexDeprecationHighlight;
}

@Override
public AttributedString highlight(LineReader reader, String buffer) {
AttributedStringBuilder builder = new AttributedStringBuilder();
List<UIHighlight> highlights;
synchronized (this) {
highlights = this.highlights;
}
int idx = 0;
if (highlights != null) {
for (UIHighlight h : highlights) {
if (h.end <= buffer.length() && h.content.equals(buffer.substring(h.start, h.end))) {
builder.append(buffer.substring(idx, h.start));
builder.append(buffer.substring(h.start, h.end), h.style);
idx = h.end;
}
}
}
builder.append(buffer.substring(idx, buffer.length()));
synchronized (this) {
if (!buffer.equals(prevBuffer)) {
prevBuffer = buffer;
backgroundWork.submit(() -> {
synchronized (HighlighterImpl.this) {
if (!buffer.equals(prevBuffer)) {
return ;
}
}
List<UIHighlight> computedHighlights = new ArrayList<>();
for (Highlight h : repl.analysis.highlights(buffer)) {
computedHighlights.add(new UIHighlight(h.start(), h.end(), buffer.substring(h.start(), h.end()), toAttributedStyle(h.attributes())));
}
synchronized (HighlighterImpl.this) {
if (buffer.equals(prevBuffer)) {
HighlighterImpl.this.highlights = computedHighlights;
}
}
((JShellLineReader) reader).repaint();
});
}
}
return builder.toAttributedString();
}

private AttributedStyle toAttributedStyle(Set<Attribute> attributes) {
AttributedStyle result = AttributedStyle.DEFAULT;
if (attributes.contains(Attribute.DECLARATION)) {
result = result.bold();
}
if (attributes.contains(Attribute.DEPRECATED)) {
result = useComplexDeprecationHighlight ? result.faint().italic()
: result.inverse();
}
if (attributes.contains(Attribute.KEYWORD)) {
result = result.underline();
}
return result;
}

@Override
public void setErrorPattern(Pattern errorPattern) {
}

@Override
public void setErrorIndex(int errorIndex) {
}

record UIHighlight(int start, int end, String content, AttributedStyle style) {}
}

private class JShellLineReader extends LineReaderImpl {

public JShellLineReader(Terminal terminal, String appName, Map<String, Object> variables) {
super(terminal, appName, variables);
//jline can handle the CONT signal on its own, but (currently) requires sun.misc for it
try {
Signal.handle(new Signal("CONT"), new Handler() {
@Override public void handle(Signal sig) {
try {
handleSignal(jdk.internal.org.jline.terminal.Terminal.Signal.CONT);
} catch (Exception ex) {
ex.printStackTrace();
}
}
});
} catch (IllegalArgumentException ignored) {
//the CONT signal does not exist on this platform
}
}

CompletionState completionState = new CompletionState();

@Override
protected boolean doComplete(CompletionType lst, boolean useMenu, boolean prefix) {
return ConsoleIOContext.this.complete(completionState);
}

@Override
public Binding readBinding(KeyMap<Binding> keys, KeyMap<Binding> local) {
completionState.actionCount++;
return super.readBinding(keys, local);
}

@Override
protected boolean insertCloseParen() {
Object oldIndent = getVariable(INDENTATION);
try {
setVariable(INDENTATION, 0);
return super.insertCloseParen();
} finally {
setVariable(INDENTATION, oldIndent);
}
}

@Override
protected boolean insertCloseSquare() {
Object oldIndent = getVariable(INDENTATION);
try {
setVariable(INDENTATION, 0);
return super.insertCloseSquare();
} finally {
setVariable(INDENTATION, oldIndent);
}
}

void repaint() {
try {
lock.lock();
if (isReading()) {
redisplay();
}
} finally {
lock.unlock();
}
}
}
}
44 changes: 43 additions & 1 deletion src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysis.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -27,6 +27,7 @@

import java.util.Collection;
import java.util.List;
import java.util.Set;

/**
* Provides analysis utilities for source code input.
Expand Down Expand Up @@ -170,6 +171,19 @@ public abstract class SourceCodeAnalysis {
*/
public abstract Collection<Snippet> dependents(Snippet snippet);

/**
* Returns a collection of {@code Highlight}s which can be used to color
* the given snippet.
* <p>
* The returned {@code Highlight}s do not overlap, and are sorted by their
* start position.
*
* @param snippet the snippet for which the {@code Highlight}s should be computed
* @return the computed {@code Highlight}s.
* @since 19
*/
public abstract List<Highlight> highlights(String snippet);

/**
* Internal only constructor
*/
Expand Down Expand Up @@ -460,4 +474,32 @@ public interface SnippetWrapper {
*/
int wrappedToSourcePosition(int pos);
}

/**Assigns attributes usable for coloring to spans inside a snippet.
*
* @param start the starting position of the span
* @param end the ending position of the span
* @param attributes the attributes assigned to the span
* @since 19
*/
public record Highlight(int start, int end, Set<Attribute> attributes) {}

/**
* A span attribute which can be used to derive a coloring.
* @since 19
*/
public enum Attribute {
/**
* The span refers to a declaration of an element.
*/
DECLARATION,
/**
* The span refers to a deprecated element.
*/
DEPRECATED,
/**
* The span is a keyword.
*/
KEYWORD;
}
}

1 comment on commit 9156c0b

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.