make richCopy functionality work properly on Linux (IDEA-67767)

This commit is contained in:
Dmitry Batrak
2014-04-21 14:58:54 +04:00
parent 3dca9089ac
commit 405332a5d2
22 changed files with 1833 additions and 1122 deletions
@@ -56,15 +56,15 @@ public class CopyHandler extends EditorActionHandler {
final CodeInsightSettings settings = CodeInsightSettings.getInstance();
if (file == null || settings.ADD_IMPORTS_ON_PASTE == CodeInsightSettings.NO){
if (myOriginalAction != null){
if (file == null || settings.ADD_IMPORTS_ON_PASTE == CodeInsightSettings.NO) {
if (myOriginalAction != null) {
myOriginalAction.execute(editor, dataContext);
}
return;
}
final SelectionModel selectionModel = editor.getSelectionModel();
if(!selectionModel.hasSelection(true) && !selectionModel.hasBlockSelection()) {
if (!selectionModel.hasSelection(true) && !selectionModel.hasBlockSelection()) {
if (Registry.is(CopyAction.SKIP_COPY_AND_CUT_FOR_EMPTY_SELECTION_KEY)) {
return;
}
@@ -89,7 +89,7 @@ public class CopyHandler extends EditorActionHandler {
final int[] endOffsets = selectionModel.getBlockSelectionEnds();
List<TextBlockTransferableData> transferableDatas = new ArrayList<TextBlockTransferableData>();
for(CopyPastePostProcessor processor: Extensions.getExtensions(CopyPastePostProcessor.EP_NAME)) {
for (CopyPastePostProcessor processor : Extensions.getExtensions(CopyPastePostProcessor.EP_NAME)) {
final TextBlockTransferableData e = processor.collectTransferableData(file, editor, startOffsets, endOffsets);
if (e != null) {
transferableDatas.add(e);
@@ -98,7 +98,7 @@ public class CopyHandler extends EditorActionHandler {
String rawText = TextBlockTransferable.convertLineSeparators(selectionModel.getSelectedText(true), "\n", transferableDatas);
String escapedText = null;
for(CopyPastePreProcessor processor: Extensions.getExtensions(CopyPastePreProcessor.EP_NAME)) {
for (CopyPastePreProcessor processor : Extensions.getExtensions(CopyPastePreProcessor.EP_NAME)) {
escapedText = processor.preprocessOnCopy(file, startOffsets, endOffsets, rawText);
if (escapedText != null) {
break;
@@ -27,37 +27,38 @@ import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
class TextBlockTransferable implements Transferable {
private final Collection<TextBlockTransferableData> myExtraData;
private final RawText myRawText;
private final String myText;
private final DataFlavor[] myTransferDataFlavors;
public TextBlockTransferable(String text, Collection<TextBlockTransferableData> extraData, RawText rawText) {
myText = text;
myExtraData = extraData;
myRawText = rawText;
List<DataFlavor> dataFlavors = new ArrayList<DataFlavor>();
Collections.addAll(dataFlavors, DataFlavor.stringFlavor, DataFlavor.plainTextFlavor);
final DataFlavor flavor = RawText.getDataFlavor();
if (myRawText != null && flavor != null) {
dataFlavors.add(flavor);
}
for(TextBlockTransferableData data: extraData) {
final DataFlavor blockFlavor = data.getFlavor();
if (blockFlavor != null) {
dataFlavors.add(blockFlavor);
}
}
myTransferDataFlavors = dataFlavors.toArray(new DataFlavor[dataFlavors.size()]);
}
@Override
public DataFlavor[] getTransferDataFlavors() {
// We don't cache the result to allow certain dataflavors to 'expire'.
// This is used e.g. for RTF and HTML flavors to avoid memory leaking.
List<DataFlavor> flavors = new ArrayList<DataFlavor>(myExtraData.size() + 3);
flavors.add(DataFlavor.stringFlavor);
flavors.add(DataFlavor.plainTextFlavor);
final DataFlavor flavor = RawText.getDataFlavor();
if (myRawText != null && flavor != null) {
flavors.add(flavor);
}
for(TextBlockTransferableData data : myExtraData) {
final DataFlavor blockFlavor = data.getFlavor();
if (blockFlavor != null) {
flavors.add(blockFlavor);
}
}
return flavors.toArray(new DataFlavor[flavors.size()]);
return myTransferDataFlavors;
}
@Override
@@ -16,32 +16,24 @@
package com.intellij.openapi.editor.richcopy;
import com.intellij.codeInsight.editorActions.CopyPastePostProcessor;
import com.intellij.codeInsight.editorActions.CopyPastePreProcessor;
import com.intellij.codeInsight.editorActions.TextBlockTransferableData;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.RangeMarker;
import com.intellij.openapi.editor.RawText;
import com.intellij.openapi.editor.richcopy.model.SyntaxInfo;
import com.intellij.openapi.editor.richcopy.view.RawTextHolder;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.psi.PsiFile;
import com.intellij.util.StringBuilderSpinAllocator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.awt.datatransfer.Transferable;
public abstract class BaseTextWithMarkupCopyPasteProcessor<T extends TextBlockTransferableData> implements CopyPastePostProcessor<T>, TextWithMarkupBuilder {
private static final Logger LOG = Logger.getInstance("#" + BaseTextWithMarkupCopyPasteProcessor.class.getName());
private static final String TRUNCATED_MESSAGE = "... truncated ...";
public abstract class BaseTextWithMarkupCopyPasteProcessor<T extends RawTextHolder & TextBlockTransferableData>
implements CopyPastePostProcessor<T>, TextWithMarkupBuilder {
private T myData;
private int mySizeLimit;
protected StringBuilder myBuilder;
protected String myDefaultFontFamily;
protected Color myDefaultForeground;
protected Color myDefaultBackground;
protected int myFontSize;
protected BaseTextWithMarkupCopyPasteProcessor(TextWithMarkupProcessor processor) {
processor.addBuilder(this);
@@ -53,10 +45,7 @@ public abstract class BaseTextWithMarkupCopyPasteProcessor<T extends TextBlockTr
if (!Registry.is("editor.richcopy.enable")) {
return null;
}
releaseBuilder(); // to avoid leakage in case 'complete' method wasn't called due to some exception
T data = myData;
myData = null;
return data;
return myData;
}
@Nullable
@@ -76,58 +65,38 @@ public abstract class BaseTextWithMarkupCopyPasteProcessor<T extends TextBlockTr
}
@Override
public void init(Color defaultForeground, Color defaultBackground, String defaultFontFamily, int fontSize) {
public void build(SyntaxInfo syntaxInfo) {
myData = doBuild(syntaxInfo);
}
@Override
public void reset() {
myData = null;
myDefaultForeground = defaultForeground;
myDefaultBackground = defaultBackground;
myDefaultFontFamily = defaultFontFamily;
myFontSize = fontSize;
allocateBuilder();
mySizeLimit = Registry.intValue("editor.richcopy.max.size.megabytes") * 1048576;
doInit();
}
@Override
public boolean isOverflowed() {
boolean overflowed = myBuilder.length() > mySizeLimit;
if (overflowed) {
setFontFamily(myDefaultFontFamily);
setForeground(myDefaultForeground);
setBackground(myDefaultBackground);
addTextFragment(TRUNCATED_MESSAGE, 0, TRUNCATED_MESSAGE.length());
protected abstract T doBuild(SyntaxInfo info);
public static class RawTextSetter implements CopyPastePreProcessor {
private final BaseTextWithMarkupCopyPasteProcessor myProcessor;
public RawTextSetter(BaseTextWithMarkupCopyPasteProcessor processor) {
myProcessor = processor;
}
return overflowed;
}
@Override
public void complete() {
try {
doComplete();
String data = myBuilder.toString();
if (Registry.is("editor.richcopy.debug")) {
LOG.info("Resulting text: \n" + data);
@Override
@Nullable
public String preprocessOnCopy(final PsiFile file, final int[] startOffsets, final int[] endOffsets, String text) {
if (myProcessor.myData != null) {
myProcessor.myData.setRawText(text);
myProcessor.myData = null;
}
myData = createTransferable(data);
return null; // noop
}
finally {
releaseBuilder();
@Override
public String preprocessOnPaste(final Project project, final PsiFile file, final Editor editor, String text, final RawText rawText) {
return text; // noop
}
}
protected abstract T createTransferable(@NotNull String data);
protected abstract void doInit();
protected abstract void doComplete();
private void allocateBuilder() {
if (myBuilder == null) {
myBuilder = StringBuilderSpinAllocator.alloc();
}
}
private void releaseBuilder() {
if (myBuilder != null) {
StringBuilderSpinAllocator.dispose(myBuilder);
myBuilder = null;
}
}
}
@@ -15,184 +15,27 @@
*/
package com.intellij.openapi.editor.richcopy;
import com.intellij.openapi.editor.richcopy.view.ReaderTransferableData;
import com.intellij.util.StringBuilderSpinAllocator;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.util.HashMap;
import java.util.Map;
import com.intellij.openapi.editor.richcopy.model.SyntaxInfo;
import com.intellij.openapi.editor.richcopy.view.HtmlTransferableData;
/**
* @author Denis Zhdanov
* @since 3/28/13 1:05 PM
*/
public class HtmlCopyPasteProcessor extends BaseTextWithMarkupCopyPasteProcessor<ReaderTransferableData> {
@NotNull public static final DataFlavor FLAVOR = new DataFlavor("text/html;class=java.io.Reader", "HTML text");
private Map<Color, String> myRenderedColors = new HashMap<Color, String>();
private Color myForeground;
private Color myBackground;
private String myFontFamily;
private boolean myBold;
private boolean myItalic;
public class HtmlCopyPasteProcessor extends BaseTextWithMarkupCopyPasteProcessor<HtmlTransferableData> {
public HtmlCopyPasteProcessor(TextWithMarkupProcessor processor) {
super(processor);
}
@Override
protected void doInit() {
myForeground = null;
myBackground = null;
myFontFamily = null;
myBold = false;
myItalic = false;
myBuilder.append("<div style=\"border:1px inset;padding:2%;\"><pre style=\"margin:0;padding:6px;background-color:");
appendColor(myBuilder, myDefaultBackground);
myBuilder.append(';');
appendFontFamilyRule(myBuilder, myDefaultFontFamily);
myBuilder.append("font-size:").append(myFontSize).append("pt;");
myBuilder.append("\" bgcolor=\"");
appendColor(myBuilder, myDefaultBackground);
myBuilder.append("\">");
protected HtmlTransferableData doBuild(SyntaxInfo info) {
return new HtmlTransferableData(info);
}
@Override
protected void doComplete() {
myBuilder.append("</pre></div>");
}
@Override
public void setFontFamily(String fontFamily) {
myFontFamily = myDefaultFontFamily.equals(fontFamily) ? null : fontFamily;
}
@Override
public void setFontStyle(int fontStyle) {
myBold = (Font.BOLD & fontStyle) != 0;
myItalic = (Font.ITALIC & fontStyle) != 0;
}
@Override
public void setForeground(Color foreground) {
myForeground = foreground;
}
@Override
public void setBackground(Color background) {
myBackground = myDefaultBackground.equals(background) ? null : background;
}
@Override
public void addTextFragment(CharSequence charSequence, int startOffset, int endOffset) {
boolean formattedText = myForeground != null || myBackground != null || myFontFamily != null || myBold || myItalic;
if (!formattedText) {
escapeAndAdd(charSequence, startOffset, endOffset);
return;
@SuppressWarnings("ClassNameSameAsAncestorName")
public static class RawTextSetter extends BaseTextWithMarkupCopyPasteProcessor.RawTextSetter {
public RawTextSetter(HtmlCopyPasteProcessor processor) {
super(processor);
}
StringBuilder styleBuffer = StringBuilderSpinAllocator.alloc();
StringBuilder closeTagBuffer = StringBuilderSpinAllocator.alloc();
try {
if (myForeground != null) {
defineForeground(myForeground, styleBuffer, closeTagBuffer);
}
if (myBackground != null) {
defineBackground(myBackground, styleBuffer, closeTagBuffer);
}
if (myBold) {
defineBold(styleBuffer, closeTagBuffer);
}
if (myItalic) {
defineItalic(styleBuffer, closeTagBuffer);
}
if (myFontFamily != null) {
appendFontFamilyRule(styleBuffer, myFontFamily);
}
myBuilder.append("<span style=\"");
myBuilder.append(styleBuffer);
myBuilder.append("\">");
escapeAndAdd(charSequence, startOffset, endOffset);
myBuilder.append("</span>");
myBuilder.append(closeTagBuffer);
}
finally {
StringBuilderSpinAllocator.dispose(styleBuffer);
StringBuilderSpinAllocator.dispose(closeTagBuffer);
}
}
private void defineForeground(Color foreground, @NotNull StringBuilder styleBuffer, @NotNull StringBuilder closeTagBuffer) {
myBuilder.append("<font color=\"");
appendColor(myBuilder, foreground);
myBuilder.append("\">");
styleBuffer.append("color:");
appendColor(styleBuffer, foreground);
styleBuffer.append(";");
closeTagBuffer.insert(0, "</font>");
}
private void defineBackground(Color background, @NotNull StringBuilder styleBuffer, @NotNull StringBuilder closeTagBuffer) {
myBuilder.append("<font bgcolor=\"");
appendColor(myBuilder, background);
myBuilder.append("\">");
styleBuffer.append("background-color:");
appendColor(styleBuffer, background);
styleBuffer.append(";");
closeTagBuffer.insert(0, "</font>");
}
private void defineBold(@NotNull StringBuilder styleBuffer, @NotNull StringBuilder closeTagBuffer) {
myBuilder.append("<b>");
styleBuffer.append("font-weight:bold;");
closeTagBuffer.insert(0, "</b>");
}
private void defineItalic(@NotNull StringBuilder styleBuffer, @NotNull StringBuilder closeTagBuffer) {
myBuilder.append("<i>");
styleBuffer.append("font-style:italic;");
closeTagBuffer.insert(0, "</i>");
}
private void appendColor(StringBuilder builder, Color color) {
String colorAsString = myRenderedColors.get(color);
if (colorAsString == null) {
StringBuilder b = StringBuilderSpinAllocator.alloc();
try {
b.append('#');
UIUtil.appendColor(color, b);
colorAsString = b.toString();
myRenderedColors.put(color, colorAsString);
}
finally {
StringBuilderSpinAllocator.dispose(b);
}
}
builder.append(colorAsString);
}
private static void appendFontFamilyRule(@NotNull StringBuilder styleBuffer, String fontFamily) {
styleBuffer.append("font-family:'").append(fontFamily).append("';");
}
private void escapeAndAdd(CharSequence charSequence, int start, int end) {
for (int i = start; i < end; i++) {
char c = charSequence.charAt(i);
switch (c) {
case '<': myBuilder.append("&lt;"); break;
case '>': myBuilder.append("&gt;"); break;
case '&': myBuilder.append("&amp;"); break;
default: myBuilder.append(c);
}
}
}
@Override
protected ReaderTransferableData createTransferable(@NotNull String data) {
return new ReaderTransferableData(data, FLAVOR);
}
}
@@ -15,184 +15,27 @@
*/
package com.intellij.openapi.editor.richcopy;
import com.intellij.openapi.editor.richcopy.model.*;
import com.intellij.openapi.editor.richcopy.view.InputStreamTransferableData;
import com.intellij.util.StringBuilderSpinAllocator;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.io.UnsupportedEncodingException;
import com.intellij.openapi.editor.richcopy.model.SyntaxInfo;
import com.intellij.openapi.editor.richcopy.view.RtfTransferableData;
/**
* @author Denis Zhdanov
* @since 3/25/13 2:18 PM
*/
public class RtfCopyPasteProcessor extends BaseTextWithMarkupCopyPasteProcessor<InputStreamTransferableData> {
@NotNull public static final DataFlavor FLAVOR = new DataFlavor("text/rtf;class=java.io.InputStream", "RTF text");
private static final String CHARSET = "US-ASCII";
@NotNull private static final String HEADER_PREFIX = "{\\rtf1\\ansi\\deff0";
@NotNull private static final String HEADER_SUFFIX = "}";
@NotNull private static final String TAB = "\\tab\n";
@NotNull private static final String NEW_LINE = "\\line\n";
@NotNull private static final String BOLD = "\\b";
@NotNull private static final String ITALIC = "\\i";
@NotNull private static final String PLAIN = "\\plain\n";
private int myBackgroundId;
private int myForegroundId;
private int myFontNameId;
private ColorRegistry myColorRegistry;
private FontNameRegistry myFontNameRegistry;
public class RtfCopyPasteProcessor extends BaseTextWithMarkupCopyPasteProcessor<RtfTransferableData> {
public RtfCopyPasteProcessor(TextWithMarkupProcessor processor) {
super(processor);
}
@Override
protected void doInit() {
myBackgroundId = -1;
myForegroundId = -1;
myFontNameId = -1;
myColorRegistry = new ColorRegistry();
myFontNameRegistry = new FontNameRegistry();
int defaultForegroundId = myColorRegistry.getId(myDefaultForeground);
int defaultBackgroundId = myColorRegistry.getId(myDefaultBackground);
myBuilder.append("\n\\s0\\box\\brdrhair\\brdrcf").append(defaultForegroundId).append("\\brsp317").append("\\cbpat").append(defaultBackgroundId);
saveBackground(defaultBackgroundId);
protected RtfTransferableData doBuild(SyntaxInfo info) {
return new RtfTransferableData(info);
}
@Override
protected void doComplete() {
StringBuilder builder = StringBuilderSpinAllocator.alloc();
try {
builder.append(HEADER_PREFIX);
// Color table.
builder.append("{\\colortbl;");
for (int id : myColorRegistry.getAllIds()) {
Color color = myColorRegistry.dataById(id);
builder.append(String.format("\\red%d\\green%d\\blue%d;", color.getRed(), color.getGreen(), color.getBlue()));
}
builder.append("}\n");
// Font table.
builder.append("{\\fonttbl");
for (int id : myFontNameRegistry.getAllIds()) {
String fontName = myFontNameRegistry.dataById(id);
builder.append(String.format("{\\f%d %s;}", id, fontName));
}
builder.append("}\n");
myBuilder.insert(0, builder);
@SuppressWarnings("ClassNameSameAsAncestorName")
public static class RawTextSetter extends BaseTextWithMarkupCopyPasteProcessor.RawTextSetter {
public RawTextSetter(RtfCopyPasteProcessor processor) {
super(processor);
}
finally {
StringBuilderSpinAllocator.dispose(builder);
}
myBuilder.append("\\par");
myBuilder.append(HEADER_SUFFIX);
}
@Override
public void setFontFamily(String fontFamily) {
int id = myFontNameRegistry.getId(fontFamily);
saveFontName(id);
myFontNameId = id;
}
@Override
public void setFontStyle(int fontStyle) {
// Reset formatting settings
myBuilder.append(PLAIN);
// Restore target formatting settings.
if (myForegroundId >= 0) {
saveForeground(myForegroundId);
}
if (myBackgroundId >= 0) {
saveBackground(myBackgroundId);
}
if (myFontNameId >= 0) {
saveFontName(myFontNameId);
}
saveFontSize(myFontSize);
if ((fontStyle & Font.ITALIC) > 0) {
myBuilder.append(ITALIC);
}
if ((fontStyle & Font.BOLD) > 0) {
myBuilder.append(BOLD);
}
}
@Override
public void setForeground(Color foreground) {
int id = myColorRegistry.getId(foreground);
saveForeground(id);
myForegroundId = id;
}
@Override
public void setBackground(Color background) {
int id = myColorRegistry.getId(background);
saveBackground(id);
myBackgroundId = id;
}
@Override
public void addTextFragment(CharSequence charSequence, int startOffset, int endOffset) {
myBuilder.append("\n");
for (int i = startOffset; i < endOffset; i++) {
char c = charSequence.charAt(i);
if (c > 127) {
// Escape non-ascii symbols.
myBuilder.append(String.format("\\u%04d?", (int)c));
continue;
}
switch (c) {
case '\t':
myBuilder.append(TAB);
continue;
case '\n':
myBuilder.append(NEW_LINE);
continue;
case '\\':
case '{':
case '}':
myBuilder.append('\\');
}
myBuilder.append(c);
}
}
@Override
protected InputStreamTransferableData createTransferable(@NotNull String data) {
try {
return new InputStreamTransferableData(data.getBytes(CHARSET), FLAVOR);
}
catch (UnsupportedEncodingException e) {
throw new RuntimeException(e); // shouldn't happen
}
}
private void saveBackground(int id) {
myBuilder.append("\\cb").append(id);
}
private void saveForeground(int id) {
myBuilder.append("\\cf").append(id);
}
private void saveFontName(int id) {
myBuilder.append("\\f").append(id);
}
private void saveFontSize(int size) {
myBuilder.append("\\fs").append(size * 2);
}
}
@@ -15,17 +15,9 @@
*/
package com.intellij.openapi.editor.richcopy;
import java.awt.*;
import java.util.EventListener;
import com.intellij.openapi.editor.richcopy.model.SyntaxInfo;
// we're extending EventListener to be able to use EventDispatcher in TextWithMarkupProcessor
public interface TextWithMarkupBuilder extends EventListener {
void init(Color defaultForeground, Color defaultBackground, String defaultFontFamily, int fontSize);
boolean isOverflowed();
void setFontFamily(String fontFamily);
void setFontStyle(int fontStyle);
void setForeground(Color foreground);
void setBackground(Color background);
void addTextFragment(CharSequence charSequence, int startOffset, int endOffset);
void complete();
public interface TextWithMarkupBuilder {
void reset();
void build(SyntaxInfo syntaxInfo);
}
@@ -19,14 +19,13 @@ import gnu.trove.TIntObjectHashMap;
import gnu.trove.TObjectIntHashMap;
import org.jetbrains.annotations.NotNull;
import java.io.Serializable;
import java.util.Arrays;
/**
* @author Denis Zhdanov
* @since 3/23/13 3:17 PM
*/
public abstract class AbstractRegistry<T> implements Serializable {
public abstract class AbstractRegistry<T> {
@NotNull private final TIntObjectHashMap<T> myDataById = new TIntObjectHashMap<T>();
@@ -0,0 +1,24 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed 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 com.intellij.openapi.editor.richcopy.model;
public interface MarkupHandler {
void handleText(int startOffset, int endOffset) throws Exception;
void handleForeground(int foregroundId) throws Exception;
void handleBackground(int backgroundId) throws Exception;
void handleFont(int fontNameId) throws Exception;
void handleStyle(int style) throws Exception;
}
@@ -0,0 +1,123 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed 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 com.intellij.openapi.editor.richcopy.model;
import com.intellij.util.io.CompactDataInput;
import com.intellij.util.io.CompactDataOutput;
import org.iq80.snappy.SnappyInputStream;
import org.iq80.snappy.SnappyOutputStream;
import java.io.IOException;
/**
* Not synchronized, stream implementations must be used from one thread at a time only
*/
public class OutputInfoSerializer {
private static final int TEXT_ID = 0;
private static final int STYLE_ID = 1;
private static final int FOREGROUND_ID = 2;
private static final int BACKGROUND_ID = 3;
private static final int FONT_ID = 4;
public static class OutputStream implements MarkupHandler {
private final CompactDataOutput myOutputStream;
private final java.io.OutputStream myUnderlyingOutputStream;
private int myCurrentOffset;
public OutputStream(java.io.OutputStream stream) throws IOException {
myUnderlyingOutputStream = new SnappyOutputStream(stream);
myOutputStream = new CompactDataOutput(myUnderlyingOutputStream);
}
public void close() throws IOException {
myUnderlyingOutputStream.close();
}
@Override
public void handleText(int startOffset, int endOffset) throws IOException {
myOutputStream.write(TEXT_ID);
myOutputStream.writeInt(startOffset - myCurrentOffset);
myOutputStream.writeInt(endOffset - startOffset);
myCurrentOffset = endOffset;
}
@Override
public void handleForeground(int foregroundId) throws IOException {
myOutputStream.write(FOREGROUND_ID);
myOutputStream.writeInt(foregroundId);
}
@Override
public void handleBackground(int backgroundId) throws IOException {
myOutputStream.write(BACKGROUND_ID);
myOutputStream.writeInt(backgroundId);
}
@Override
public void handleFont(int fontNameId) throws IOException {
myOutputStream.write(FONT_ID);
myOutputStream.writeInt(fontNameId);
}
@Override
public void handleStyle(int style) throws IOException {
myOutputStream.write(STYLE_ID);
myOutputStream.writeInt(style);
}
}
public static class InputStream {
private final CompactDataInput myInputStream;
private final java.io.InputStream myUnderlyingInputStream;
private int myCurrentOffset;
public InputStream(java.io.InputStream stream) throws IOException {
myUnderlyingInputStream = new SnappyInputStream(stream);
myInputStream = new CompactDataInput(myUnderlyingInputStream);
}
public void read(MarkupHandler handler) throws Exception {
int id = myInputStream.readByte();
switch (id) {
case TEXT_ID:
int startOffset = myCurrentOffset + myInputStream.readInt();
myCurrentOffset = startOffset;
int endOffset = myCurrentOffset + myInputStream.readInt();
myCurrentOffset = endOffset;
handler.handleText(startOffset, endOffset);
break;
case STYLE_ID:
handler.handleStyle(myInputStream.readInt());
break;
case FOREGROUND_ID:
handler.handleForeground(myInputStream.readInt());
break;
case BACKGROUND_ID:
handler.handleBackground(myInputStream.readInt());
break;
case FONT_ID:
handler.handleFont(myInputStream.readInt());
break;
default:
throw new IllegalStateException("Unknown tag id: " + id);
}
}
public void close() throws IOException {
myUnderlyingInputStream.close();
}
}
}
@@ -0,0 +1,252 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed 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 com.intellij.openapi.editor.richcopy.model;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
/**
* @author Denis Zhdanov
* @since 3/25/13 1:19 PM
*/
public class SyntaxInfo {
private final int myOutputInfoCount;
private final byte[] myOutputInfosSerialized;
@NotNull private final ColorRegistry myColorRegistry;
@NotNull private final FontNameRegistry myFontNameRegistry;
private final int myDefaultForeground;
private final int myDefaultBackground;
private final int mySingleFontSize;
private SyntaxInfo(int outputInfoCount,
byte[] outputInfosSerialized,
int defaultForeground,
int defaultBackground,
int singleFontSize,
@NotNull FontNameRegistry fontNameRegistry,
@NotNull ColorRegistry colorRegistry)
{
myOutputInfoCount = outputInfoCount;
myOutputInfosSerialized = outputInfosSerialized;
myDefaultForeground = defaultForeground;
myDefaultBackground = defaultBackground;
mySingleFontSize = singleFontSize;
myFontNameRegistry = fontNameRegistry;
myColorRegistry = colorRegistry;
}
@NotNull
public ColorRegistry getColorRegistry() {
return myColorRegistry;
}
@NotNull
public FontNameRegistry getFontNameRegistry() {
return myFontNameRegistry;
}
public int getDefaultForeground() {
return myDefaultForeground;
}
public int getDefaultBackground() {
return myDefaultBackground;
}
/**
* @return positive value if all tokens have the same font size (returned value);
* non-positive value otherwise
*/
public int getSingleFontSize() {
return mySingleFontSize;
}
@Override
public String toString() {
final StringBuilder b = new StringBuilder();
b.append("default colors: foreground=").append(myDefaultForeground).append(", background=").append(myDefaultBackground).append("; output infos: ");
boolean first = true;
MarkupIterator it = new MarkupIterator();
try {
while(it.hasNext()) {
if (first) {
b.append(',');
}
it.processNext(new MarkupHandler() {
@Override
public void handleText(int startOffset, int endOffset) throws Exception {
b.append("text(").append(startOffset).append(",").append(endOffset).append(")");
}
@Override
public void handleForeground(int foregroundId) throws Exception {
b.append("foreground(").append(foregroundId).append(")");
}
@Override
public void handleBackground(int backgroundId) throws Exception {
b.append("background(").append(backgroundId).append(")");
}
@Override
public void handleFont(int fontNameId) throws Exception {
b.append("font(").append(fontNameId).append(")");
}
@Override
public void handleStyle(int style) throws Exception {
b.append("style(").append(style).append(")");
}
});
first = false;
}
return b.toString();
}
finally {
it.dispose();
}
}
public static class Builder {
private final ColorRegistry myColorRegistry = new ColorRegistry();
private final FontNameRegistry myFontNameRegistry = new FontNameRegistry();
private final int myDefaultForeground;
private final int myDefaultBackground;
private final int myFontSize;
private final ByteArrayOutputStream myStream = new ByteArrayOutputStream();
private final OutputInfoSerializer.OutputStream myOutputInfoStream;
private int myOutputInfoCount;
public Builder(Color defaultForeground, Color defaultBackground, int fontSize) {
myDefaultForeground = myColorRegistry.getId(defaultForeground);
myDefaultBackground = myColorRegistry.getId(defaultBackground);
myFontSize = fontSize;
try {
myOutputInfoStream = new OutputInfoSerializer.OutputStream(myStream);
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
public void addFontStyle(int fontStyle) {
try {
myOutputInfoStream.handleStyle(fontStyle);
}
catch (IOException e) {
throw new RuntimeException(e);
}
myOutputInfoCount++;
}
public void addFontFamilyName(String fontFamilyName) {
try {
myOutputInfoStream.handleFont(myFontNameRegistry.getId(fontFamilyName));
}
catch (IOException e) {
throw new RuntimeException(e);
}
myOutputInfoCount++;
}
public void addForeground(Color foreground) {
try {
myOutputInfoStream.handleForeground(myColorRegistry.getId(foreground));
}
catch (IOException e) {
throw new RuntimeException(e);
}
myOutputInfoCount++;
}
public void addBackground(Color background) {
try {
myOutputInfoStream.handleBackground(myColorRegistry.getId(background));
}
catch (IOException e) {
throw new RuntimeException(e);
}
myOutputInfoCount++;
}
public void addText(int startOffset, int endOffset) {
try {
myOutputInfoStream.handleText(startOffset, endOffset);
}
catch (IOException e) {
throw new RuntimeException(e);
}
myOutputInfoCount++;
}
public SyntaxInfo build() {
myColorRegistry.seal();
myFontNameRegistry.seal();
try {
myOutputInfoStream.close();
}
catch (IOException e) {
throw new RuntimeException(e);
}
return new SyntaxInfo(myOutputInfoCount, myStream.toByteArray(), myDefaultForeground, myDefaultBackground, myFontSize, myFontNameRegistry, myColorRegistry);
}
}
public class MarkupIterator {
private int pos;
private final OutputInfoSerializer.InputStream myOutputInfoStream;
public MarkupIterator() {
try {
myOutputInfoStream = new OutputInfoSerializer.InputStream(new ByteArrayInputStream(myOutputInfosSerialized));
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
public boolean hasNext() {
return pos < myOutputInfoCount;
}
public void processNext(MarkupHandler handler) {
if (!hasNext()) {
throw new IllegalStateException();
}
pos++;
try {
myOutputInfoStream.read(handler);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
public void dispose() {
try {
myOutputInfoStream.close();
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
@@ -0,0 +1,130 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed 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 com.intellij.openapi.editor.richcopy.view;
import com.intellij.codeInsight.editorActions.TextBlockTransferableData;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.richcopy.model.SyntaxInfo;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.util.StringBuilderSpinAllocator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.datatransfer.DataFlavor;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
/**
* @author Denis Zhdanov
* @since 3/28/13 1:20 PM
*/
public abstract class AbstractSyntaxAwareInputStreamTransferableData extends InputStream implements TextBlockTransferableData, RawTextHolder
{
private static final Logger LOG = Logger.getInstance("#" + AbstractSyntaxAwareInputStreamTransferableData.class.getName());
protected String myRawText;
@NotNull
protected final SyntaxInfo mySyntaxInfo;
@NotNull
private final DataFlavor myDataFlavor;
@Nullable private transient InputStream myDelegate;
public AbstractSyntaxAwareInputStreamTransferableData(@NotNull SyntaxInfo syntaxInfo, @NotNull DataFlavor flavor) {
mySyntaxInfo = syntaxInfo;
myDataFlavor = flavor;
}
@Override
public DataFlavor getFlavor() {
return myDataFlavor;
}
@Override
public int getOffsetCount() {
return 0;
}
@Override
public int getOffsets(int[] offsets, int index) {
return index;
}
@Override
public int setOffsets(int[] offsets, int index) {
return index;
}
@Override
public int read() throws IOException {
return getDelegate().read();
}
@Override
public int read(@NotNull byte[] b, int off, int len) throws IOException {
return getDelegate().read(b, off, len);
}
@Override
public void close() throws IOException {
myDelegate = null;
}
@Override
public void setRawText(String rawText) {
myRawText = rawText;
}
@NotNull
private InputStream getDelegate() {
if (myDelegate != null) {
return myDelegate;
}
int maxLength = Registry.intValue("editor.richcopy.max.size.megabytes") * 1048576;
final StringBuilder buffer = StringBuilderSpinAllocator.alloc();
try {
try {
build(buffer, maxLength);
}
catch (Exception e) {
LOG.error(e);
}
String s = buffer.toString();
if (Registry.is("editor.richcopy.debug")) {
LOG.info("Resulting text: \n" + s);
}
try {
myDelegate = new ByteArrayInputStream(s.getBytes(getCharset()));
}
catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
return myDelegate;
}
finally {
StringBuilderSpinAllocator.dispose(buffer);
}
}
protected abstract void build(@NotNull StringBuilder holder, int maxLength);
@NotNull
protected abstract String getCharset();
}
@@ -0,0 +1,121 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed 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 com.intellij.openapi.editor.richcopy.view;
import com.intellij.codeInsight.editorActions.TextBlockTransferableData;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.richcopy.model.SyntaxInfo;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.util.StringBuilderSpinAllocator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.datatransfer.DataFlavor;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
/**
* @author Denis Zhdanov
* @since 3/28/13 7:09 PM
*/
public abstract class AbstractSyntaxAwareReaderTransferableData extends Reader implements TextBlockTransferableData, RawTextHolder
{
private static final Logger LOG = Logger.getInstance("#" + AbstractSyntaxAwareReaderTransferableData.class.getName());
protected String myRawText;
@NotNull
protected final SyntaxInfo mySyntaxInfo;
@NotNull
private final DataFlavor myDataFlavor;
@Nullable private transient Reader myDelegate;
public AbstractSyntaxAwareReaderTransferableData(@NotNull SyntaxInfo syntaxInfo, @NotNull DataFlavor dataFlavor) {
mySyntaxInfo = syntaxInfo;
myDataFlavor = dataFlavor;
}
@Override
public DataFlavor getFlavor() {
return myDataFlavor;
}
@Override
public int getOffsetCount() {
return 0;
}
@Override
public int getOffsets(int[] offsets, int index) {
return index;
}
@Override
public int setOffsets(int[] offsets, int index) {
return index;
}
@Override
public int read() throws IOException {
return getDelegate().read();
}
@Override
public int read(@NotNull char[] cbuf, int off, int len) throws IOException {
return getDelegate().read(cbuf, off, len);
}
@Override
public void close() throws IOException {
myDelegate = null;
}
@Override
public void setRawText(String rawText) {
myRawText = rawText;
}
@NotNull
private Reader getDelegate() {
if (myDelegate != null) {
return myDelegate;
}
int maxLength = Registry.intValue("editor.richcopy.max.size.megabytes") * 1048576;
final StringBuilder buffer = StringBuilderSpinAllocator.alloc();
try {
try {
build(buffer, maxLength);
}
catch (Exception e) {
LOG.error(e);
}
String s = buffer.toString();
if (Registry.is("editor.richcopy.debug")) {
LOG.info("Resulting text: \n" + s);
}
myDelegate = new StringReader(s);
return myDelegate;
}
finally {
StringBuilderSpinAllocator.dispose(buffer);
}
}
protected abstract void build(@NotNull StringBuilder holder, int maxLength);
}
@@ -0,0 +1,224 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed 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 com.intellij.openapi.editor.richcopy.view;
import com.intellij.openapi.editor.richcopy.model.ColorRegistry;
import com.intellij.openapi.editor.richcopy.model.FontNameRegistry;
import com.intellij.openapi.editor.richcopy.model.MarkupHandler;
import com.intellij.openapi.editor.richcopy.model.SyntaxInfo;
import com.intellij.util.StringBuilderSpinAllocator;
import com.intellij.util.ui.UIUtil;
import gnu.trove.TIntObjectHashMap;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
/**
* @author Denis Zhdanov
* @since 3/28/13 1:06 PM
*/
public class HtmlTransferableData extends AbstractSyntaxAwareReaderTransferableData implements MarkupHandler {
@NotNull public static final DataFlavor FLAVOR = new DataFlavor("text/html;class=java.io.Reader", "HTML text");
private StringBuilder myResultBuffer;
private ColorRegistry myColorRegistry;
private FontNameRegistry myFontNameRegistry;
private int myForeground;
private int myBackground;
private int myFontFamily;
private boolean myBold;
private boolean myItalic;
private final TIntObjectHashMap<String> myColors = new TIntObjectHashMap<String>();
public HtmlTransferableData(@NotNull SyntaxInfo syntaxInfo) {
super(syntaxInfo, FLAVOR);
}
@Override
protected void build(@NotNull StringBuilder holder, int maxLength) {
myResultBuffer = holder;
myColorRegistry = mySyntaxInfo.getColorRegistry();
myFontNameRegistry = mySyntaxInfo.getFontNameRegistry();
try {
buildColorMap();
myResultBuffer.append("<div style=\"border:1px inset;padding:2%;\">")
.append("<pre style=\"margin:0;padding:6px;background-color:");
// .append("<pre style='height:30%;overflow:auto;margin:0;padding:6px;background-color:")
appendColor(myResultBuffer, mySyntaxInfo.getDefaultBackground());
myResultBuffer.append(';');
if (myFontNameRegistry.size() == 1) {
appendFontFamilyRule(myResultBuffer, myFontNameRegistry.getAllIds()[0]);
myFontNameRegistry = null;
}
appendFontSizeRule(myResultBuffer, mySyntaxInfo.getSingleFontSize());
myResultBuffer.append("\" bgcolor=\"");
appendColor(myResultBuffer, mySyntaxInfo.getDefaultBackground());
myResultBuffer.append("\">");
SyntaxInfo.MarkupIterator it = mySyntaxInfo.new MarkupIterator();
try {
while(it.hasNext()) {
it.processNext(this);
if (myResultBuffer.length() > maxLength) {
myResultBuffer.append("... truncated ...");
break;
}
}
}
finally {
it.dispose();
}
myResultBuffer.append("</pre></div>");
}
finally {
myResultBuffer = null;
myRawText = null;
myColorRegistry = null;
myFontNameRegistry = null;
myColors.clear();
}
}
private void defineForeground(int id, @NotNull StringBuilder styleBuffer, @NotNull StringBuilder closeTagBuffer) {
myResultBuffer.append("<font color=\"");
appendColor(myResultBuffer, id);
myResultBuffer.append("\">");
styleBuffer.append("color:");
appendColor(styleBuffer, id);
styleBuffer.append(";");
closeTagBuffer.insert(0, "</font>");
}
private void defineBackground(int id, @NotNull StringBuilder styleBuffer, @NotNull StringBuilder closeTagBuffer) {
myResultBuffer.append("<font bgcolor=\"");
appendColor(myResultBuffer, id);
myResultBuffer.append("\">");
styleBuffer.append("background-color:");
appendColor(styleBuffer, id);
styleBuffer.append(";");
closeTagBuffer.insert(0, "</font>");
}
private void defineBold(@NotNull StringBuilder styleBuffer, @NotNull StringBuilder closeTagBuffer) {
myResultBuffer.append("<b>");
styleBuffer.append("font-weight:bold;");
closeTagBuffer.insert(0, "</b>");
}
private void defineItalic(@NotNull StringBuilder styleBuffer, @NotNull StringBuilder closeTagBuffer) {
myResultBuffer.append("<i>");
styleBuffer.append("font-style:italic;");
closeTagBuffer.insert(0, "</i>");
}
private void appendColor(StringBuilder builder, int id) {
builder.append(myColors.get(id));
}
private void buildColorMap() {
for (int id : myColorRegistry.getAllIds()) {
StringBuilder b = new StringBuilder("#");
UIUtil.appendColor(myColorRegistry.dataById(id), b);
myColors.put(id, b.toString());
}
}
private void appendFontFamilyRule(@NotNull StringBuilder styleBuffer, int fontFamilyId) {
styleBuffer.append("font-family:'").append(myFontNameRegistry.dataById(fontFamilyId)).append("';");
}
private static void appendFontSizeRule(@NotNull StringBuilder styleBuffer, int fontSize) {
styleBuffer.append("font-size:").append(fontSize).append("pt;");
}
@Override
public void handleText(int startOffset, int endOffset) {
boolean formattedText = myForeground > 0 || myBackground > 0 || myFontFamily > 0 || myBold || myItalic;
if (!formattedText) {
escapeAndAdd(startOffset, endOffset);
return;
}
StringBuilder styleBuffer = StringBuilderSpinAllocator.alloc();
StringBuilder closeTagBuffer = StringBuilderSpinAllocator.alloc();
try {
if (myForeground > 0) {
defineForeground(myForeground, styleBuffer, closeTagBuffer);
}
if (myBackground > 0) {
defineBackground(myBackground, styleBuffer, closeTagBuffer);
}
if (myBold) {
defineBold(styleBuffer, closeTagBuffer);
}
if (myItalic) {
defineItalic(styleBuffer, closeTagBuffer);
}
if (myFontFamily > 0) {
appendFontFamilyRule(styleBuffer, myFontFamily);
}
myResultBuffer.append("<span style=\"");
myResultBuffer.append(styleBuffer);
myResultBuffer.append("\">");
escapeAndAdd(startOffset, endOffset);
myResultBuffer.append("</span>");
myResultBuffer.append(closeTagBuffer);
}
finally {
StringBuilderSpinAllocator.dispose(styleBuffer);
StringBuilderSpinAllocator.dispose(closeTagBuffer);
}
}
private void escapeAndAdd(int start, int end) {
for (int i = start; i < end; i++) {
char c = myRawText.charAt(i);
switch (c) {
case '<': myResultBuffer.append("&lt;"); break;
case '>': myResultBuffer.append("&gt;"); break;
case '&': myResultBuffer.append("&amp;"); break;
default: myResultBuffer.append(c);
}
}
}
@Override
public void handleForeground(int foregroundId) throws Exception {
myForeground = foregroundId;
}
@Override
public void handleBackground(int backgroundId) throws Exception {
myBackground = backgroundId;
}
@Override
public void handleFont(int fontNameId) throws Exception {
if (myFontNameRegistry != null) {
myFontFamily = fontNameId;
}
}
@Override
public void handleStyle(int style) throws Exception {
myBold = (Font.BOLD & style) != 0;
myItalic = (Font.ITALIC & style) != 0;
}
}
@@ -1,83 +0,0 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed 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 com.intellij.openapi.editor.richcopy.view;
import com.intellij.codeInsight.editorActions.TextBlockTransferableData;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.datatransfer.DataFlavor;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* @author Denis Zhdanov
* @since 3/28/13 1:20 PM
*/
public class InputStreamTransferableData extends InputStream implements TextBlockTransferableData
{
@NotNull
private final DataFlavor myDataFlavor;
@Nullable
private InputStream myDelegate;
public InputStreamTransferableData(@NotNull byte[] data, @NotNull DataFlavor flavor) {
myDataFlavor = flavor;
myDelegate = new ByteArrayInputStream(data);
}
@Override
public DataFlavor getFlavor() {
return myDelegate == null ? null : myDataFlavor;
}
@Override
public int getOffsetCount() {
return 0;
}
@Override
public int getOffsets(int[] offsets, int index) {
return index;
}
@Override
public int setOffsets(int[] offsets, int index) {
return index;
}
@Override
public int read() throws IOException {
if (myDelegate == null) {
return -1;
}
return myDelegate.read();
}
@Override
public int read(@NotNull byte[] b, int off, int len) throws IOException {
if (myDelegate == null) {
return -1;
}
return myDelegate.read(b, off, len);
}
@Override
public void close() throws IOException {
myDelegate = null;
}
}
@@ -0,0 +1,20 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed 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 com.intellij.openapi.editor.richcopy.view;
public interface RawTextHolder {
void setRawText(String rawText);
}
@@ -1,84 +0,0 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed 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 com.intellij.openapi.editor.richcopy.view;
import com.intellij.codeInsight.editorActions.TextBlockTransferableData;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.awt.datatransfer.DataFlavor;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
/**
* @author Denis Zhdanov
* @since 3/28/13 7:09 PM
*/
public class ReaderTransferableData extends Reader implements TextBlockTransferableData
{
@NotNull
private final DataFlavor myDataFlavor;
@Nullable
private Reader myDelegate;
@SuppressWarnings("AbstractMethodCallInConstructor")
public ReaderTransferableData(@NotNull String data, @NotNull DataFlavor dataFlavor) {
myDataFlavor = dataFlavor;
myDelegate = new StringReader(data);
}
@Override
public DataFlavor getFlavor() {
return myDelegate == null ? null : myDataFlavor;
}
@Override
public int getOffsetCount() {
return 0;
}
@Override
public int getOffsets(int[] offsets, int index) {
return index;
}
@Override
public int setOffsets(int[] offsets, int index) {
return index;
}
@Override
public int read() throws IOException {
if (myDelegate == null) {
return -1;
}
return myDelegate.read();
}
@Override
public int read(@NotNull char[] cbuf, int off, int len) throws IOException {
if (myDelegate == null) {
return -1;
}
return myDelegate.read(cbuf, off, len);
}
@Override
public void close() throws IOException {
myDelegate = null;
}
}
@@ -0,0 +1,220 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed 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 com.intellij.openapi.editor.richcopy.view;
import com.intellij.openapi.editor.richcopy.model.ColorRegistry;
import com.intellij.openapi.editor.richcopy.model.FontNameRegistry;
import com.intellij.openapi.editor.richcopy.model.MarkupHandler;
import com.intellij.openapi.editor.richcopy.model.SyntaxInfo;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
public class RtfTransferableData extends AbstractSyntaxAwareInputStreamTransferableData {
@NotNull public static final DataFlavor FLAVOR = new DataFlavor("text/rtf;class=java.io.InputStream", "RTF text");
@NotNull private static final String HEADER_PREFIX = "{\\rtf1\\ansi\\deff0";
@NotNull private static final String HEADER_SUFFIX = "}";
@NotNull private static final String TAB = "\\tab\n";
@NotNull private static final String NEW_LINE = "\\line\n";
@NotNull private static final String BOLD = "\\b";
@NotNull private static final String ITALIC = "\\i";
@NotNull private static final String PLAIN = "\\plain\n";
public RtfTransferableData(@NotNull SyntaxInfo syntaxInfo) {
super(syntaxInfo, FLAVOR);
}
@Override
protected void build(@NotNull final StringBuilder holder, final int maxLength) {
header(mySyntaxInfo, holder, new Runnable() {
@Override
public void run() {
rectangularBackground(mySyntaxInfo, holder, new Runnable() {
@Override
public void run() {
content(mySyntaxInfo, holder, myRawText, maxLength);
}
});
}
});
}
@NotNull
@Override
protected String getCharset() {
return "US-ASCII";
}
private static void header(@NotNull SyntaxInfo syntaxInfo, @NotNull StringBuilder buffer, @NotNull Runnable next) {
buffer.append(HEADER_PREFIX);
// Color table.
buffer.append("{\\colortbl;");
ColorRegistry colorRegistry = syntaxInfo.getColorRegistry();
for (int id : colorRegistry.getAllIds()) {
Color color = colorRegistry.dataById(id);
buffer.append(String.format("\\red%d\\green%d\\blue%d;", color.getRed(), color.getGreen(), color.getBlue()));
}
buffer.append("}\n");
// Font table.
buffer.append("{\\fonttbl");
FontNameRegistry fontNameRegistry = syntaxInfo.getFontNameRegistry();
for (int id : fontNameRegistry.getAllIds()) {
String fontName = fontNameRegistry.dataById(id);
buffer.append(String.format("{\\f%d %s;}", id, fontName));
}
buffer.append("}\n");
next.run();
buffer.append(HEADER_SUFFIX);
}
private static void rectangularBackground(@NotNull SyntaxInfo syntaxInfo, @NotNull StringBuilder buffer, @NotNull Runnable next) {
buffer.append("\n\\s0\\box\\brdrhair\\brdrcf").append(syntaxInfo.getDefaultForeground()).append("\\brsp317").append("\\cbpat")
.append(syntaxInfo.getDefaultBackground());
saveBackground(buffer, syntaxInfo.getDefaultBackground());
next.run();
buffer.append("\\par");
}
private static void content(@NotNull SyntaxInfo syntaxInfo, @NotNull StringBuilder buffer, @NotNull String rawText, int maxLength) {
MyVisitor visitor = new MyVisitor(buffer, rawText, syntaxInfo.getSingleFontSize());
SyntaxInfo.MarkupIterator it = syntaxInfo.new MarkupIterator();
try {
while(it.hasNext()) {
it.processNext(visitor);
if (buffer.length() > maxLength) {
buffer.append("... truncated ...");
break;
}
}
}
finally {
it.dispose();
}
}
private static void saveBackground(@NotNull StringBuilder buffer, int id) {
buffer.append("\\cb").append(id);
}
private static void saveForeground(@NotNull StringBuilder buffer, int id) {
buffer.append("\\cf").append(id);
}
private static void saveFontName(@NotNull StringBuilder buffer, int id) {
buffer.append("\\f").append(id);
}
private static void saveFontSize(@NotNull StringBuilder buffer, int size) {
buffer.append("\\fs").append(size * 2);
}
private static class MyVisitor implements MarkupHandler {
@NotNull private final StringBuilder myBuffer;
@NotNull private final String myRawText;
private int myBackgroundId = -1;
private int myForegroundId = -1;
private int myFontNameId = -1;
private int myFontStyle = -1;
private int myFontSize = -1;
MyVisitor(@NotNull StringBuilder buffer, @NotNull String rawText, int fontSize) {
myBuffer = buffer;
myRawText = rawText;
myFontSize = fontSize;
}
@Override
public void handleText(int startOffset, int endOffset) throws Exception {
myBuffer.append("\n");
for (int i = startOffset; i < endOffset; i++) {
char c = myRawText.charAt(i);
if (c > 127) {
// Escape non-ascii symbols.
myBuffer.append(String.format("\\u%04d?", (int)c));
continue;
}
switch (c) {
case '\t':
myBuffer.append(TAB);
continue;
case '\n':
myBuffer.append(NEW_LINE);
continue;
case '\\':
case '{':
case '}':
myBuffer.append('\\');
}
myBuffer.append(c);
}
}
@Override
public void handleForeground(int foregroundId) throws Exception {
saveForeground(myBuffer, foregroundId);
myForegroundId = foregroundId;
}
@Override
public void handleBackground(int backgroundId) throws Exception {
saveBackground(myBuffer, backgroundId);
myBackgroundId = backgroundId;
}
@Override
public void handleFont(int fontNameId) throws Exception {
saveFontName(myBuffer, fontNameId);
myFontNameId = fontNameId;
}
@Override
public void handleStyle(int style) throws Exception {
// Reset formatting settings
myBuffer.append(PLAIN);
// Restore target formatting settings.
if (myForegroundId >= 0) {
saveForeground(myBuffer, myForegroundId);
}
if (myBackgroundId >= 0) {
saveBackground(myBuffer, myBackgroundId);
}
if (myFontNameId >= 0) {
saveFontName(myBuffer, myFontNameId);
}
if (myFontSize > 0) {
saveFontSize(myBuffer, myFontSize);
}
myFontStyle = style;
if ((myFontStyle & Font.ITALIC) > 0) {
myBuffer.append(ITALIC);
}
if ((myFontStyle & Font.BOLD) > 0) {
myBuffer.append(BOLD);
}
}
}
}
@@ -154,7 +154,7 @@ public class CopyPasteManagerEx extends CopyPasteManager implements ClipboardOwn
addToTheTopOfTheStack(content);
}
else {
moveContentToStackTop(same);
moveContentToStackTop(same, false); // notification is done in setContents() method
}
}
catch (UnsupportedFlavorException ignore) { }
@@ -283,12 +283,18 @@ public class CopyPasteManagerEx extends CopyPasteManager implements ClipboardOwn
}
public void moveContentToStackTop(Transferable t) {
moveContentToStackTop(t, true);
}
private void moveContentToStackTop(Transferable t, boolean notifyOthers) {
Transferable current = myData.isEmpty() ? null : myData.get(0);
if (!Comparing.equal(t, current)) {
myData.remove(t);
myData.add(0, t);
setSystemClipboardContent(t);
fireContentChanged(current, t);
if (notifyOthers) {
setSystemClipboardContent(t);
fireContentChanged(current, t);
}
}
}
}
@@ -884,6 +884,9 @@
<copyPastePostProcessor implementation="com.intellij.openapi.editor.richcopy.TextWithMarkupProcessor"/>
<copyPastePostProcessor implementation="com.intellij.openapi.editor.richcopy.RtfCopyPasteProcessor"/>
<copyPastePostProcessor implementation="com.intellij.openapi.editor.richcopy.HtmlCopyPasteProcessor"/>
<!--the following bindings use 'first' order to make sure they capture raw text before any other processor modifies it -->
<copyPastePreProcessor implementation="com.intellij.openapi.editor.richcopy.RtfCopyPasteProcessor$RawTextSetter" order="first" id="richcopy-rtf"/>
<copyPastePreProcessor implementation="com.intellij.openapi.editor.richcopy.HtmlCopyPasteProcessor$RawTextSetter" order="first" id="richcopy-html"/>
</extensions>
</idea-plugin>
@@ -16,6 +16,8 @@
package com.intellij.openapi.editor.richcopy;
import com.intellij.openapi.application.ex.PathManagerEx;
import com.intellij.openapi.editor.richcopy.view.HtmlTransferableData;
import com.intellij.openapi.editor.richcopy.view.RtfTransferableData;
import com.intellij.openapi.ide.CopyPasteManager;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase;
@@ -48,14 +50,14 @@ public class RichCopyTest extends LightPlatformCodeInsightFixtureTestCase {
Transferable contents = CopyPasteManager.getInstance().getContents();
assertNotNull(contents);
assertTrue(contents.isDataFlavorSupported(HtmlCopyPasteProcessor.FLAVOR));
assertTrue(contents.isDataFlavorSupported(HtmlTransferableData.FLAVOR));
String expectedHtml = getFileContents(getTestName(false) + ".html");
String actualHtml = readFully((Reader)contents.getTransferData(HtmlCopyPasteProcessor.FLAVOR));
String actualHtml = readFully((Reader)contents.getTransferData(HtmlTransferableData.FLAVOR));
assertMatches("HTML contents differs", expectedHtml, actualHtml);
assertTrue(contents.isDataFlavorSupported(RtfCopyPasteProcessor.FLAVOR));
assertTrue(contents.isDataFlavorSupported(RtfTransferableData.FLAVOR));
String expectedRtf = getFileContents(getTestName(false) + ".rtf");
String actualRtf = readFully((InputStream)contents.getTransferData(RtfCopyPasteProcessor.FLAVOR));
String actualRtf = readFully((InputStream)contents.getTransferData(RtfTransferableData.FLAVOR));
assertMatches("RTF contents differs", expectedRtf, actualRtf);
}
@@ -19,12 +19,13 @@ import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.LogicalPosition;
import com.intellij.openapi.editor.SelectionModel;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.richcopy.model.ColorRegistry;
import com.intellij.openapi.editor.richcopy.model.MarkupHandler;
import com.intellij.openapi.editor.richcopy.model.SyntaxInfo;
import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase;
import com.intellij.ui.JBColor;
import junit.framework.TestCase;
import java.awt.*;
/**
* @author Denis Zhdanov
* @since 3/27/13 11:11 AM
@@ -235,56 +236,64 @@ public class SyntaxInfoConstructionTest extends LightPlatformCodeInsightFixtureT
}
private String getSyntaxInfo() {
final StringBuilder syntaxInfo = new StringBuilder();
Editor editor = myFixture.getEditor();
final StringBuilder builder = new StringBuilder();
final Editor editor = myFixture.getEditor();
final String text = editor.getSelectionModel().getSelectedText(true);
assertNotNull(text);
TextWithMarkupProcessor processor = new TextWithMarkupProcessor();
processor.addBuilder(new TextWithMarkupBuilder() {
@Override
public void init(Color defaultForeground, Color defaultBackground, String defaultFontFamily, int fontSize) {
assertEquals(JBColor.BLACK, defaultForeground);
assertEquals(JBColor.WHITE, defaultBackground);
assertEquals(getFontSize(), fontSize);
public void reset() {
}
@Override
public boolean isOverflowed() {
return false;
}
public void build(SyntaxInfo syntaxInfo) {
final ColorRegistry colorRegistry = syntaxInfo.getColorRegistry();
assertEquals(JBColor.BLACK, colorRegistry.dataById(syntaxInfo.getDefaultForeground()));
assertEquals(JBColor.WHITE, colorRegistry.dataById(syntaxInfo.getDefaultBackground()));
assertEquals(getFontSize(), syntaxInfo.getSingleFontSize());
SyntaxInfo.MarkupIterator it = syntaxInfo.new MarkupIterator();
try {
while (it.hasNext()) {
it.processNext(new MarkupHandler() {
@Override
public void handleText(int startOffset, int endOffset) throws Exception {
builder.append("text=").append(text.substring(startOffset, endOffset)).append('\n');
}
@Override
public void setFontFamily(String fontFamily) {
}
@Override
public void handleForeground(int foregroundId) throws Exception {
builder.append("foreground=").append(colorRegistry.dataById(foregroundId)).append(',');
}
@Override
public void setFontStyle(int fontStyle) {
syntaxInfo.append("fontStyle=").append(fontStyle).append(',');
}
@Override
public void handleBackground(int backgroundId) throws Exception {
builder.append("background=").append(colorRegistry.dataById(backgroundId)).append(',');
}
@Override
public void setForeground(Color foreground) {
syntaxInfo.append("foreground=").append(foreground).append(',');
}
@Override
public void handleFont(int fontNameId) throws Exception {
assertEquals(1, fontNameId);
}
@Override
public void setBackground(Color background) {
syntaxInfo.append("background=").append(background).append(',');
@Override
public void handleStyle(int style) throws Exception {
builder.append("fontStyle=").append(style).append(',');
}
});
}
}
finally {
it.dispose();
}
}
@Override
public void addTextFragment(CharSequence charSequence, int startOffset, int endOffset) {
syntaxInfo.append("text=").append(charSequence.subSequence(startOffset, endOffset)).append('\n');
}
@Override
public void complete() {
}
});
});
SelectionModel selectionModel = editor.getSelectionModel();
processor.collectTransferableData(myFixture.getFile(), editor, selectionModel.getBlockSelectionStarts(), selectionModel.getBlockSelectionEnds());
return syntaxInfo.toString();
return builder.toString();
}
private void init(String text) {