Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 142 additions & 0 deletions .github/workflows/build-win-mac-universal.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
name: Build and Package (p2 ZIP)

on:
push:
branches: [ main ]
workflow_dispatch:

env:
MAVEN_OPTS: -Xmx2g

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Extract plugin version
run: |
VERSION=$(grep -oP '(?<=<copilot-plugin-version>)[^<]+' pom.xml)
TIMESTAMP=$(date -u +%Y%m%d%H%M%S)
DISPLAY_VERSION="${VERSION/-SNAPSHOT/-$TIMESTAMP}"
echo "COPILOT_PLUGIN_VERSION=$VERSION" >> $GITHUB_ENV
echo "COPILOT_DISPLAY_VERSION=$DISPLAY_VERSION" >> $GITHUB_ENV
Comment on lines +22 to +26

- name: Set up Java 21 (Temurin)
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
cache: 'maven'

- name: Cache Maven local repo
uses: actions/cache@v4
with:
path: ~/.m2/repository
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-m2-

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 22

- name: Install Copilot agent
working-directory: com.microsoft.copilot.eclipse.core/copilot-agent
run: |
npm install --force
cp -r node_modules/@github/copilot-language-server/dist ./dist

- name: Make mvnw executable
run: chmod +x ./mvnw

- name: Build (Tycho/Maven)
run: |
./mvnw -DskipTests -Dcheckstyle.skip=true clean verify

- name: Prepare build artifacts
run: |
mkdir -p build
SRC=com.microsoft.copilot.eclipse.repository/target/com.microsoft.copilot.eclipse.repository-${COPILOT_PLUGIN_VERSION}.zip
if [ ! -f "$SRC" ]; then
echo "ERROR: Expected ZIP not found at $SRC"
ls -la com.microsoft.copilot.eclipse.repository/target || true
exit 1
fi
if ! unzip -l "$SRC" | grep -qE 'content\.(jar|xml|xml\.xz)|p2\.index'; then
echo "ERROR: p2 repository ZIP is missing p2 metadata (content.jar / content.xml.xz / p2.index)"
unzip -l "$SRC"
exit 1
fi
find build/ -maxdepth 1 -name "github-copilot-for-anypoint-*.zip" -delete 2>/dev/null || true

# Create universal p2 repository (all platforms)
OUT_UNIVERSAL=build/github-copilot-for-anypoint-${COPILOT_DISPLAY_VERSION}.zip
cp -f "$SRC" "$OUT_UNIVERSAL"
sha256sum "$OUT_UNIVERSAL" | awk '{print $1 " " $2}' > "${OUT_UNIVERSAL}.sha256"

# Create Windows-specific package (win32 only)
OUT_WIN=build/github-copilot-for-anypoint-${COPILOT_DISPLAY_VERSION}-win32.zip
unzip -q "$SRC" -d temp_win
rm -rf temp_win/plugins/*linux* temp_win/plugins/*macosx* temp_win/features/*linux* temp_win/features/*macosx* 2>/dev/null || true
cd temp_win && zip -q -r "../${OUT_WIN}" . && cd ..
rm -rf temp_win
sha256sum "$OUT_WIN" | awk '{print $1 " " $2}' > "${OUT_WIN}.sha256"

# Create macOS-specific package (macosx only)
OUT_MAC=build/github-copilot-for-anypoint-${COPILOT_DISPLAY_VERSION}-macos.zip
unzip -q "$SRC" -d temp_mac
rm -rf temp_mac/plugins/*linux* temp_mac/plugins/*win32* temp_mac/features/*linux* temp_mac/features/*win32* 2>/dev/null || true
cd temp_mac && zip -q -r "../${OUT_MAC}" . && cd ..
rm -rf temp_mac
sha256sum "$OUT_MAC" | awk '{print $1 " " $2}' > "${OUT_MAC}.sha256"

- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: github-copilot-for-anypoint-${{ env.COPILOT_DISPLAY_VERSION }}
path: |
build/github-copilot-for-anypoint-${{ env.COPILOT_DISPLAY_VERSION }}.zip
build/github-copilot-for-anypoint-${{ env.COPILOT_DISPLAY_VERSION }}.zip.sha256
build/github-copilot-for-anypoint-${{ env.COPILOT_DISPLAY_VERSION }}-win32.zip
build/github-copilot-for-anypoint-${{ env.COPILOT_DISPLAY_VERSION }}-win32.zip.sha256
build/github-copilot-for-anypoint-${{ env.COPILOT_DISPLAY_VERSION }}-macos.zip
build/github-copilot-for-anypoint-${{ env.COPILOT_DISPLAY_VERSION }}-macos.zip.sha256

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: "v${{ env.COPILOT_DISPLAY_VERSION }}"
name: "GitHub Copilot for Anypoint Studio v${{ env.COPILOT_DISPLAY_VERSION }}"
prerelease: true
body: |
Pre-release build of GitHub Copilot for Anypoint Studio.

**Installation in Anypoint Studio:**

Select the package appropriate for your platform:

- **Windows (64-bit)**: Download `github-copilot-for-anypoint-${{ env.COPILOT_DISPLAY_VERSION }}-win32.zip`
- **macOS (Intel & Apple Silicon)**: Download `github-copilot-for-anypoint-${{ env.COPILOT_DISPLAY_VERSION }}-macos.zip`
- **All Platforms**: Download `github-copilot-for-anypoint-${{ env.COPILOT_DISPLAY_VERSION }}.zip` (universal p2 repository with all platform support)

**Installation Steps:**
1. In Anypoint Studio, go to **Help → Install New Software...**
2. Click **Add...** and select the downloaded ZIP file as a local update site.
3. Follow the installation wizard and restart Anypoint Studio.

**Verification:**
Each package includes a `.sha256` file for integrity verification. Use `sha256sum -c file.zip.sha256` to verify the download.
files: |
build/github-copilot-for-anypoint-${{ env.COPILOT_DISPLAY_VERSION }}.zip
build/github-copilot-for-anypoint-${{ env.COPILOT_DISPLAY_VERSION }}.zip.sha256
build/github-copilot-for-anypoint-${{ env.COPILOT_DISPLAY_VERSION }}-win32.zip
build/github-copilot-for-anypoint-${{ env.COPILOT_DISPLAY_VERSION }}-win32.zip.sha256
build/github-copilot-for-anypoint-${{ env.COPILOT_DISPLAY_VERSION }}-macos.zip
build/github-copilot-for-anypoint-${{ env.COPILOT_DISPLAY_VERSION }}-macos.zip.sha256
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
86 changes: 43 additions & 43 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,43 +1,43 @@
name: CI

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

permissions:
contents: read

jobs:
build:

runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]

steps:
- uses: actions/checkout@v4

- name: Setup Node.js environment
uses: actions/setup-node@v4
with:
node-version: 22

- name: Install Copilot agent
working-directory: com.microsoft.copilot.eclipse.core/copilot-agent
run: npm i -f

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: maven

- name: Build
uses: coactions/setup-xvfb@b6b4fcfb9f5a895edadc3bc76318fae0ac17c8b3 # v1.0.1
with:
run: >-
./mvnw clean verify --batch-mode
# name: CI

# on:
# push:
# branches: [ "main" ]
Comment on lines +1 to +5
# pull_request:
# branches: [ "main" ]
Comment on lines +3 to +7

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Re-enable pull_request CI checks

This change comments out the entire CI workflow, so the repository no longer runs any build/test job on pull_request (or on push to main) from this file. I checked the other workflow files in .github/workflows and none define a pull_request trigger, which means regressions can now be merged without automated validation.

Useful? React with 👍 / 👎.


# permissions:
# contents: read

# jobs:
# build:

# runs-on: ${{ matrix.os }}
# strategy:
# matrix:
# os: [ubuntu-latest, macos-latest, windows-latest]

# steps:
# - uses: actions/checkout@v4

# - name: Setup Node.js environment
# uses: actions/setup-node@v4
# with:
# node-version: 22

# - name: Install Copilot agent
# working-directory: com.microsoft.copilot.eclipse.core/copilot-agent
# run: npm i -f

# - name: Set up JDK 17
# uses: actions/setup-java@v4
# with:
# java-version: '17'
# distribution: 'temurin'
# cache: maven

# - name: Build
# uses: coactions/setup-xvfb@b6b4fcfb9f5a895edadc3bc76318fae0ac17c8b3 # v1.0.1
# with:
# run: >-
# ./mvnw clean verify --batch-mode
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,16 @@ public CompletableFuture<LanguageModelToolConfirmationResult> requestToolExecuti
reset();

this.confirmDialog = new InvokeToolConfirmationDialog(this, content, input);
this.confirmDialog.addDisposeListener(e -> {
Composite ancestor = this.getParent();
while (ancestor != null && !ancestor.isDisposed()) {
if (ancestor instanceof ChatContentViewer) {
((ChatContentViewer) ancestor).requestRefreshScrollerLayout();
break;
}
ancestor = ancestor.getParent();
}
});
CompletableFuture<LanguageModelToolConfirmationResult> toolConfirmationFuture = this.confirmDialog
.getConfirmationFuture();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,14 @@ public void renderErrorMessage(String errorMessage) {
scrollToLatestUserTurn();
}

/**
* Schedules a single async {@link #refreshScrollerLayout()} call so that multiple dispose/layout
* events that arrive in the same event-loop tick are coalesced into one pass.
*/
Comment on lines +407 to +410
public void requestRefreshScrollerLayout() {
SwtUtils.invokeOnDisplayThreadAsync(() -> refreshScrollerLayout(), this);
}

/**
* Update the size of scrolled composite when there are content updates.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,7 @@ public void cancelConfirmation() {
&& StringUtils.isNotEmpty(this.cancelMessage)) {
new AgentToolCancelLabel(parent, SWT.NONE, this.cancelMessage);
}
this.dispose();
if (parent != null && !parent.isDisposed()) {
parent.requestLayout();
}
disposeAndRequestParentLayout();
}, this);
}
}
Expand Down Expand Up @@ -318,6 +315,10 @@ private void acceptAndDispose(ConfirmationAction action) {
new LanguageModelToolConfirmationResult(
ToolConfirmationResult.ACCEPT));

disposeAndRequestParentLayout();
}

private void disposeAndRequestParentLayout() {
Composite parent = this.getParent();
this.dispose();
if (parent != null && !parent.isDisposed()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,21 @@
package com.microsoft.copilot.eclipse.ui.chat;

import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
Comment on lines 11 to 13
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
Comment on lines 18 to 20
import org.eclipse.swt.widgets.Link;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;

Expand All @@ -30,7 +34,12 @@
* and whether to pass a plan.
*/
public class WarnWidget extends Composite {
private static final Pattern MARKDOWN_LINK_PATTERN = Pattern.compile("\\[([^\\]]+)]\\(([^)]+)\\)");
private static final Pattern RAW_URL_PATTERN = Pattern.compile("https?://\\S+");

private int buttonLeftMargin;
private Color darkBackground;
private Color darkForeground;
Comment on lines 40 to +42

/**
* Create the composite.
Expand Down Expand Up @@ -119,15 +128,71 @@ private void buildWarnLabelWithIcon(String message) {
buttonLeftMargin = warnLayout.marginWidth + warnLayout.marginLeft + warnImage.getBounds().width
+ warnLayout.horizontalSpacing;

ChatMarkupViewer textLabel = new ChatMarkupViewer(composite, SWT.LEFT | SWT.WRAP);
StyledText styledText = textLabel.getTextWidget();
styledText.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, true));
styledText.setEditable(false);
textLabel.setMarkup(message);
Link messageLink = new Link(composite, SWT.LEFT | SWT.WRAP);
messageLink.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
messageLink.setText(toLinkMarkup(message));
messageLink.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(org.eclipse.swt.events.SelectionEvent event) {
UiUtils.openLink(event.text);
}
});

requestLayout();
}

private static String toLinkMarkup(String message) {
String text = stripMarkdownEmphasis(message == null ? "" : message);
Matcher markdownLinkMatcher = MARKDOWN_LINK_PATTERN.matcher(text);
StringBuilder result = new StringBuilder();
int offset = 0;
while (markdownLinkMatcher.find()) {
appendTextWithRawLinks(result, text.substring(offset, markdownLinkMatcher.start()));
appendLink(result, markdownLinkMatcher.group(1), markdownLinkMatcher.group(2));
offset = markdownLinkMatcher.end();
}
appendTextWithRawLinks(result, text.substring(offset));
return result.toString();
}

private static void appendTextWithRawLinks(StringBuilder result, String text) {
Matcher rawUrlMatcher = RAW_URL_PATTERN.matcher(text);
int offset = 0;
while (rawUrlMatcher.find()) {
result.append(escapeLinkText(text.substring(offset, rawUrlMatcher.start())));
String url = rawUrlMatcher.group();
String trailingPunctuation = "";
while (!url.isEmpty() && ".,;:".indexOf(url.charAt(url.length() - 1)) >= 0) {
trailingPunctuation = url.charAt(url.length() - 1) + trailingPunctuation;
url = url.substring(0, url.length() - 1);
}
appendLink(result, url, url);
result.append(escapeLinkText(trailingPunctuation));
offset = rawUrlMatcher.end();
}
result.append(escapeLinkText(text.substring(offset)));
}

private static void appendLink(StringBuilder result, String label, String url) {
result.append("<a href=\"")
.append(escapeLinkAttribute(url == null ? "" : url.trim()))
.append("\">")
.append(escapeLinkText(stripMarkdownEmphasis(label == null ? "" : label)))
.append("</a>");
}

private static String stripMarkdownEmphasis(String text) {
return text.replace("**", "");
}

private static String escapeLinkText(String text) {
return text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;");
}

private static String escapeLinkAttribute(String text) {
return escapeLinkText(text).replace("\"", "&quot;").replace("'", "&apos;");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep URL query separators unescaped in Link href

escapeLinkAttribute HTML-escapes & via escapeLinkText, but SWT Link treats the href value as plain text and the selection handler opens event.text directly. As a result, links with query params (for example ...?a=1&b=2) are converted to ...?a=1&amp;b=2 and the browser is opened with the wrong URL.

Useful? React with 👍 / 👎.

}

/**
* Render plan-driven action buttons for a quota-exceeded warning, kept in sync with the quota {@link StaticBanner}.
*/
Expand Down