diff --git a/pom.xml b/pom.xml
index 2063fca..7b5d0cf 100644
--- a/pom.xml
+++ b/pom.xml
@@ -58,6 +58,21 @@
flatlaf
2.2
+
+ com.weblookandfeel
+ weblaf-core
+ 1.2.13
+
+
+ com.weblookandfeel
+ weblaf-ui
+ 1.2.13
+
+
+ com.thoughtworks.xstream
+ xstream
+ 1.4.19
+
com.google.code.gson
gson
diff --git a/src/main/java/net/mightypork/utils/DesktopApi.java b/src/main/java/net/mightypork/utils/DesktopApi.java
new file mode 100644
index 0000000..0e63d1f
--- /dev/null
+++ b/src/main/java/net/mightypork/utils/DesktopApi.java
@@ -0,0 +1,181 @@
+package net.mightypork.utils;
+
+import java.awt.Desktop;
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.List;
+
+// https://stackoverflow.com/a/18004334/10719432
+public class DesktopApi {
+ public static boolean browse(URI uri) {
+ if (openSystemSpecific(uri.toString())) return true;
+ return browseDESKTOP(uri);
+ }
+
+ public static boolean open(File file) {
+ if (openSystemSpecific(file.getPath())) return true;
+ return openDESKTOP(file);
+ }
+
+ public static boolean edit(File file) {
+ if (openSystemSpecific(file.getPath())) return true;
+ return editDESKTOP(file);
+ }
+
+ private static boolean openSystemSpecific(String what) {
+ EnumOS os = getOs();
+
+ if (os.isLinux()) {
+ if (runCommand("kde-open", what)) return true;
+ if (runCommand("gnome-open", what)) return true;
+ if (runCommand("xdg-open", what)) return true;
+ }
+
+ if (os.isMac()) {
+ if (runCommand("open", what)) return true;
+ }
+
+ if (os.isWindows()) {
+ return runCommand("explorer", what);
+ }
+
+ return false;
+ }
+
+ private static boolean browseDESKTOP(URI uri) {
+ logOut("Trying to use Desktop.getDesktop().browse() with " + uri.toString());
+ try {
+ if (!Desktop.isDesktopSupported()) {
+ logErr("Platform is not supported.");
+ return false;
+ }
+
+ if (!Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
+ logErr("BROWSE is not supported.");
+ return false;
+ }
+
+ Desktop.getDesktop().browse(uri);
+ return true;
+ } catch (Throwable t) {
+ logErr("Error using desktop browse.", t);
+ return false;
+ }
+ }
+
+
+ private static boolean openDESKTOP(File file) {
+ logOut("Trying to use Desktop.getDesktop().open() with " + file.toString());
+ try {
+ if (!Desktop.isDesktopSupported()) {
+ logErr("Platform is not supported.");
+ return false;
+ }
+
+ if (!Desktop.getDesktop().isSupported(Desktop.Action.OPEN)) {
+ logErr("OPEN is not supported.");
+ return false;
+ }
+
+ Desktop.getDesktop().open(file);
+ return true;
+ } catch (Throwable t) {
+ logErr("Error using desktop open.", t);
+ return false;
+ }
+ }
+
+
+ private static boolean editDESKTOP(File file) {
+ logOut("Trying to use Desktop.getDesktop().edit() with " + file);
+ try {
+ if (!Desktop.isDesktopSupported()) {
+ logErr("Platform is not supported.");
+ return false;
+ }
+
+ if (!Desktop.getDesktop().isSupported(Desktop.Action.EDIT)) {
+ logErr("EDIT is not supported.");
+ return false;
+ }
+
+ Desktop.getDesktop().edit(file);
+ return true;
+ } catch (Throwable t) {
+ logErr("Error using desktop edit.", t);
+ return false;
+ }
+ }
+
+
+ private static boolean runCommand(String command, String file) {
+ String[] parts = prepareCommand(command, file);
+ try {
+ Process p = Runtime.getRuntime().exec(parts);
+ if (p == null) return false;
+
+ try {
+ int result = p.exitValue();
+ if (result == 0) logErr("Process ended immediately.");
+ else logErr("Process crashed.");
+ return false;
+ } catch (IllegalThreadStateException ignored) {
+ return true;
+ }
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+ private static String[] prepareCommand(String command, String file) {
+ List parts = new ArrayList<>();
+ parts.add(command);
+ for (String s : "%s".split(" ")) {
+ s = String.format(s, file);
+ parts.add(s.trim());
+ }
+ return parts.toArray(new String[0]);
+ }
+
+ private static void logErr(String msg, Throwable t) {
+ System.err.println(msg);
+ t.printStackTrace();
+ }
+
+ private static void logErr(String msg) {
+ System.err.println(msg);
+ }
+
+ private static void logOut(String msg) {
+ System.out.println(msg);
+ }
+
+ public enum EnumOS {
+ Linux, MacOS, Solaris, Unknown, Windows;
+
+ public boolean isLinux() {
+ return this == Linux || this == Solaris;
+ }
+
+ public boolean isMac() {
+ return this == MacOS;
+ }
+
+ public boolean isWindows() {
+ return this == Windows;
+ }
+ }
+
+ public static EnumOS getOs() {
+ String s = System.getProperty("os.name").toLowerCase();
+ if (s.contains("win")) return EnumOS.Windows;
+ if (s.contains("mac")) return EnumOS.MacOS;
+ if (s.contains("solaris")) return EnumOS.Solaris;
+ if (s.contains("sunos")) return EnumOS.Solaris;
+ if (s.contains("linux")) return EnumOS.Linux;
+ if (s.contains("unix")) return EnumOS.Linux;
+ return EnumOS.Unknown;
+ }
+}
diff --git a/src/main/java/xyz/mrmelon54/codesize/CodeSize.java b/src/main/java/xyz/mrmelon54/codesize/CodeSize.java
index 658ee74..ff38013 100644
--- a/src/main/java/xyz/mrmelon54/codesize/CodeSize.java
+++ b/src/main/java/xyz/mrmelon54/codesize/CodeSize.java
@@ -16,6 +16,7 @@ import java.awt.event.KeyEvent;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.IOException;
+import java.util.Objects;
import java.util.regex.Pattern;
public class CodeSize extends JFrame {
@@ -23,14 +24,15 @@ public class CodeSize extends JFrame {
private JTextField pathField;
private JTextField regexField;
private InfoPanel infoPanel;
+ private JButton searchButton;
public CodeSize(Settings settings) {
super("Code Size");
this.settings = settings;
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- setPreferredSize(new Dimension(600, 600));
- setMinimumSize(new Dimension(600, 600));
+ setPreferredSize(new Dimension(800, 600));
+ setMinimumSize(new Dimension(800, 600));
setContentPane(createMainPanel());
setJMenuBar(createMenuBar());
}
@@ -38,13 +40,13 @@ public class CodeSize extends JFrame {
private JPanel createMainPanel() {
pathField = ComponentUtils.fixedTextFieldHeight(new JTextField());
regexField = ComponentUtils.fixedTextFieldHeight(new JTextField());
- infoPanel = new InfoPanel();
+ infoPanel = new InfoPanel(this);
JButton browseButton = new JButton("Browse");
browseButton.addActionListener(e -> browseForFolder());
JButton presetButton = new JButton("Preset");
presetButton.addActionListener(e -> presetForRegex());
- JButton searchButton = new JButton("Search");
+ searchButton = new JButton("Search");
searchButton.addActionListener(e -> searchCodeFiles());
Box pathRow = Box.createHorizontalBox();
@@ -157,12 +159,20 @@ public class CodeSize extends JFrame {
return;
}
Pattern pattern;
+ String rawPattern = regexField.getText();
+ if (Objects.equals(rawPattern, "")) rawPattern = "\\..+$";
try {
- pattern = Pattern.compile(regexField.getText());
+ pattern = Pattern.compile(rawPattern);
} catch (Exception ex) {
JOptionPane.showMessageDialog(this, "Failed to parse regex pattern", "Code Size", JOptionPane.ERROR_MESSAGE);
return;
}
- new Thread(() -> FileFinder.search(mainFile, pattern, infoPanel)).start();
+ searchButton.setEnabled(false);
+ infoPanel.reset();
+ new Thread(() -> {
+ FileFinder.search(mainFile, pattern, infoPanel);
+ infoPanel.finishLoading();
+ SwingUtilities.invokeLater(() -> searchButton.setEnabled(true));
+ }).start();
}
}
diff --git a/src/main/java/xyz/mrmelon54/codesize/components/InfoPanel.java b/src/main/java/xyz/mrmelon54/codesize/components/InfoPanel.java
index 2ee16d9..0d191da 100644
--- a/src/main/java/xyz/mrmelon54/codesize/components/InfoPanel.java
+++ b/src/main/java/xyz/mrmelon54/codesize/components/InfoPanel.java
@@ -1,13 +1,22 @@
package xyz.mrmelon54.codesize.components;
+import net.mightypork.utils.DesktopApi;
+import xyz.mrmelon54.codesize.CodeSize;
+import xyz.mrmelon54.codesize.loading.LoadingAnimation;
import xyz.mrmelon54.codesize.utils.ByteUtils;
import javax.swing.*;
+import javax.swing.border.EmptyBorder;
+import java.awt.*;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.File;
import java.util.ArrayList;
import java.util.List;
-public class InfoPanel extends Box {
- private final DefaultListModel fileListModel = new DefaultListModel<>();
+public class InfoPanel extends JPanel {
+ private final DefaultListModel fileListModel = new DefaultListModel<>();
+ private final List fileListObjects = new ArrayList<>();
public InfoPanelItem totalFiles = new InfoPanelItem("Total Files", false);
public InfoPanelItem totalSize = new InfoPanelItem("Total Size", true);
public InfoPanelItem averageSize = new InfoPanelItem("Average Size", true);
@@ -18,14 +27,35 @@ public class InfoPanel extends Box {
public InfoPanelItem maxLines = new InfoPanelItem("Max Lines", false);
public InfoPanelItem minLines = new InfoPanelItem("Min Lines", false);
private final List values = new ArrayList<>();
+ private final LoadingAnimation loadingAnimation = new LoadingAnimation();
+ private final Timer loadingTimer;
+ private long loadingStartTime;
- public InfoPanel() {
- super(BoxLayout.Y_AXIS);
+ public InfoPanel(CodeSize codeSize) {
+ super();
+ setLayout(new BorderLayout());
- JList fileListInfo = new JList<>();
+ JList fileListInfo = new JList<>();
fileListInfo.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
fileListInfo.setModel(fileListModel);
- add(fileListInfo);
+ fileListInfo.addMouseListener(new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ if (e.getClickCount() == 2) {
+ InfoPanelFile selectedValue = fileListInfo.getSelectedValue();
+ if (selectedValue == null) return;
+ File f = selectedValue.file();
+ if (DesktopApi.edit(f)) {
+ System.out.println("Opened file: '" + f.getAbsolutePath() + "'");
+ } else {
+ String s = "Failed to open file '%s' in default text editor".formatted(f.getAbsolutePath());
+ System.out.println(s);
+ JOptionPane.showMessageDialog(codeSize, s, "Code Size", JOptionPane.ERROR_MESSAGE);
+ }
+ } else super.mouseClicked(e);
+ }
+ });
+ add(new JScrollPane(fileListInfo), BorderLayout.CENTER);
values.add(totalFiles);
values.add(totalSize);
@@ -36,15 +66,59 @@ public class InfoPanel extends Box {
values.add(averageLines);
values.add(maxLines);
values.add(minLines);
- values.forEach(infoPanelItem -> add(infoPanelItem.component));
+
+ Box box = Box.createVerticalBox();
+ box.setBorder(new EmptyBorder(0, 8, 0, 0));
+ values.forEach(infoPanelItem -> box.add(infoPanelItem.component));
+ add(box, BorderLayout.EAST);
+
+ loadingTimer = new Timer(1, e -> repaint());
+ }
+
+ @Override
+ public void paint(Graphics g) {
+ super.paint(g);
+ if (loadingTimer.isRunning()) {
+ int w = getWidth();
+ int h = getHeight();
+ g.setColor(new Color(0f, 0f, 0f, .5f));
+ g.fillRect(0, 0, w, h);
+ loadingAnimation.paint(g.create(w / 2 - 100, h / 2 - 100, 200, 200));
+ }
+ }
+
+ public void reset() {
+ fileListObjects.clear();
+ fileListModel.clear();
+ values.forEach(infoPanelItem -> infoPanelItem.setValue(0));
+ loadingStartTime = System.currentTimeMillis();
+ loadingAnimation.reset();
+ loadingTimer.start();
+ }
+
+ public void finishLoading() {
+ long l = System.currentTimeMillis() - loadingStartTime;
+ if (l < 2000) {
+ try {
+ Thread.sleep(2000 - l);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ loadingTimer.stop();
+ repaint();
}
public void updateValues() {
- values.forEach(InfoPanelItem::updateValue);
+ SwingUtilities.invokeLater(() -> {
+ fileListModel.clear();
+ fileListObjects.forEach(fileListModel::addElement);
+ values.forEach(InfoPanelItem::updateValue);
+ });
}
- public void addFileOutput(String s) {
- fileListModel.addElement(s);
+ public void addFileOutput(InfoPanelFile infoPanelFile) {
+ fileListObjects.add(infoPanelFile);
}
public static class InfoPanelItem {
@@ -61,14 +135,21 @@ public class InfoPanel extends Box {
updateValue();
}
+ // only use inside invokeLater() call
private void updateValue() {
String v = byteUnits ? ByteUtils.ToBytesCount((long) value) : String.valueOf(value);
- SwingUtilities.invokeLater(() -> component.setText(label + ": " + v));
+ component.setText(label + ": " + v);
}
public void setValue(float v) {
value = v;
- updateValue();
+ }
+ }
+
+ public record InfoPanelFile(File file, long lines, long size) {
+ @Override
+ public String toString() {
+ return ByteUtils.ToBytesCount(size) + " -- " + lines + " -- " + file.getName();
}
}
}
diff --git a/src/main/java/xyz/mrmelon54/codesize/loading/LoadingAnimation.java b/src/main/java/xyz/mrmelon54/codesize/loading/LoadingAnimation.java
new file mode 100644
index 0000000..5be2cfb
--- /dev/null
+++ b/src/main/java/xyz/mrmelon54/codesize/loading/LoadingAnimation.java
@@ -0,0 +1,37 @@
+package xyz.mrmelon54.codesize.loading;
+
+import java.awt.*;
+
+public class LoadingAnimation {
+ private long lastTime = 0;
+ private float tick;
+
+ private Color[] ovalColors = new Color[]{
+ new Color(0x0099e5),
+ new Color(0xff4c4c),
+ new Color(0x34bf49),
+ new Color(0xedea42),
+ };
+
+ public void reset() {
+ lastTime = System.nanoTime();
+ tick = 0;
+ }
+
+ public void paint(Graphics g) {
+ double deltaTime = (lastTime - (lastTime = System.nanoTime())) / -150000000f;
+ tick += deltaTime;
+ for (int i = 0; i < 8; i++) {
+ g.setColor(getOvalColor(i));
+ g.fillOval(25 * i + 2, getOvalY(tick + i * 0.7f), 21, 21);
+ }
+ }
+
+ private Color getOvalColor(int i) {
+ return ovalColors[i % ovalColors.length];
+ }
+
+ private int getOvalY(float y) {
+ return (int) (Math.sin(y) * 50 + 100 - 21 / 2f);
+ }
+}
diff --git a/src/main/java/xyz/mrmelon54/codesize/process/FileFinder.java b/src/main/java/xyz/mrmelon54/codesize/process/FileFinder.java
index 6ea26c1..67539f7 100644
--- a/src/main/java/xyz/mrmelon54/codesize/process/FileFinder.java
+++ b/src/main/java/xyz/mrmelon54/codesize/process/FileFinder.java
@@ -8,8 +8,6 @@ import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
-import static xyz.mrmelon54.codesize.utils.ByteUtils.ToBytesCount;
-
public class FileFinder {
public static void search(File mainFile, Pattern pattern, InfoPanel infoPanel) {
List searchable = new ArrayList<>();
@@ -29,11 +27,10 @@ public class FileFinder {
if (files1 == null) continue;
Collections.addAll(searchable, files1);
- File[] files2 = path1.listFiles((dir, name) -> {
- return pattern.matcher(name).find();
- });
+ File[] files2 = path1.listFiles((dir, name) -> pattern.matcher(name).find());
if (files2 == null) return;
for (File file : files2) {
+ if (!file.isFile()) continue;
long len = file.length();
totalFiles++;
totalSize += len;
@@ -43,7 +40,7 @@ public class FileFinder {
totalLines += lineCount;
if (lineCount > maxLines) maxLines = lineCount;
if (lineCount < minLines || minLines == 0) minLines = lineCount;
- infoPanel.addFileOutput(ToBytesCount(len) + " -- " + lineCount + " -- " + file.getName());
+ infoPanel.addFileOutput(new InfoPanel.InfoPanelFile(file, lineCount, len));
}
infoPanel.totalFiles.setValue(totalFiles);
diff --git a/src/main/java/xyz/mrmelon54/codesize/ui/PresetSelector.java b/src/main/java/xyz/mrmelon54/codesize/ui/PresetSelector.java
index 0e8af7e..4e69e92 100644
--- a/src/main/java/xyz/mrmelon54/codesize/ui/PresetSelector.java
+++ b/src/main/java/xyz/mrmelon54/codesize/ui/PresetSelector.java
@@ -45,6 +45,10 @@ public class PresetSelector extends JDialog {
copyButton.addActionListener(this::copyAction);
JIconButton editButton = new JIconButton(GoogleMaterialDesignIcons.EDIT, 16, 24);
editButton.addActionListener(this::editAction);
+ JIconButton upButton = new JIconButton(GoogleMaterialDesignIcons.KEYBOARD_ARROW_UP, 16, 24);
+ upButton.addActionListener(this::upAction);
+ JIconButton downButton = new JIconButton(GoogleMaterialDesignIcons.KEYBOARD_ARROW_DOWN, 16, 24);
+ downButton.addActionListener(this::downAction);
JButton chooseButton = new JButton("Choose");
chooseButton.addActionListener(this::selectAction);
@@ -56,6 +60,8 @@ public class PresetSelector extends JDialog {
hBox.add(removeButton);
hBox.add(editButton);
hBox.add(copyButton);
+ hBox.add(upButton);
+ hBox.add(downButton);
hBox.add(Box.createHorizontalGlue());
hBox.add(chooseButton);
hBox.add(cancelButton);
@@ -147,4 +153,26 @@ public class PresetSelector extends JDialog {
private void cancelAction(ActionEvent actionEvent) {
dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING));
}
+
+ private void upAction(ActionEvent actionEvent) {
+ Pair selectedPreset = getSelectedPreset();
+ int idx = selectedPreset.t();
+ int prev = idx - 1;
+ if (prev < 0) return;
+ codeSize.settings.presets.setElementAt(codeSize.settings.presets.elementAt(prev), idx);
+ codeSize.settings.presets.setElementAt(selectedPreset.u(), prev);
+ reload();
+ presetList.setSelectedIndex(prev);
+ }
+
+ private void downAction(ActionEvent actionEvent) {
+ Pair selectedPreset = getSelectedPreset();
+ int idx = selectedPreset.t();
+ int next = idx + 1;
+ if (next >= codeSize.settings.presets.size()) return;
+ codeSize.settings.presets.setElementAt(codeSize.settings.presets.elementAt(next), idx);
+ codeSize.settings.presets.setElementAt(selectedPreset.u(), next);
+ reload();
+ presetList.setSelectedIndex(next);
+ }
}
diff --git a/src/main/java/xyz/mrmelon54/codesize/utils/ArrayUtils.java b/src/main/java/xyz/mrmelon54/codesize/utils/ArrayUtils.java
deleted file mode 100644
index 4ed0036..0000000
--- a/src/main/java/xyz/mrmelon54/codesize/utils/ArrayUtils.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package xyz.mrmelon54.codesize.utils;
-
-
-import java.lang.reflect.Array;
-import java.util.Collections;
-import java.util.function.IntFunction;
-
-public record ArrayUtils(IntFunction generator) {
- public T[] concat(T[] a, T[] b) {
- if (a == null && b == null) return null;
- if (a == null) return b;
- if (b == null) return a;
- final int aLen = Array.getLength(a);
- final int bLen = Array.getLength(b);
- if (aLen == 0) return b;
- if (bLen == 0) return a;
-
- T[] result = generator.apply(aLen + bLen);
- System.arraycopy(a, 0, result, 0, aLen);
- System.arraycopy(b, 0, result, aLen, bLen);
- return result;
- }
-
- public T[] append(T[] a, T b) {
- if (a == null) {
- T[] result = generator.apply(1);
- result[0] = b;
- return result;
- }
- final int aLen = Array.getLength(a);
- T[] result = generator.apply(aLen + 1);
- System.arraycopy(a, 0, result, 0, aLen);
- result[aLen] = b;
- return result;
- }
-}
diff --git a/src/main/java/xyz/mrmelon54/codesize/utils/ByteUtils.java b/src/main/java/xyz/mrmelon54/codesize/utils/ByteUtils.java
index 23fce51..f157158 100644
--- a/src/main/java/xyz/mrmelon54/codesize/utils/ByteUtils.java
+++ b/src/main/java/xyz/mrmelon54/codesize/utils/ByteUtils.java
@@ -7,8 +7,8 @@ public class ByteUtils {
if ((len < unit)) return String.format("%d %s", len, unitStr);
else unitStr = unitStr.toUpperCase();
- byte[] a = "KMGTPEZY".getBytes();
+ String a = "KMGTPEZY";
int exp = ((int) ((Math.log(len) / Math.log(unit))));
- return String.format("%.2f %s%s", (len / Math.pow(unit, exp)), a[(exp - 1)], unitStr);
+ return String.format("%.2f %s%s", (len / Math.pow(unit, exp)), a.charAt(exp - 1), unitStr);
}
}
diff --git a/src/main/java/xyz/mrmelon54/codesize/utils/ComponentUtils.java b/src/main/java/xyz/mrmelon54/codesize/utils/ComponentUtils.java
index 50ade79..b79227c 100644
--- a/src/main/java/xyz/mrmelon54/codesize/utils/ComponentUtils.java
+++ b/src/main/java/xyz/mrmelon54/codesize/utils/ComponentUtils.java
@@ -5,7 +5,7 @@ import java.awt.*;
public class ComponentUtils {
public static JTextField fixedTextFieldHeight(JTextField textField) {
- textField.setMaximumSize(new Dimension(textField.getMaximumSize().width, textField.getPreferredSize().height));
+ textField.setMaximumSize(new Dimension(textField.getMaximumSize().width, textField.getPreferredSize().height + 10));
return textField;
}
}
diff --git a/src/main/java/xyz/mrmelon54/codesize/utils/Preset.java b/src/main/java/xyz/mrmelon54/codesize/utils/Preset.java
index e9ba204..fbd24c4 100644
--- a/src/main/java/xyz/mrmelon54/codesize/utils/Preset.java
+++ b/src/main/java/xyz/mrmelon54/codesize/utils/Preset.java
@@ -12,7 +12,7 @@ public class Preset {
public String regex;
public boolean builtin;
- private static final Pattern rawNamePattern = Pattern.compile("(.+) \\(Copy \\d+\\)"); // TODO
+ private static final Pattern rawNamePattern = Pattern.compile("(.+) \\(Copy \\d+\\)");
@Override
public String toString() {
diff --git a/src/main/resources/presets.json b/src/main/resources/presets.json
index a3aaeaf..c33b395 100644
--- a/src/main/resources/presets.json
+++ b/src/main/resources/presets.json
@@ -11,6 +11,12 @@
"regex": "\\.(java)$",
"builtin": true
},
+ {
+ "uuid": "2ca15e4f-dff8-4da9-8408-52d884f729ec",
+ "name": "Go Project",
+ "regex": "\\.(go)$",
+ "builtin": true
+ },
{
"uuid": "1b71b0a9-365d-45ff-8159-4a1acd869be0",
"name": "Node Project",
@@ -28,5 +34,11 @@
"name": "Website Project",
"regex": "\\.(html|js|css|php)$",
"builtin": true
+ },
+ {
+ "uuid": "0a721596-7ae9-435b-9ad2-3c6fce84853d",
+ "name": "C/C++ Project",
+ "regex": "\\.(c|cpp|c\\+\\+|h)$",
+ "builtin": true
}
]