This is an automated email from the ASF dual-hosted git repository.

vieiro pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/netbeans.git


The following commit(s) were added to refs/heads/master by this push:
     new 1a0954d3f5 Rust output improvements
     new 5f2c2262f6 Merge pull request #5764 from 
vieiro/feature/rust-output-coloring
1a0954d3f5 is described below

commit 1a0954d3f52ec56ee5a94c64a8866bb192e9dade
Author: Antonio <[email protected]>
AuthorDate: Sun Apr 2 12:38:47 2023 +0200

    Rust output improvements
---
 rust/rust.cargo/licenseinfo.xml                    |  29 ++++
 rust/rust.cargo/manifest.mf                        |   2 +-
 rust/rust.cargo/nbproject/project.xml              |  17 ++
 .../org/netbeans/modules/rust/cargo/api/Cargo.java |   1 +
 .../modules/rust/cargo/impl/CargoBuildImpl.java    |  52 +++---
 .../modules/rust/cargo/output/RustConsole.java     | 182 +++++++++++++++++++++
 .../output/RustErrorHyperlinkConvertorFactory.java | 113 +++++++++++++
 .../rust/cargo/output/resources/options.png        | Bin 0 -> 926 bytes
 .../modules/rust/cargo/output/resources/rerun.png  | Bin 0 -> 539 bytes
 .../test/unit/data/rust-output-with-errors-1.txt   | 108 ++++++++++++
 .../output/RustErrorLineConvertorFactoryTest.java  | 112 +++++++++++++
 .../modules/rust/options/api/CargoOptions.java     |   7 +
 12 files changed, 597 insertions(+), 26 deletions(-)

diff --git a/rust/rust.cargo/licenseinfo.xml b/rust/rust.cargo/licenseinfo.xml
new file mode 100644
index 0000000000..e7a9715259
--- /dev/null
+++ b/rust/rust.cargo/licenseinfo.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements.  See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership.  The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied.  See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+<licenseinfo>
+   <fileset>
+        
<file>src/org/netbeans/modules/rust/cargo/output/resources/options.png</file>
+        
<file>src/org/netbeans/modules/rust/cargo/output/resources/rerun.png</file>
+        <license ref="Apache-2.0-ASF" />
+        <comment type="COMMENT_UNSUPPORTED" />
+    </fileset>
+</licenseinfo>
diff --git a/rust/rust.cargo/manifest.mf b/rust/rust.cargo/manifest.mf
index f1cbe21b6a..64d3f4c6c8 100644
--- a/rust/rust.cargo/manifest.mf
+++ b/rust/rust.cargo/manifest.mf
@@ -2,5 +2,5 @@ Manifest-Version: 1.0
 OpenIDE-Module: org.netbeans.modules.rust.cargo
 OpenIDE-Module-Layer: org/netbeans/modules/rust/cargo/layer.xml
 OpenIDE-Module-Localizing-Bundle: 
org/netbeans/modules/rust/cargo/Bundle.properties
-OpenIDE-Module-Specification-Version: 1.11
+OpenIDE-Module-Specification-Version: 1.12
 
diff --git a/rust/rust.cargo/nbproject/project.xml 
b/rust/rust.cargo/nbproject/project.xml
index 804a7bb7e2..693c953866 100644
--- a/rust/rust.cargo/nbproject/project.xml
+++ b/rust/rust.cargo/nbproject/project.xml
@@ -25,6 +25,15 @@
         <data xmlns="http://www.netbeans.org/ns/nb-module-project/3";>
             <code-name-base>org.netbeans.modules.rust.cargo</code-name-base>
             <module-dependencies>
+                <dependency>
+                    
<code-name-base>org.netbeans.api.annotations.common</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <release-version>1</release-version>
+                        <specification-version>1.48</specification-version>
+                    </run-dependency>
+                </dependency>
                 <dependency>
                     <code-name-base>org.netbeans.api.io</code-name-base>
                     <build-prerequisite/>
@@ -159,6 +168,14 @@
                         <specification-version>7.65</specification-version>
                     </run-dependency>
                 </dependency>
+                <dependency>
+                    <code-name-base>org.openide.text</code-name-base>
+                    <build-prerequisite/>
+                    <compile-dependency/>
+                    <run-dependency>
+                        <specification-version>6.88</specification-version>
+                    </run-dependency>
+                </dependency>
                 <dependency>
                     <code-name-base>org.openide.util</code-name-base>
                     <build-prerequisite/>
diff --git a/rust/rust.cargo/src/org/netbeans/modules/rust/cargo/api/Cargo.java 
b/rust/rust.cargo/src/org/netbeans/modules/rust/cargo/api/Cargo.java
index 9276d2612a..8f7456d657 100644
--- a/rust/rust.cargo/src/org/netbeans/modules/rust/cargo/api/Cargo.java
+++ b/rust/rust.cargo/src/org/netbeans/modules/rust/cargo/api/Cargo.java
@@ -36,6 +36,7 @@ public interface Cargo {
      * @param cargotoml The Cargo.toml affected by the cargo.
      * @param commands The array of cargo commands, to be executed one after
      * another.
+     * @param options optional list of options (verbose, for instance).
      * @throws IOException If a problem happens.
      */
     public void cargo(CargoTOML cargotoml, CargoCommand[] commands, String... 
options) throws IOException;
diff --git 
a/rust/rust.cargo/src/org/netbeans/modules/rust/cargo/impl/CargoBuildImpl.java 
b/rust/rust.cargo/src/org/netbeans/modules/rust/cargo/impl/CargoBuildImpl.java
index 1d0ad7f579..7e09758cd1 100644
--- 
a/rust/rust.cargo/src/org/netbeans/modules/rust/cargo/impl/CargoBuildImpl.java
+++ 
b/rust/rust.cargo/src/org/netbeans/modules/rust/cargo/impl/CargoBuildImpl.java
@@ -39,10 +39,10 @@ import org.netbeans.modules.rust.cargo.api.CargoTOML;
 import org.openide.filesystems.FileUtil;
 import org.openide.util.RequestProcessor;
 import org.openide.util.lookup.ServiceProvider;
-import org.openide.windows.IOProvider;
-import org.openide.windows.InputOutput;
 import org.netbeans.modules.rust.cargo.api.Cargo;
 import org.netbeans.modules.rust.cargo.api.RustPackage;
+import org.netbeans.modules.rust.cargo.output.RustConsole;
+import 
org.netbeans.modules.rust.cargo.output.RustErrorHyperlinkConvertorFactory;
 import org.netbeans.modules.rust.options.api.CargoOptions;
 import org.openide.LifecycleManager;
 import org.openide.util.Exceptions;
@@ -66,10 +66,10 @@ public class CargoBuildImpl implements Cargo {
 
         private final CargoTOML cargotoml;
         private final CargoCommand command;
-        private final InputOutput console;
+        private final RustConsole console;
         private final String[] options;
 
-        CargoProcess(CargoTOML cargotoml, CargoCommand command, String[] 
options, InputOutput console) {
+        CargoProcess(CargoTOML cargotoml, CargoCommand command, String[] 
options, RustConsole console) {
             this.cargotoml = cargotoml;
             this.command = command;
             this.console = console;
@@ -93,7 +93,7 @@ public class CargoBuildImpl implements Cargo {
             pb.setExecutable(cargo.toString());
             pb.setArguments(arguments);
 
-            console.getOut().println(String.format("%n$ cargo %s", 
String.join(" ", arguments))); // NOI18N
+            console.printInformationMessage(String.format("%n$ cargo %s", 
String.join(" ", arguments))); // NOI18N
 
             return pb.call();
         }
@@ -111,11 +111,14 @@ public class CargoBuildImpl implements Cargo {
         private final CargoTOML cargotoml;
         private final CargoCommand[] commands;
         private final String[] options;
+        private final RequestProcessor requestProcessor;
+        private RustConsole console;
 
-        SequentialCargoProcesses(CargoTOML cargotoml, CargoCommand[] commands, 
String[] options) {
+        SequentialCargoProcesses(RequestProcessor requestProcessor, CargoTOML 
cargotoml, CargoCommand[] commands, String[] options) {
             this.cargotoml = cargotoml;
             this.commands = commands;
             this.options = options;
+            this.requestProcessor = requestProcessor;
         }
 
         @Override
@@ -127,41 +130,35 @@ public class CargoBuildImpl implements Cargo {
             // Get a proper console for the input/output
             String projectName = cargotoml.getPackageName();
             String commandNames = 
Arrays.stream(commands).map(CargoCommand::getDisplayName).collect(Collectors.joining(","));
 // NOI18N
-            String ioName = String.format("%s (%s)", projectName, 
commandNames); // NOI18N
-            InputOutput console = IOProvider.getDefault().getIO(ioName, false);
-            console.select();
+            String consoleTabName = String.format("%s (%s)", projectName, 
commandNames); // NOI18N
 
-            File workingDirectory = 
FileUtil.toFile(cargotoml.getFileObject()).getParentFile();
-            console.getOut().format("# %s %s%n", // NOI18N
-                    NbBundle.getMessage(CargoBuildImpl.class, 
"MSG_WORKING_DIRECTORY"), 
-                    workingDirectory.getAbsolutePath()); 
-            console.getOut().format("# %s %s%n",  // NOI18N
-                    NbBundle.getMessage(CargoBuildImpl.class, 
"MSG_CARGO_PATH"), 
-                    cargo); 
+            console = new RustConsole(cargotoml, consoleTabName, this::run);
 
             ExecutionDescriptor ed = new ExecutionDescriptor()
-                    .inputOutput(IOProvider.getDefault().getIO(ioName, false))
+                    .controllable(false)
+                    .inputOutput(console.getInputOutput())
                     .inputVisible(true)
                     .frontWindow(false)
                     .frontWindowOnError(true)
-                    .noReset(true)
                     .showProgress(false)
-                    .controllable(true);
+                    .noReset(true)
+                    .errConvertorFactory(new 
RustErrorHyperlinkConvertorFactory(cargotoml, console.getInputOutput()))
+                    ;
 
             int resultCode = 0;
 
             for (CargoCommand command : commands) {
                 CargoProcess process = new CargoProcess(cargotoml, command, 
options, console);
-                ExecutionService service = 
ExecutionService.newService(process, ed, ioName);
+                ExecutionService service = 
ExecutionService.newService(process, ed, consoleTabName);
                 Future<Integer> resultCodeFuture = service.run();
                 try {
                     resultCode = resultCodeFuture.get();
                 } catch (Exception e) {
-                    console.getErr().println(String.format("Cargo execution 
failed: %s%n", e.getMessage()));
+                    console.printErrorMessage(String.format("Cargo execution 
failed: %s%n", e.getMessage()));
                     Exceptions.printStackTrace(e);
                 }
                 if (resultCode != 0) {
-                    console.getErr().println(String.format("Command \"cargo 
%s\" failed with exit status %d", // NOI18N
+                    console.printErrorMessage(String.format("Command \"cargo 
%s\" failed with exit status %d", // NOI18N
                             String.join(" ", command.arguments),
                             resultCode));
                     break;
@@ -170,6 +167,11 @@ public class CargoBuildImpl implements Cargo {
             return resultCode;
         }
 
+        public void run() {
+            LifecycleManager.getDefault().saveAll();
+            requestProcessor.submit(this);
+        }
+
     }
 
     private final RequestProcessor requestProcessor;
@@ -190,11 +192,11 @@ public class CargoBuildImpl implements Cargo {
         if (cargo == null) {
             return;
         }
-        // Let's save stuff just in case
-        LifecycleManager.getDefault().saveAll();
-        requestProcessor.submit(new SequentialCargoProcesses(cargotoml, 
commands, options));
+        SequentialCargoProcesses sequentialCommands = new 
SequentialCargoProcesses(requestProcessor, cargotoml, commands, options);
+        sequentialCommands.run();
     }
 
+
     /**
      * Runs `cargo search [text] --limit 15 --color never`
      */
diff --git 
a/rust/rust.cargo/src/org/netbeans/modules/rust/cargo/output/RustConsole.java 
b/rust/rust.cargo/src/org/netbeans/modules/rust/cargo/output/RustConsole.java
new file mode 100644
index 0000000000..756267c7d7
--- /dev/null
+++ 
b/rust/rust.cargo/src/org/netbeans/modules/rust/cargo/output/RustConsole.java
@@ -0,0 +1,182 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.rust.cargo.output;
+
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.ImageIcon;
+import org.netbeans.api.annotations.common.StaticResource;
+import org.netbeans.modules.rust.cargo.api.CargoTOML;
+import org.netbeans.modules.rust.cargo.impl.CargoBuildImpl;
+import org.netbeans.modules.rust.options.api.CargoOptions;
+import org.openide.filesystems.FileUtil;
+import org.openide.util.ImageUtilities;
+import org.openide.util.NbBundle;
+import org.openide.windows.IOColorLines;
+import org.openide.windows.IOColors;
+import org.openide.windows.IOContainer;
+import org.openide.windows.IOProvider;
+import org.openide.windows.InputOutput;
+
+/**
+ * Responsible for handling the Rust inputOutput.
+ */
[email protected]({
+    "MSG_WORKING_DIRECTORY=Working directory:",
+    "MSG_CARGO_PATH=Cargo: ",
+    "MSG_RERUN=Re-run",
+    "MSG_RERUN_SHORT=Re-runs these same commands again",
+    "MSG_RUST_OPTIONS=Options",
+    "MSG_RUST_OPTIONS_SHORT=Open Rust options panel",
+})
+public final class RustConsole {
+
+    private static final class ReRunAction extends AbstractAction {
+
+        @StaticResource
+        private static final String RERUN_ICON = 
"org/netbeans/modules/rust/cargo/output/resources/rerun.png"; // NOI18N
+
+        private final Runnable reRunCommand;
+
+        private ReRunAction(Runnable reRunCommand) {
+            super(Bundle.MSG_RERUN(), 
ImageUtilities.image2Icon(ImageUtilities.loadImage(RERUN_ICON)));
+            this.reRunCommand = reRunCommand;
+            putValue(Action.SHORT_DESCRIPTION, Bundle.MSG_RERUN_SHORT());
+        }
+
+        @Override
+        public void actionPerformed(ActionEvent e) {
+            reRunCommand.run();
+        }
+
+    }
+    private static final class OptionsAction extends AbstractAction { // #59396
+
+        @StaticResource
+        private static final String OPTIONS_ICON = 
"org/netbeans/modules/rust/cargo/output/resources/options.png"; // NOI18N
+
+        @Override
+        public Object getValue(String key) {
+            if (key.equals(Action.SMALL_ICON)) {
+                return 
ImageUtilities.image2Icon(ImageUtilities.loadImage(OPTIONS_ICON));
+            } else if (key.equals(Action.SHORT_DESCRIPTION)) {
+                return Bundle.MSG_RUST_OPTIONS_SHORT();
+            } else {
+                return super.getValue(key);
+            }
+        }
+
+        public void actionPerformed(ActionEvent e) {
+            CargoOptions.showRustCargoOptions();
+        }
+
+    }
+    private final CargoTOML cargotoml;
+    private final InputOutput inputOutput;
+    private final String name;
+    private final Runnable reRunAction;
+
+    /**
+     * Creates a new RustConsole that prints out Cargo command results.
+     *
+     * @param cargotoml The Cargo.toml for the project.
+     * @param name The name of the console (that name of the tab)
+     * @param reRunCommand an action to be invoked if the user wants to re-run
+     * something, or null.
+     * @throws IOException if an I/O error happens.
+     */
+    public RustConsole(CargoTOML cargotoml, String name, Runnable 
reRunCommand) throws IOException {
+        this.cargotoml = cargotoml;
+        this.name = name;
+        this.reRunAction = reRunCommand;
+
+        ArrayList<Action> actions = new ArrayList<>();
+
+        if (reRunCommand != null) {
+            actions.add(new ReRunAction(reRunCommand));
+        }
+
+        actions.add(new OptionsAction());
+
+        inputOutput = IOProvider.getDefault().getIO(name, false, 
actions.toArray(new Action[0]), IOContainer.getDefault());
+        inputOutput.select();
+        inputOutput.getOut().reset();
+
+        // Print working directory and cargo path
+        File workingDirectory = 
FileUtil.toFile(cargotoml.getFileObject()).getParentFile();
+        Path cargo = CargoOptions.getCargoLocation(false);
+
+        String message = String.format("# %s %s", // NOI18N
+                NbBundle.getMessage(CargoBuildImpl.class, 
"MSG_WORKING_DIRECTORY"),
+                workingDirectory.getAbsolutePath());
+        printInformationMessage(message);
+        message = String.format("# %s %s", // NOI18N
+                NbBundle.getMessage(CargoBuildImpl.class, "MSG_CARGO_PATH"),
+                cargo);
+        printInformationMessage(message);
+    }
+
+    /**
+     * Returns the underlying InputOutput object used for printing out 
messages.
+     *
+     * @return The InputOutput object.
+     */
+    public InputOutput getInputOutput() {
+        return inputOutput;
+    }
+
+    /**
+     * The name of this console.
+     *
+     * @return As shown in the Output tab.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Prints a (possibly colored) information message.
+     *
+     * @param message The message to print
+     * @throws IOException On error
+     */
+    public void printInformationMessage(String message) throws IOException {
+        if (IOColorLines.isSupported(inputOutput)) {
+            IOColorLines.println(inputOutput, message, 
IOColors.getColor(inputOutput, IOColors.OutputType.LOG_DEBUG));
+        } else {
+            inputOutput.getOut().println(message);
+        }
+    }
+
+    /**
+     * Prints a (possibly colored) error message.
+     *
+     * @param message The message to print
+     * @throws IOException On error
+     */
+    public void printErrorMessage(String message) throws IOException {
+        inputOutput.getErr().println(message);
+    }
+
+}
diff --git 
a/rust/rust.cargo/src/org/netbeans/modules/rust/cargo/output/RustErrorHyperlinkConvertorFactory.java
 
b/rust/rust.cargo/src/org/netbeans/modules/rust/cargo/output/RustErrorHyperlinkConvertorFactory.java
new file mode 100644
index 0000000000..a13acebfc2
--- /dev/null
+++ 
b/rust/rust.cargo/src/org/netbeans/modules/rust/cargo/output/RustErrorHyperlinkConvertorFactory.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.rust.cargo.output;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.netbeans.api.extexecution.ExecutionDescriptor;
+import org.netbeans.api.extexecution.print.ConvertedLine;
+import org.netbeans.api.extexecution.print.LineConvertor;
+import org.netbeans.modules.rust.cargo.api.CargoTOML;
+import org.openide.cookies.LineCookie;
+import org.openide.filesystems.FileObject;
+import org.openide.text.Line;
+import org.openide.windows.InputOutput;
+import org.openide.windows.OutputEvent;
+import org.openide.windows.OutputListener;
+
+/**
+ * An LineConvertorFactory that adds hyperlinks in Rust error messages to go
+ * directly to the origin of a problem.
+ */
+public class RustErrorHyperlinkConvertorFactory implements OutputListener, 
LineConvertor, ExecutionDescriptor.LineConvertorFactory {
+
+    private static final Pattern RUST_ERROR_HYPERLINK = 
Pattern.compile("^[\\s]+--> ([^:]+):([\\d]+):([\\d]+)$");
+
+    static Matcher matchesSourcePosition(String line) {
+        return RUST_ERROR_HYPERLINK.matcher(line);
+    }
+
+    private final CargoTOML cargo;
+    private final InputOutput inputOutput;
+
+    public RustErrorHyperlinkConvertorFactory(CargoTOML cargo, InputOutput 
inputOutput) {
+        this.cargo = cargo;
+        this.inputOutput = inputOutput;
+    }
+
+    // ExecutionDescriptor.LineConverterFactory
+    @Override
+    public LineConvertor newLineConvertor() {
+        return this;
+    }
+
+    // LineConverter
+    @Override
+    public List<ConvertedLine> convert(String line) {
+        Matcher errorLocationLineMatcher = matchesSourcePosition(line);
+        if (errorLocationLineMatcher.matches()) {
+            ConvertedLine cline = ConvertedLine.forText(line, this);
+            return Collections.singletonList(cline);
+        }
+        return null;
+    }
+
+    // OutputListener
+    @Override
+    public void outputLineSelected(OutputEvent ev) {
+    }
+
+    // OutputListener
+    @Override
+    public void outputLineCleared(OutputEvent ev) {
+    }
+
+    // OutputListener
+    @Override
+    public void outputLineAction(OutputEvent ev) {
+        // Invoked to show a given position in the source code
+        Matcher m = matchesSourcePosition(ev.getLine());
+        if (m.matches()) {
+            String dirAndFile = m.group(1);
+            int lineNumber = Integer.parseInt(m.group(2));
+            int column = Integer.parseInt(m.group(3));
+            openFile(dirAndFile, lineNumber, column);
+        }
+    }
+
+    private void openFile(String dirAndFile, int lineNumber, int column) {
+        FileObject projectDirectory = cargo.getFileObject().getParent();
+        FileObject file = projectDirectory.getFileObject(dirAndFile);
+
+        if (file != null) {
+            LineCookie lineCookie = file.getLookup().lookup(LineCookie.class);
+            if (lineCookie != null) {
+                Line theLine = lineCookie.getLineSet().getCurrent(lineNumber - 
1);
+                if (theLine != null) {
+                    theLine.show(Line.ShowOpenType.OPEN,
+                            Line.ShowVisibilityType.FOCUS,
+                            column - 1);
+                }
+            }
+        }
+    }
+
+}
diff --git 
a/rust/rust.cargo/src/org/netbeans/modules/rust/cargo/output/resources/options.png
 
b/rust/rust.cargo/src/org/netbeans/modules/rust/cargo/output/resources/options.png
new file mode 100644
index 0000000000..d532dedd64
Binary files /dev/null and 
b/rust/rust.cargo/src/org/netbeans/modules/rust/cargo/output/resources/options.png
 differ
diff --git 
a/rust/rust.cargo/src/org/netbeans/modules/rust/cargo/output/resources/rerun.png
 
b/rust/rust.cargo/src/org/netbeans/modules/rust/cargo/output/resources/rerun.png
new file mode 100644
index 0000000000..1e06be9c9e
Binary files /dev/null and 
b/rust/rust.cargo/src/org/netbeans/modules/rust/cargo/output/resources/rerun.png
 differ
diff --git a/rust/rust.cargo/test/unit/data/rust-output-with-errors-1.txt 
b/rust/rust.cargo/test/unit/data/rust-output-with-errors-1.txt
new file mode 100644
index 0000000000..0d568d72c4
--- /dev/null
+++ b/rust/rust.cargo/test/unit/data/rust-output-with-errors-1.txt
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// This file used to detect errors in Rust build output
+
+$ cargo run
+Hello, world!: Another thing!!
+warning: unused variable: `z`
+ --> src/main.rs:8:17
+  |
+8 |             let z="Hello, world!";
+  |                 ^ help: if this is intentional, prefix it with an 
underscore: `_z`
+  |
+  = note: `#[warn(unused_variables)]` on by default
+
+warning: struct `Val` is never constructed
+  --> src/main.rs:17:12
+   |
+17 |     struct Val {
+   |            ^^^
+   |
+   = note: `#[warn(dead_code)]` on by default
+
+warning: function `private_function` is never used
+  --> src/main.rs:29:8
+   |
+29 |     fn private_function() {
+   |        ^^^^^^^^^^^^^^^^
+
+warning: function `function` is never used
+  --> src/main.rs:34:12
+   |
+34 |     pub fn function() {
+   |            ^^^^^^^^
+
+warning: function `indirect_access` is never used
+  --> src/main.rs:40:12
+   |
+40 |     pub fn indirect_access() {
+   |            ^^^^^^^^^^^^^^^
+
+warning: function `call_public_function_in_my_mod` is never used
+  --> src/main.rs:76:12
+   |
+76 |     pub fn call_public_function_in_my_mod() {
+   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+warning: function `public_function_in_crate` is never used
+  --> src/main.rs:84:19
+   |
+84 |     pub(crate) fn public_function_in_crate() {
+   |                   ^^^^^^^^^^^^^^^^^^^^^^^^
+
+warning: associated function `value` is never used
+  --> src/main.rs:22:12
+   |
+22 |         fn value(&self) -> &f64 {
+   |            ^^^^^
+
+warning: function `whatever` is never used
+ --> src/main.rs:7:12
+  |
+7 |         fn whatever() {
+  |            ^^^^^^^^
+
+warning: function `function` is never used
+  --> src/main.rs:47:16
+   |
+47 |         pub fn function() {
+   |                ^^^^^^^^
+
+warning: function `public_function_in_my_mod` is never used
+  --> src/main.rs:58:34
+   |
+58 |         pub(in crate::my_mod) fn public_function_in_my_mod() {
+   |                                  ^^^^^^^^^^^^^^^^^^^^^^^^^
+
+warning: function `public_function_in_nested` is never used
+  --> src/main.rs:65:22
+   |
+65 |         pub(self) fn public_function_in_nested() {
+   |                      ^^^^^^^^^^^^^^^^^^^^^^^^^
+
+warning: function `public_function_in_super_mod` is never used
+  --> src/main.rs:71:23
+   |
+71 |         pub(super) fn public_function_in_super_mod() {
+   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+warning: `rust1` (bin "rust1") generated 13 warnings
+    Finished dev [unoptimized + debuginfo] target(s) in 0.04s
+     Running `target/debug/rust1`
diff --git 
a/rust/rust.cargo/test/unit/src/org/netbeans/modules/rust/cargo/output/RustErrorLineConvertorFactoryTest.java
 
b/rust/rust.cargo/test/unit/src/org/netbeans/modules/rust/cargo/output/RustErrorLineConvertorFactoryTest.java
new file mode 100644
index 0000000000..75ddd2a454
--- /dev/null
+++ 
b/rust/rust.cargo/test/unit/src/org/netbeans/modules/rust/cargo/output/RustErrorLineConvertorFactoryTest.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.rust.cargo.output;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.regex.Matcher;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.netbeans.junit.NbTestCase;
+
+/**
+ *
+ * @author antonio
+ */
+public class RustErrorLineConvertorFactoryTest extends NbTestCase {
+
+    public RustErrorLineConvertorFactoryTest() {
+        super("RustErrorLineConvertorFactoryTest");
+    }
+
+    @BeforeClass
+    public static void setUpClass() {
+    }
+
+    @AfterClass
+    public static void tearDownClass() {
+    }
+
+    @Before
+    public void setUp() {
+    }
+
+    @After
+    public void tearDown() {
+    }
+
+    @Test
+    public void testShouldDetectHyperlinks() throws Exception {
+        System.out.println("testShouldDetectHyperlinks");
+
+        // Given some error lines with a proper source position
+        String[] lines = {
+            " --> src/main.rs:8:17",
+            "  --> src/main.rs:17:12",
+            "  --> src/main.rs:29:8"
+        };
+
+        for (String line : lines) {
+            // When we try to find the position
+            Matcher matcher = 
RustErrorHyperlinkConvertorFactory.matchesSourcePosition(line);
+
+            // Then the position is found
+            assertTrue("This line could not be detected: " + line, 
matcher.matches());
+            assertEquals(
+                    "src/main.rs", matcher.group(1));
+            int nLine = Integer.parseInt(matcher.group(2));
+            assertEquals("" + nLine, matcher.group(2));
+            int nCol = Integer.parseInt(matcher.group(3));
+            assertEquals("" + nCol, matcher.group(3));
+        }
+    }
+
+    @Test
+    public void testShouldDetectHyperlinksInTestFile() throws Exception {
+        System.out.println("testShouldDetectHyperlinksInTestFile");
+        File test = new File(getDataDir(), "rust-output-with-errors-1.txt");
+
+        int hyperlinkCount = 0;
+        int lineNumber = 0;
+
+        try (BufferedReader reader = new BufferedReader(new 
InputStreamReader(new FileInputStream(test), StandardCharsets.UTF_8))) {
+            do {
+                String line = reader.readLine();
+                if (line == null) {
+                    break;
+                }
+                lineNumber ++;
+                if 
(RustErrorHyperlinkConvertorFactory.matchesSourcePosition(line).matches()) {
+                    hyperlinkCount++;
+                    // DEBUG:
+                    // System.out.format("LINE %3d:'%s'%n", lineNumber, line);
+                }
+            } while (true);
+        }
+        assertEquals(13, hyperlinkCount);
+
+    }
+
+}
diff --git 
a/rust/rust.options/src/org/netbeans/modules/rust/options/api/CargoOptions.java 
b/rust/rust.options/src/org/netbeans/modules/rust/options/api/CargoOptions.java
index 883c24e4cf..0489202185 100644
--- 
a/rust/rust.options/src/org/netbeans/modules/rust/options/api/CargoOptions.java
+++ 
b/rust/rust.options/src/org/netbeans/modules/rust/options/api/CargoOptions.java
@@ -38,4 +38,11 @@ public final class CargoOptions {
         return CargoOptionsImpl.getCargoLocation(verifying);
     }
 
+    /**
+     * Opens the Cargo options panel.
+     */
+    public static void showRustCargoOptions() {
+        CargoOptionsImpl.showRustCargoOptions();
+    }
+
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists

Reply via email to