mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
Use Android-compatible CompletableFuture
Updated jls.jar Much more changes
This commit is contained in:
+4
-3
@@ -77,6 +77,10 @@ android {
|
||||
exclude 'about_files/LICENSE-2.0.txt'
|
||||
exclude 'plugin.xml'
|
||||
exclude 'plugin.properties'
|
||||
exclude 'about.mappings'
|
||||
exclude 'about.properties'
|
||||
exclude 'about.ini'
|
||||
exclude 'modeling32.png'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,9 +146,6 @@ dependencies {
|
||||
implementation project(':javapoet')
|
||||
implementation project(':lsp')
|
||||
|
||||
// LSP4J
|
||||
implementation 'org.eclipse.lsp4j:org.eclipse.lsp4j:0.12.0'
|
||||
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -90,6 +90,7 @@ import org.eclipse.lsp4j.Diagnostic;
|
||||
import org.eclipse.lsp4j.DidChangeConfigurationParams;
|
||||
import org.eclipse.lsp4j.InitializeResult;
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import com.itsaky.androidide.databinding.LayoutDiagnosticInfoBinding;
|
||||
|
||||
public class EditorActivity extends StudioActivity implements FileTreeFragment.FileActionListener,
|
||||
TabLayout.OnTabSelectedListener,
|
||||
@@ -101,6 +102,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
EditorActivityProvider {
|
||||
|
||||
private ActivityEditorBinding mBinding;
|
||||
private LayoutDiagnosticInfoBinding mDiagnosticInfoBinding;
|
||||
private EditorPagerAdapter mPagerAdapter;
|
||||
private FileTreeFragment mFileTreeFragment;
|
||||
private EditorFragment mCurrentFragment;
|
||||
@@ -162,6 +164,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
@Override
|
||||
protected View bindLayout() {
|
||||
mBinding = ActivityEditorBinding.inflate(getLayoutInflater());
|
||||
mDiagnosticInfoBinding = mBinding.diagnosticInfo;
|
||||
return mBinding.getRoot();
|
||||
}
|
||||
|
||||
@@ -202,6 +205,7 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
registerLogReceiver();
|
||||
setupContainers();
|
||||
setupSignatureText();
|
||||
setupDiagnosticInfo();
|
||||
|
||||
startLanguageServers();
|
||||
}
|
||||
@@ -603,6 +607,10 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
public ActivityEditorBinding getBinding() {
|
||||
return mBinding;
|
||||
}
|
||||
|
||||
public LayoutDiagnosticInfoBinding getDiagnosticBinding() {
|
||||
return mDiagnosticInfoBinding;
|
||||
}
|
||||
|
||||
/**
|
||||
* Positions the view to the provided coordinates and within screen
|
||||
@@ -611,13 +619,12 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
* @param initialX Initial X coordinate of view
|
||||
* @param initialY Initial Y coordinate of view
|
||||
*/
|
||||
public void positionViewWithinScreen(View view, float initialX, float initialY) {
|
||||
public void positionViewWithinScreen(final View view, final float initialX, final float initialY) {
|
||||
view.setX(initialX);
|
||||
view.setY(initialY);
|
||||
|
||||
Rect r = new Rect();
|
||||
int width = view.getWidth();
|
||||
initialX = view.getX();
|
||||
final Rect r = new Rect();
|
||||
final int width = view.getWidth();
|
||||
view.getWindowVisibleDisplayFrame(r);
|
||||
if (r.width() != width) {
|
||||
|
||||
@@ -625,16 +632,14 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
* Will be true when the view is going out of screen to left
|
||||
*/
|
||||
if (initialX < r.left) {
|
||||
initialX = SizeUtils.dp2px(8); // an offset of 8dp from the left edge of screen
|
||||
view.setX(initialX);
|
||||
view.setX(SizeUtils.dp2px(8)); // an offset of 8dp from the left edge of screen
|
||||
}
|
||||
|
||||
/**
|
||||
* Will be true when the view is going out of screen to right
|
||||
*/
|
||||
if (initialX + width > r.right) {
|
||||
initialX = r.right - SizeUtils.dp2px(8) - width; // position to the right but leaving 8dp space from the right edge of screen
|
||||
view.setX(initialX);
|
||||
view.setX(r.right - SizeUtils.dp2px(8) - width); // position to the right but leaving 8dp space from the right edge of screen
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1010,6 +1015,16 @@ public class EditorActivity extends StudioActivity implements FileTreeFragment.F
|
||||
mBinding.symbolText.setBackgroundDrawable(gd);
|
||||
mBinding.symbolText.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void setupDiagnosticInfo() {
|
||||
GradientDrawable gd = new GradientDrawable();
|
||||
gd.setShape(GradientDrawable.RECTANGLE);
|
||||
gd.setColor(0xff212121);
|
||||
gd.setStroke(1, 0xffffffff);
|
||||
gd.setCornerRadius(8);
|
||||
mDiagnosticInfoBinding.getRoot().setBackground(gd);
|
||||
mDiagnosticInfoBinding.getRoot().setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
private void setupContainers() {
|
||||
removeFromParent(getBuildView());
|
||||
|
||||
@@ -201,6 +201,7 @@ public class TerminalActivity extends StudioActivity {
|
||||
public boolean onKeyDown(int keyCode, KeyEvent e, TerminalSession session) {
|
||||
if(keyCode == KeyEvent.KEYCODE_ENTER && !session.isRunning()) {
|
||||
finish();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -258,15 +259,7 @@ public class TerminalActivity extends StudioActivity {
|
||||
|
||||
@Override
|
||||
public float onScale(float scale) {
|
||||
if(scale < 0.9f || scale > 1.1f) {
|
||||
boolean increase = scale > 1.1f;
|
||||
int newSize = (increase ? 1 : -1) * 2;
|
||||
currentTextSize += newSize;
|
||||
if(terminal != null)
|
||||
terminal.setTextSize(currentTextSize);
|
||||
}
|
||||
|
||||
return scale;
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -315,6 +308,16 @@ public class TerminalActivity extends StudioActivity {
|
||||
return state != null && state.booleanValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readFnKey() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readShiftKey() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEmulatorSet() {
|
||||
setTerminalCursorBlinkingState(true);
|
||||
|
||||
@@ -115,8 +115,6 @@ public class BuildServiceHandler extends IDEHandler implements IDEService.BuildL
|
||||
activity().setStatus(msg);
|
||||
}
|
||||
|
||||
activity().showViewOptions();
|
||||
activity().showBuildResult();
|
||||
activity().getApp().getPrefManager().putBoolean(PreferenceManager.KEY_IS_FIRST_PROJECT_BUILD, false);
|
||||
activity().invalidateOptionsMenu();
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ public class GroovyLanguage extends BaseLanguage {
|
||||
|
||||
@Override
|
||||
public boolean useTab() {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -9,7 +9,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java9.util.concurrent.CompletableFuture;
|
||||
import org.eclipse.lsp4j.CompletionItem;
|
||||
import org.eclipse.lsp4j.CompletionList;
|
||||
import org.eclipse.lsp4j.CompletionParams;
|
||||
|
||||
@@ -86,7 +86,7 @@ public class JavaLanguage extends BaseLanguage {
|
||||
|
||||
@Override
|
||||
public boolean useTab() {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -105,8 +105,6 @@ public class JavaLanguage extends BaseLanguage {
|
||||
|
||||
@Override
|
||||
public boolean matchesRequirement(String beforeText, String afterText) {
|
||||
beforeText = beforeText.trim();
|
||||
afterText = afterText.trim();
|
||||
return beforeText.endsWith("{") && afterText.startsWith("}");
|
||||
}
|
||||
|
||||
@@ -116,7 +114,10 @@ public class JavaLanguage extends BaseLanguage {
|
||||
int advanceBefore = getIndentAdvance(beforeText);
|
||||
int advanceAfter = getIndentAdvance(afterText);
|
||||
String text;
|
||||
StringBuilder sb = new StringBuilder("\n").append(TextUtils.createIndent(count + advanceBefore, tabSize, useTab())).append('\n').append(text = TextUtils.createIndent(count + advanceAfter, tabSize, useTab()));
|
||||
StringBuilder sb = new StringBuilder("\n")
|
||||
.append(TextUtils.createIndent(count + advanceBefore, tabSize, useTab()))
|
||||
.append('\n')
|
||||
.append(text = TextUtils.createIndent(count + advanceAfter, tabSize, useTab()));
|
||||
int shiftLeft = text.length() + 1;
|
||||
return new HandleResult(sb, shiftLeft);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ public class XMLLanguage extends BaseLanguage {
|
||||
super(file);
|
||||
this.completer = new XMLAutoComplete();
|
||||
this.analyzer = new XMLAnalyzer();
|
||||
this.newlineHandlers = new NewlineHandler[]{new IndentHandler()};
|
||||
this.newlineHandlers = new NewlineHandler[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -59,6 +59,11 @@ public class XMLLanguage extends BaseLanguage {
|
||||
|| ch == '/';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsWholePreviousContentForIndent() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndentAdvance(String content) {
|
||||
try {
|
||||
@@ -94,7 +99,7 @@ public class XMLLanguage extends BaseLanguage {
|
||||
|
||||
@Override
|
||||
public boolean useTab() {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -106,20 +111,4 @@ public class XMLLanguage extends BaseLanguage {
|
||||
public NewlineHandler[] getNewlineHandlers() {
|
||||
return newlineHandlers;
|
||||
}
|
||||
|
||||
class IndentHandler implements NewlineHandler {
|
||||
|
||||
@Override
|
||||
public boolean matchesRequirement(String beforeText, String afterText) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandleResult handleNewline(String beforeText, String afterText, int tabSize) {
|
||||
int count = TextUtils.countLeadingSpaceCount(beforeText, tabSize);
|
||||
int advanceBefore = getIndentAdvance(beforeText);
|
||||
StringBuilder sb = new StringBuilder("\n").append(TextUtils.createIndent(count + advanceBefore, tabSize, useTab()));
|
||||
return new HandleResult(sb, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-6
@@ -141,12 +141,7 @@ public class XMLCompletionService {
|
||||
if(!closing) {
|
||||
sb.append("\n");
|
||||
sb.append(INITIAL_TAG_ATTRIBUTES);
|
||||
if(view.isViewGroup) {
|
||||
sb.append(">$0</");
|
||||
sb.append(view.simpleName);
|
||||
} else {
|
||||
sb.append("$0/");
|
||||
}
|
||||
sb.append("\n$0/");
|
||||
} else {
|
||||
sb.append(">$0");
|
||||
}
|
||||
|
||||
@@ -3,6 +3,10 @@ package com.itsaky.androidide.lsp;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.view.View;
|
||||
import androidx.transition.ChangeBounds;
|
||||
import androidx.transition.Fade;
|
||||
import androidx.transition.TransitionManager;
|
||||
import androidx.transition.TransitionSet;
|
||||
import com.blankj.utilcode.util.FileIOUtils;
|
||||
import com.blankj.utilcode.util.FileUtils;
|
||||
import com.blankj.utilcode.util.ThrowableUtils;
|
||||
@@ -12,6 +16,7 @@ import com.itsaky.androidide.EditorActivity;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.adapters.DiagnosticsAdapter;
|
||||
import com.itsaky.androidide.adapters.SearchListAdapter;
|
||||
import com.itsaky.androidide.databinding.LayoutDiagnosticInfoBinding;
|
||||
import com.itsaky.androidide.fragments.EditorFragment;
|
||||
import com.itsaky.androidide.interfaces.EditorActivityProvider;
|
||||
import com.itsaky.androidide.models.DiagnosticGroup;
|
||||
@@ -29,7 +34,7 @@ import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java9.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
import org.eclipse.lsp4j.Diagnostic;
|
||||
import org.eclipse.lsp4j.Location;
|
||||
@@ -60,6 +65,8 @@ public abstract class AbstractLanguageClient implements IDELanguageClient {
|
||||
private final OnConnectedListener onConnectedListener;
|
||||
|
||||
private boolean isConnected;
|
||||
|
||||
public static final int DIAGNOSTIC_TRANSITION_DURATION = 80;
|
||||
|
||||
public AbstractLanguageClient(StarterListener starterListener, OnConnectedListener onConnectedListener) {
|
||||
this.starterListener = starterListener;
|
||||
@@ -118,12 +125,55 @@ public abstract class AbstractLanguageClient implements IDELanguageClient {
|
||||
}
|
||||
}
|
||||
|
||||
public void showDiagnostic(Diagnostic diagnostic, final CodeEditor editor) {
|
||||
if(activity() == null || activity().getDiagnosticBinding() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(diagnostic == null) {
|
||||
hideDiagnostics();
|
||||
return;
|
||||
}
|
||||
|
||||
final LayoutDiagnosticInfoBinding binding = activity().getDiagnosticBinding();
|
||||
binding.getRoot().setText(diagnostic.getMessage());
|
||||
|
||||
TransitionSet set = new TransitionSet();
|
||||
set.addTransition(new ChangeBounds());
|
||||
set.addTransition(new Fade());
|
||||
set.setDuration(DIAGNOSTIC_TRANSITION_DURATION);
|
||||
|
||||
binding.getRoot().setVisibility(View.VISIBLE);
|
||||
|
||||
final float[] cursor = editor.getCursorPosition();
|
||||
|
||||
float x = editor.updateCursorAnchor() - (binding.getRoot().getWidth() / 2);
|
||||
float y = activity().getBinding().editorAppBarLayout.getHeight() + (cursor[0] - editor.getRowHeight() - editor.getOffsetY() - binding.getRoot().getHeight());
|
||||
binding.getRoot().setX(x);
|
||||
binding.getRoot().setY(y);
|
||||
activity().positionViewWithinScreen(binding.getRoot(), x, y);
|
||||
|
||||
}
|
||||
|
||||
public void hideDiagnostics() {
|
||||
if(activity() == null || activity().getDiagnosticBinding() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TransitionSet set = new TransitionSet();
|
||||
set.addTransition(new ChangeBounds());
|
||||
set.addTransition(new Fade());
|
||||
set.setDuration(DIAGNOSTIC_TRANSITION_DURATION);
|
||||
|
||||
activity().getDiagnosticBinding().getRoot().setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by {@link io.github.rosemoe.editor.widget.CodeEditor CodeEditor} to show signature help in EditorActivity
|
||||
*/
|
||||
public void showSignatureHelp(SignatureHelp signature, File file) {
|
||||
if(signature == null || signature.getSignatures() == null) {
|
||||
activity().getBinding().symbolText.setVisibility(View.GONE);
|
||||
hideSignatureHelp();
|
||||
return;
|
||||
}
|
||||
SignatureInformation info = signatureWithMostParams(signature);
|
||||
@@ -136,6 +186,7 @@ public abstract class AbstractLanguageClient implements IDELanguageClient {
|
||||
|
||||
float x = editor.updateCursorAnchor() - (activity().getBinding().symbolText.getWidth() / 2);
|
||||
float y = activity().getBinding().editorAppBarLayout.getHeight() + (cursor[0] - editor.getRowHeight() - editor.getOffsetY() - activity().getBinding().symbolText.getHeight());
|
||||
TransitionManager.beginDelayedTransition(activity().getBinding().getRoot());
|
||||
activity().getBinding().symbolText.setVisibility(View.VISIBLE);
|
||||
activity().positionViewWithinScreen(activity().getBinding().symbolText, x, y);
|
||||
}
|
||||
@@ -146,6 +197,7 @@ public abstract class AbstractLanguageClient implements IDELanguageClient {
|
||||
*/
|
||||
public void hideSignatureHelp() {
|
||||
if(activity() == null) return;
|
||||
TransitionManager.beginDelayedTransition(activity().getBinding().getRoot());
|
||||
activity().getBinding().symbolText.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import com.itsaky.lsp.services.IDELanguageServer;
|
||||
import com.itsaky.lsp.services.IDETextDocumentService;
|
||||
import com.itsaky.lsp.services.IDEWorkspaceService;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java9.util.concurrent.CompletableFuture;
|
||||
import org.eclipse.lsp4j.CallHierarchyIncomingCall;
|
||||
import org.eclipse.lsp4j.CallHierarchyIncomingCallsParams;
|
||||
import org.eclipse.lsp4j.CallHierarchyItem;
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.itsaky.androidide.lsp.client.java;
|
||||
|
||||
import com.itsaky.androidide.lsp.AbstractLanguageClient;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java9.util.concurrent.CompletableFuture;
|
||||
import org.eclipse.lsp4j.ApplyWorkspaceEditParams;
|
||||
import org.eclipse.lsp4j.ApplyWorkspaceEditResponse;
|
||||
import org.eclipse.lsp4j.ConfigurationParams;
|
||||
@@ -60,8 +60,7 @@ public class JavaLanguageClient extends AbstractLanguageClient {
|
||||
public CompletableFuture<Void> registerCapability(RegistrationParams p1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
public void setTrace(SetTraceParams p1) {
|
||||
LOG.info("setTrace: ", gson.toJson(p1));
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java9.util.concurrent.CompletableFuture;
|
||||
import org.eclipse.lsp4j.InitializeParams;
|
||||
import org.eclipse.lsp4j.InitializeResult;
|
||||
import org.eclipse.lsp4j.InitializedParams;
|
||||
|
||||
@@ -120,11 +120,11 @@ public final class Environment {
|
||||
final Map<String, String> map = new HashMap<>();
|
||||
map.put("HOME", HOME.getAbsolutePath());
|
||||
map.put("GRADLE_USER_HOME", GRADLE_USER_HOME.getAbsolutePath());
|
||||
map.put("GRADLE_HOME", GRADLE_HOME.getAbsolutePath());
|
||||
map.put("TMPDIR", TMP_DIR.getAbsolutePath());
|
||||
map.put("PROJECT_DIR", PROJECTS_DIR.getAbsolutePath());
|
||||
map.put("ANDROID_DATA", "/data");
|
||||
map.put("ANDROID_ROOT", "/system");
|
||||
map.put("LANG", "en_US.UTF-8");
|
||||
map.put("LC_ALL", "en_US.UTF-8");
|
||||
|
||||
if(!publicUse) {
|
||||
// These environment variables must not be provided to users
|
||||
@@ -132,10 +132,10 @@ public final class Environment {
|
||||
}
|
||||
|
||||
map.put("SYSROOT", SYSROOT.getAbsolutePath());
|
||||
map.put("PREFIX", SYSROOT.getAbsolutePath());
|
||||
|
||||
map.put("BUSYBOX", BUSYBOX.getAbsolutePath());
|
||||
map.put("SHELL", SHELL.getAbsolutePath());
|
||||
map.put("CONFIG_SHELL", SHELL.getAbsolutePath());
|
||||
map.put("TERM", "screen");
|
||||
|
||||
map.put("ANDROID_HOME", ANDROID_HOME.getAbsolutePath());
|
||||
@@ -181,7 +181,7 @@ public final class Environment {
|
||||
environment.put(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static String path(File file) {
|
||||
return file.getAbsolutePath();
|
||||
}
|
||||
|
||||
@@ -75,10 +75,15 @@ public interface EditorLanguage {
|
||||
*/
|
||||
boolean isAutoCompleteChar(char ch);
|
||||
|
||||
/**
|
||||
* Return {@code true} if the language needs whole previous content for #getIndentAdvance(String)
|
||||
*/
|
||||
boolean needsWholePreviousContentForIndent();
|
||||
|
||||
/**
|
||||
* Get advance for indent
|
||||
*
|
||||
* @param content Content of a line
|
||||
* @param content Content of a line if
|
||||
* @return Advance space count
|
||||
*/
|
||||
int getIndentAdvance(String content);
|
||||
|
||||
@@ -23,4 +23,9 @@ public abstract class AbstractEditorLanguage implements EditorLanguage {
|
||||
public File getFile() {
|
||||
return this.file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsWholePreviousContentForIndent() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,11 @@ package io.github.rosemoe.editor.text;
|
||||
|
||||
import io.github.rosemoe.editor.interfaces.EditorLanguage;
|
||||
import io.github.rosemoe.editor.util.IntPair;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import io.github.rosemoe.editor.widget.CodeEditor;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author Rose
|
||||
|
||||
@@ -30,17 +30,13 @@ import android.graphics.Path;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.MutableInt;
|
||||
import android.util.TypedValue;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
@@ -48,7 +44,6 @@ import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ScaleGestureDetector;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.view.inputmethod.CursorAnchorInfo;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
@@ -57,9 +52,7 @@ import android.view.inputmethod.ExtractedTextRequest;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.OverScroller;
|
||||
import android.widget.PopupWindow;
|
||||
import android.widget.SearchView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -67,9 +60,6 @@ import androidx.annotation.Px;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.view.ActionMode;
|
||||
import androidx.appcompat.view.menu.MenuBuilder;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.view.GravityCompat;
|
||||
import com.blankj.utilcode.util.SizeUtils;
|
||||
import com.blankj.utilcode.util.ThreadUtils;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.itsaky.androidide.R;
|
||||
@@ -112,8 +102,9 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java9.util.concurrent.CompletableFuture;
|
||||
import org.eclipse.lsp4j.CodeActionOptions;
|
||||
import org.eclipse.lsp4j.CompletionOptions;
|
||||
import org.eclipse.lsp4j.DefinitionOptions;
|
||||
import org.eclipse.lsp4j.DefinitionParams;
|
||||
import org.eclipse.lsp4j.Diagnostic;
|
||||
@@ -124,7 +115,6 @@ import org.eclipse.lsp4j.DidOpenTextDocumentParams;
|
||||
import org.eclipse.lsp4j.DidSaveTextDocumentParams;
|
||||
import org.eclipse.lsp4j.Location;
|
||||
import org.eclipse.lsp4j.LocationLink;
|
||||
import org.eclipse.lsp4j.ParameterInformation;
|
||||
import org.eclipse.lsp4j.Position;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.ReferenceContext;
|
||||
@@ -137,13 +127,11 @@ import org.eclipse.lsp4j.SignatureHelpContext;
|
||||
import org.eclipse.lsp4j.SignatureHelpOptions;
|
||||
import org.eclipse.lsp4j.SignatureHelpParams;
|
||||
import org.eclipse.lsp4j.SignatureHelpTriggerKind;
|
||||
import org.eclipse.lsp4j.SignatureInformation;
|
||||
import org.eclipse.lsp4j.TextDocumentContentChangeEvent;
|
||||
import org.eclipse.lsp4j.TextDocumentIdentifier;
|
||||
import org.eclipse.lsp4j.TextDocumentItem;
|
||||
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.eclipse.lsp4j.CompletionOptions;
|
||||
|
||||
/**
|
||||
* CodeEditor is a editor that can highlight text regions by doing basic syntax analyzing
|
||||
@@ -287,9 +275,9 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
private String mLnTip = "Line:";
|
||||
private EditorLanguage mLanguage;
|
||||
private IDELanguageServer mLanguageServer;
|
||||
private AbstractLanguageClient mLanguageClient;
|
||||
private long mLastMakeVisible = 0;
|
||||
private EditorAutoCompleteWindow mCompletionWindow;
|
||||
private DiagnosticWindow mDiagnosticWindow;
|
||||
private EditorTouchEventHandler mEventHandler;
|
||||
private Paint.Align mLineNumberAlign;
|
||||
private GestureDetector mBasicDetector;
|
||||
@@ -572,7 +560,7 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
setFocusableInTouchMode(true);
|
||||
mConnection = new EditorInputConnection(this);
|
||||
mCompletionWindow = new EditorAutoCompleteWindow(this);
|
||||
mDiagnosticWindow = new DiagnosticWindow(this);
|
||||
|
||||
mVerticalEdgeGlow = new MaterialEdgeEffect();
|
||||
mHorizontalGlow = new MaterialEdgeEffect();
|
||||
mOverrideSymbolPairs = new SymbolPairMatch();
|
||||
@@ -673,18 +661,12 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
}
|
||||
|
||||
public void showOrHideDiagnosticWindow() {
|
||||
if(mDiagnostics == null) return;
|
||||
boolean hide = true;
|
||||
if(mDiagnostics == null || mLanguageClient == null) return;
|
||||
Diagnostic d = findDiagnosticInCursorRange();
|
||||
if(d != null) {
|
||||
hide = false;
|
||||
mDiagnosticWindow.setDiagnostic(d);
|
||||
if(!mDiagnosticWindow.isShowing())
|
||||
mDiagnosticWindow.show();
|
||||
else updateDiagnosticWindowPosition();
|
||||
}
|
||||
if(hide && mDiagnosticWindow.isShowing()) {
|
||||
mDiagnosticWindow.hide();
|
||||
mLanguageClient.showDiagnostic(d, this);
|
||||
} else {
|
||||
mLanguageClient.hideDiagnostics();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -830,6 +812,7 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
}
|
||||
this.mLanguage = lang;
|
||||
this.mLanguageServer = mLanguage.getLanguageServer();
|
||||
this.mLanguageClient = LSPProvider.getClientForLanguage(mLanguage.getLanguageCode());
|
||||
|
||||
// Update spanner
|
||||
if (mSpanner != null) {
|
||||
@@ -2580,37 +2563,6 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
mTextActionPresenter.onExit();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateDiagnosticWindowPosition() {
|
||||
float panelX = updateCursorAnchor() + mDpUnit * 20;
|
||||
float[] rightLayoutOffset = mLayout.getCharLayoutOffset(mCursor.getRightLine(), mCursor.getRightColumn());
|
||||
float panelY = rightLayoutOffset[0] - getOffsetY() - getRowHeight();
|
||||
float restY = getHeight() - panelY;
|
||||
if (restY > mDpUnit * 120) {
|
||||
restY = mDpUnit * 120;
|
||||
} else if (restY < mDpUnit * 100) {
|
||||
float offset = 0;
|
||||
while (restY < mDpUnit * 100) {
|
||||
restY += getRowHeight();
|
||||
panelY -= getRowHeight();
|
||||
offset += getRowHeight();
|
||||
}
|
||||
getScroller().startScroll(getOffsetX(), getOffsetY(), 0, (int) offset, 0);
|
||||
}
|
||||
mDiagnosticWindow.setExtendedX(panelX);
|
||||
mDiagnosticWindow.setExtendedY(panelY);
|
||||
if (getWidth() < 500 * mDpUnit) {
|
||||
mDiagnosticWindow.setWidth(getWidth() * 7 / 8);
|
||||
mDiagnosticWindow.setExtendedX(getWidth() / 8f / 2f);
|
||||
} else {
|
||||
int w = getWidth() / 3;
|
||||
mDiagnosticWindow.setWidth(w * 2);
|
||||
}
|
||||
|
||||
mDiagnosticWindow.setHeight((int) restY);
|
||||
mDiagnosticWindow.setExtendedY(panelY - restY);
|
||||
mDiagnosticWindow.updatePosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply new position of auto completion window
|
||||
@@ -3762,9 +3714,8 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
showOrHideDiagnosticWindow();
|
||||
|
||||
if(mLanguage != null) {
|
||||
AbstractLanguageClient client = LSPProvider.getClientForLanguage(mLanguage.getLanguageCode());
|
||||
if(client != null) {
|
||||
client.hideSignatureHelp();
|
||||
if(mLanguageClient != null) {
|
||||
mLanguageClient.hideSignatureHelp();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4091,8 +4042,8 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
}
|
||||
|
||||
public void hideDiagnosticWindow() {
|
||||
if(mDiagnosticWindow != null) {
|
||||
mDiagnosticWindow.hide();
|
||||
if(mLanguageClient != null) {
|
||||
mLanguageClient.hideDiagnostics();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4708,9 +4659,8 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
future.whenComplete((e, t) -> {
|
||||
final Either<List<? extends Location>, List<? extends LocationLink>> either = e;
|
||||
final Throwable th = t;
|
||||
|
||||
final AbstractLanguageClient client = LSPProvider.getClientForLanguage(mLanguage.getLanguageCode());
|
||||
if(either == null || client == null || future.isCancelled() || future.isCompletedExceptionally()) {
|
||||
|
||||
if(either == null || mLanguageClient == null || future.isCancelled() || future.isCompletedExceptionally()) {
|
||||
showDefinitionNotFound(pd);
|
||||
return;
|
||||
}
|
||||
@@ -4725,16 +4675,16 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
ThreadUtils.runOnUiThread(() -> {
|
||||
if(locations.size() == 1) {
|
||||
Location location = locations.get(0);
|
||||
showDocument(client, location.getUri(), location.getRange());
|
||||
showDocument(mLanguageClient, location.getUri(), location.getRange());
|
||||
} else {
|
||||
client.showLocations(locations);
|
||||
mLanguageClient.showLocations(locations);
|
||||
}
|
||||
});
|
||||
|
||||
dismissOnUiThread(pd);
|
||||
} else if(either.isRight()) {
|
||||
final List<? extends LocationLink> locations = either.getRight();
|
||||
if(locations == null || locations.size() <= 0) {
|
||||
if(mLanguageClient == null || locations == null || locations.size() <= 0) {
|
||||
showDefinitionNotFound(pd);
|
||||
return;
|
||||
}
|
||||
@@ -4742,9 +4692,9 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
ThreadUtils.runOnUiThread(() -> {
|
||||
if(locations.size() == 1) {
|
||||
LocationLink location = locations.get(0);
|
||||
showDocument(client, location.getTargetUri(), location.getTargetSelectionRange());
|
||||
showDocument(mLanguageClient, location.getTargetUri(), location.getTargetSelectionRange());
|
||||
} else {
|
||||
client.showLocationLinks(locations);
|
||||
mLanguageClient.showLocationLinks(locations);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4780,9 +4730,7 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
final List<? extends Location> locations = l;
|
||||
final Throwable th = t;
|
||||
|
||||
final AbstractLanguageClient client = LSPProvider.getClientForLanguage(mLanguage.getLanguageCode());
|
||||
|
||||
if(locations == null || client == null || future.isCancelled() || future.isCompletedExceptionally()) {
|
||||
if(locations == null || mLanguageClient == null || future.isCancelled() || future.isCompletedExceptionally()) {
|
||||
showReferencesNotFound(pd);
|
||||
return;
|
||||
}
|
||||
@@ -4792,7 +4740,7 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
return;
|
||||
} else {
|
||||
ThreadUtils.runOnUiThread(() -> {
|
||||
client.showLocations(locations);
|
||||
mLanguageClient.showLocations(locations);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4832,14 +4780,13 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
future.whenComplete((h, t) -> {
|
||||
final SignatureHelp help = h;
|
||||
final Throwable th = t;
|
||||
final AbstractLanguageClient client = LSPProvider.getClientForLanguage(mLanguage.getLanguageCode());
|
||||
|
||||
if(help == null || client == null || future.isCancelled() || future.isCompletedExceptionally()) {
|
||||
if(help == null || mLanguageClient == null || future.isCancelled() || future.isCompletedExceptionally()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadUtils.runOnUiThread(() -> {
|
||||
client.showSignatureHelp(help, getFile());
|
||||
mLanguageClient.showSignatureHelp(help, getFile());
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -4912,8 +4859,8 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
public void afterInsert(Content content, int startLine, int startColumn, int endLine, int endColumn, CharSequence insertedContent) {
|
||||
notifyChanged();
|
||||
|
||||
if(mDiagnosticWindow != null)
|
||||
mDiagnosticWindow.hide();
|
||||
if(mLanguageClient != null)
|
||||
mLanguageClient.hideDiagnostics();
|
||||
|
||||
// Update spans
|
||||
if (isSpanMapPrepared(true, endLine - startLine)) {
|
||||
@@ -4981,8 +4928,8 @@ public class CodeEditor extends View implements ContentListener, TextAnalyzer.Ca
|
||||
public void afterDelete(Content content, int startLine, int startColumn, int endLine, int endColumn, CharSequence deletedContent) {
|
||||
notifyChanged();
|
||||
|
||||
if(mDiagnosticWindow != null)
|
||||
mDiagnosticWindow.hide();
|
||||
if(mLanguageClient != null)
|
||||
mLanguageClient.hideDiagnostics();
|
||||
|
||||
if (isSpanMapPrepared(false, endLine - startLine)) {
|
||||
if (startLine == endLine) {
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
package io.github.rosemoe.editor.widget;
|
||||
|
||||
import android.graphics.PorterDuff;
|
||||
import android.view.LayoutInflater;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import com.itsaky.androidide.R;
|
||||
import com.itsaky.androidide.databinding.LayoutDiagnosticInfoBinding;
|
||||
import org.eclipse.lsp4j.Diagnostic;
|
||||
import org.eclipse.lsp4j.DiagnosticSeverity;
|
||||
|
||||
public class DiagnosticWindow extends EditorBasePopupWindow {
|
||||
|
||||
private CodeEditor editor;
|
||||
private Diagnostic diagnostic;
|
||||
private LayoutDiagnosticInfoBinding binding;
|
||||
|
||||
public DiagnosticWindow(CodeEditor editor) {
|
||||
super(editor);
|
||||
this.editor = editor;
|
||||
this.binding = LayoutDiagnosticInfoBinding.inflate(LayoutInflater.from(editor.getContext()));
|
||||
|
||||
setContentView(this.binding.getRoot());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
super.show();
|
||||
setupData();
|
||||
}
|
||||
|
||||
public void setMaxHeight(int height) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePosition() {
|
||||
super.updatePosition();
|
||||
setupData();
|
||||
}
|
||||
|
||||
private void setupData() {
|
||||
if(diagnostic != null) {
|
||||
binding.icon.setImageResource(getDiagnosticIconId());
|
||||
binding.icon.setColorFilter(ContextCompat.getColor(binding.icon.getContext(), R.color.primaryIconColor), PorterDuff.Mode.SRC_ATOP);
|
||||
binding.errorType.setText(getDiagnosticTypeString());
|
||||
binding.errorPosition.setText(
|
||||
editor.getContext().getString(
|
||||
com.itsaky.androidide.R.string.diagnostic_position,
|
||||
""+ (diagnostic.getRange().getStart().getLine() + 1),
|
||||
""+ (diagnostic.getRange().getStart().getCharacter() + 1),
|
||||
""+ (diagnostic.getRange().getEnd().getLine() + 1),
|
||||
""+ (diagnostic.getRange().getEnd().getCharacter() + 1)
|
||||
)
|
||||
);
|
||||
|
||||
binding.msg.setText(diagnostic.getMessage());
|
||||
binding.msg.setTextIsSelectable(true);
|
||||
}
|
||||
}
|
||||
|
||||
public DiagnosticWindow setDiagnostic(Diagnostic diag) {
|
||||
this.diagnostic = diag;
|
||||
return this;
|
||||
}
|
||||
|
||||
private int getDiagnosticIconId() {
|
||||
if(diagnostic.getSeverity() == DiagnosticSeverity.Error)
|
||||
return R.drawable.ic_compilation_error;
|
||||
return R.drawable.ic_info;
|
||||
}
|
||||
|
||||
private String getDiagnosticTypeString() {
|
||||
if(diagnostic.getSeverity() == DiagnosticSeverity.Error) {
|
||||
return editor.getContext().getString(com.itsaky.androidide.R.string.diagnostic_error);
|
||||
} else if(diagnostic.getSeverity() == DiagnosticSeverity.Warning) {
|
||||
return editor.getContext().getString(com.itsaky.androidide.R.string.diagnostic_warning);
|
||||
} else if(diagnostic.getSeverity() == DiagnosticSeverity.Hint) {
|
||||
return editor.getContext().getString(com.itsaky.androidide.R.string.diagnostic_hint);
|
||||
}
|
||||
return editor.getContext().getString(com.itsaky.androidide.R.string.diagnostic_info);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -200,7 +200,15 @@ public class EditorAutoCompleteWindow extends EditorBasePopupWindow {
|
||||
range.getEnd().getLine(),
|
||||
range.getEnd().getCharacter()
|
||||
);
|
||||
mEditor.getCursor().onCommitText(text);
|
||||
if(!text.contains("\n")) {
|
||||
mEditor.getCursor().onCommitText(text);
|
||||
} else {
|
||||
final String[] lines = text.split("\n");
|
||||
mEditor.getCursor().onCommitText(lines[0]);
|
||||
for(int i=1;i<lines.length;i++) {
|
||||
mEditor.getCursor().onCommitText("\n" + lines[i].trim());
|
||||
}
|
||||
}
|
||||
|
||||
if(shiftCursor) {
|
||||
final int line = mEditor.getCursor().getLeftLine();
|
||||
|
||||
@@ -189,8 +189,6 @@ class EditorInputConnection extends BaseInputConnection {
|
||||
|
||||
@Override
|
||||
public boolean commitText(CharSequence text, int newCursorPosition) {
|
||||
//Logs.log("Commit text: text = " + text + ", newCursorPosition = " + newCursorPosition);
|
||||
//Log.d(LOG_TAG, "commit text:text = " + text + ", newCur = " + newCursorPosition);
|
||||
if (!mEditor.isEditable() || mInvalid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -369,6 +369,10 @@
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="4dp"
|
||||
tools:ignore="MissingConstraints"/>
|
||||
|
||||
<include
|
||||
android:id="@+id/diagnosticInfo"
|
||||
layout="@layout/layout_diagnostic_info"/>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab_view"
|
||||
|
||||
@@ -1,69 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.itsaky.androidide.views.TooltipLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:color/transparent"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/secondaryColor"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingBottom="5dp"
|
||||
android:gravity="center">
|
||||
|
||||
<ImageView
|
||||
android:layout_height="30dp"
|
||||
android:layout_width="30dp"
|
||||
android:scaleType="center"
|
||||
app:srcCompat="@drawable/ic_compilation_error"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:id="@+id/icon"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/errorType"
|
||||
android:textColor="@color/primaryTextColor"
|
||||
android:textAllCaps="true"
|
||||
android:textStyle="bold"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:singleLine="true"
|
||||
android:textSize="13sp"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_toEndOf="@id/icon"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/errorPosition"
|
||||
android:textColor="@color/secondaryTextColor_lighter"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:maxLines="2"
|
||||
android:textSize="10sp"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_below="@id/errorType"
|
||||
android:layout_toEndOf="@id/icon"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/msg"
|
||||
android:padding="8dp"
|
||||
android:textColor="@color/primaryTextColor"
|
||||
android:textSize="11sp"
|
||||
android:textIsSelectable="true"/>
|
||||
|
||||
</com.itsaky.androidide.views.TooltipLayout>
|
||||
<TextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/msg"
|
||||
android:padding="8dp"
|
||||
android:textColor="@color/primaryTextColor"
|
||||
android:textSize="11sp"
|
||||
android:textIsSelectable="true"/>
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
package com.itsaky.terminal;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
public class ByteQueueTest extends TestCase {
|
||||
|
||||
private static void assertArrayEquals(byte[] expected, byte[] actual) {
|
||||
if (expected.length != actual.length) {
|
||||
fail("Difference array length");
|
||||
}
|
||||
for (int i = 0; i < expected.length; i++) {
|
||||
if (expected[i] != actual[i]) {
|
||||
fail("Inequals at index=" + i + ", expected=" + (int) expected[i] + ", actual=" + (int) actual[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testCompleteWrites() throws Exception {
|
||||
ByteQueue q = new ByteQueue(10);
|
||||
assertTrue(q.write(new byte[]{1, 2, 3}, 0, 3));
|
||||
|
||||
byte[] arr = new byte[10];
|
||||
assertEquals(3, q.read(arr, true));
|
||||
assertArrayEquals(new byte[]{1, 2, 3}, new byte[]{arr[0], arr[1], arr[2]});
|
||||
|
||||
assertTrue(q.write(new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, 0, 10));
|
||||
assertEquals(10, q.read(arr, true));
|
||||
assertArrayEquals(new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, arr);
|
||||
}
|
||||
|
||||
public void testQueueWraparound() throws Exception {
|
||||
ByteQueue q = new ByteQueue(10);
|
||||
|
||||
byte[] origArray = new byte[]{1, 2, 3, 4, 5, 6};
|
||||
byte[] readArray = new byte[origArray.length];
|
||||
for (int i = 0; i < 20; i++) {
|
||||
q.write(origArray, 0, origArray.length);
|
||||
assertEquals(origArray.length, q.read(readArray, true));
|
||||
assertArrayEquals(origArray, readArray);
|
||||
}
|
||||
}
|
||||
|
||||
public void testWriteNotesClosing() throws Exception {
|
||||
ByteQueue q = new ByteQueue(10);
|
||||
q.close();
|
||||
assertFalse(q.write(new byte[]{1, 2, 3}, 0, 3));
|
||||
}
|
||||
|
||||
public void testReadNonBlocking() throws Exception {
|
||||
ByteQueue q = new ByteQueue(10);
|
||||
assertEquals(0, q.read(new byte[128], false));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package com.itsaky.terminal;
|
||||
|
||||
/** "\033[" is the Control Sequence Introducer char sequence (CSI). */
|
||||
public class ControlSequenceIntroducerTest extends TerminalTestCase {
|
||||
|
||||
/** CSI Ps P Scroll down Ps lines (default = 1) (SD). */
|
||||
public void testCsiT() {
|
||||
withTerminalSized(4, 6).enterString("1\r\n2\r\n3\r\nhi\033[2Tyo\r\nA\r\nB").assertLinesAre(" ", " ", "1 ", "2 yo", "A ",
|
||||
"Bi ");
|
||||
// Default value (1):
|
||||
withTerminalSized(4, 6).enterString("1\r\n2\r\n3\r\nhi\033[Tyo\r\nA\r\nB").assertLinesAre(" ", "1 ", "2 ", "3 yo", "Ai ",
|
||||
"B ");
|
||||
}
|
||||
|
||||
/** CSI Ps S Scroll up Ps lines (default = 1) (SU). */
|
||||
public void testCsiS() {
|
||||
// The behaviour here is a bit inconsistent between terminals - this is how the OS X Terminal.app does it:
|
||||
withTerminalSized(3, 4).enterString("1\r\n2\r\n3\r\nhi\033[2Sy").assertLinesAre("3 ", "hi ", " ", " y");
|
||||
// Default value (1):
|
||||
withTerminalSized(3, 4).enterString("1\r\n2\r\n3\r\nhi\033[Sy").assertLinesAre("2 ", "3 ", "hi ", " y");
|
||||
}
|
||||
|
||||
/** CSI Ps X Erase Ps Character(s) (default = 1) (ECH). */
|
||||
public void testCsiX() {
|
||||
// See https://code.google.com/p/chromium/issues/detail?id=212712 where test was extraced from.
|
||||
withTerminalSized(13, 2).enterString("abcdefghijkl\b\b\b\b\b\033[X").assertLinesAre("abcdefg ijkl ", " ");
|
||||
withTerminalSized(13, 2).enterString("abcdefghijkl\b\b\b\b\b\033[1X").assertLinesAre("abcdefg ijkl ", " ");
|
||||
withTerminalSized(13, 2).enterString("abcdefghijkl\b\b\b\b\b\033[2X").assertLinesAre("abcdefg jkl ", " ");
|
||||
withTerminalSized(13, 2).enterString("abcdefghijkl\b\b\b\b\b\033[20X").assertLinesAre("abcdefg ", " ");
|
||||
}
|
||||
|
||||
/** CSI Pm m Set SGR parameter(s) from semicolon-separated list Pm. */
|
||||
public void testCsiSGRParameters() {
|
||||
// Set more parameters (19) than supported (16). Additional parameters should be silently consumed.
|
||||
withTerminalSized(3, 2).enterString("\033[0;38;2;255;255;255;48;2;0;0;0;1;2;3;4;5;7;8;9mabc").assertLinesAre("abc", " ");
|
||||
}
|
||||
|
||||
/** CSI Ps b Repeat the preceding graphic character Ps times (REP). */
|
||||
public void testRepeat() {
|
||||
withTerminalSized(3, 2).enterString("a\033[b").assertLinesAre("aa ", " ");
|
||||
withTerminalSized(3, 2).enterString("a\033[2b").assertLinesAre("aaa", " ");
|
||||
// When no char has been output we ignore REP:
|
||||
withTerminalSized(3, 2).enterString("\033[b").assertLinesAre(" ", " ");
|
||||
// This shows that REP outputs the last emitted code point and not the one relative to the
|
||||
// current cursor position:
|
||||
withTerminalSized(5, 2).enterString("abcde\033[2G\033[2b\n").assertLinesAre("aeede", " ");
|
||||
}
|
||||
|
||||
/** CSI 3 J Clear scrollback (xterm, libvte; non-standard). */
|
||||
public void testCsi3J() {
|
||||
withTerminalSized(3, 2).enterString("a\r\nb\r\nc\r\nd");
|
||||
assertEquals("a\nb\nc\nd", mTerminal.getScreen().getTranscriptText());
|
||||
enterString("\033[3J");
|
||||
assertEquals("c\nd", mTerminal.getScreen().getTranscriptText());
|
||||
|
||||
withTerminalSized(3, 2).enterString("Lorem_ipsum");
|
||||
assertEquals("Lorem_ipsum", mTerminal.getScreen().getTranscriptText());
|
||||
enterString("\033[3J");
|
||||
assertEquals("ipsum", mTerminal.getScreen().getTranscriptText());
|
||||
|
||||
withTerminalSized(3, 2).enterString("w\r\nx\r\ny\r\nz\033[?1049h\033[3J\033[?1049l");
|
||||
assertEquals("y\nz", mTerminal.getScreen().getTranscriptText());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,266 +0,0 @@
|
||||
package com.itsaky.terminal;
|
||||
|
||||
import org.junit.Assert;
|
||||
|
||||
public class CursorAndScreenTest extends TerminalTestCase {
|
||||
|
||||
public void testDeleteLinesKeepsStyles() {
|
||||
int cols = 5, rows = 5;
|
||||
withTerminalSized(cols, rows);
|
||||
for (int row = 0; row < 5; row++) {
|
||||
for (int col = 0; col < 5; col++) {
|
||||
// Foreground color to col, background to row:
|
||||
enterString("\033[38;5;" + col + "m");
|
||||
enterString("\033[48;5;" + row + "m");
|
||||
enterString(Character.toString((char) ('A' + col + row * 5)));
|
||||
}
|
||||
}
|
||||
assertLinesAre("ABCDE", "FGHIJ", "KLMNO", "PQRST", "UVWXY");
|
||||
for (int row = 0; row < 5; row++) {
|
||||
for (int col = 0; col < 5; col++) {
|
||||
long s = getStyleAt(row, col);
|
||||
Assert.assertEquals(col, TextStyle.decodeForeColor(s));
|
||||
Assert.assertEquals(row, TextStyle.decodeBackColor(s));
|
||||
}
|
||||
}
|
||||
// "${CSI}H" - place cursor at 1,1, then "${CSI}2M" to delete two lines.
|
||||
enterString("\033[H\033[2M");
|
||||
assertLinesAre("KLMNO", "PQRST", "UVWXY", " ", " ");
|
||||
for (int row = 0; row < 3; row++) {
|
||||
for (int col = 0; col < 5; col++) {
|
||||
long s = getStyleAt(row, col);
|
||||
Assert.assertEquals(col, TextStyle.decodeForeColor(s));
|
||||
Assert.assertEquals(row + 2, TextStyle.decodeBackColor(s));
|
||||
}
|
||||
}
|
||||
// Set default fg and background for the new blank lines:
|
||||
enterString("\033[38;5;98m");
|
||||
enterString("\033[48;5;99m");
|
||||
// "${CSI}B" to go down one line, then "${CSI}2L" to insert two lines:
|
||||
enterString("\033[B\033[2L");
|
||||
assertLinesAre("KLMNO", " ", " ", "PQRST", "UVWXY");
|
||||
for (int row = 0; row < 5; row++) {
|
||||
for (int col = 0; col < 5; col++) {
|
||||
int wantedForeground = (row == 1 || row == 2) ? 98 : col;
|
||||
int wantedBackground = (row == 1 || row == 2) ? 99 : (row == 0 ? 2 : row);
|
||||
long s = getStyleAt(row, col);
|
||||
Assert.assertEquals(wantedForeground, TextStyle.decodeForeColor(s));
|
||||
Assert.assertEquals(wantedBackground, TextStyle.decodeBackColor(s));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testDeleteCharacters() {
|
||||
withTerminalSized(5, 2).enterString("枝ce").assertLinesAre("枝ce ", " ");
|
||||
withTerminalSized(5, 2).enterString("a枝ce").assertLinesAre("a枝ce", " ");
|
||||
withTerminalSized(5, 2).enterString("nice").enterString("\033[G\033[P").assertLinesAre("ice ", " ");
|
||||
withTerminalSized(5, 2).enterString("nice").enterString("\033[G\033[2P").assertLinesAre("ce ", " ");
|
||||
withTerminalSized(5, 2).enterString("nice").enterString("\033[2G\033[2P").assertLinesAre("ne ", " ");
|
||||
// "${CSI}${n}P, the delete characters (DCH) sequence should cap characters to delete.
|
||||
withTerminalSized(5, 2).enterString("nice").enterString("\033[G\033[99P").assertLinesAre(" ", " ");
|
||||
// With combining char U+0302.
|
||||
withTerminalSized(5, 2).enterString("n\u0302ice").enterString("\033[G\033[2P").assertLinesAre("ce ", " ");
|
||||
withTerminalSized(5, 2).enterString("n\u0302ice").enterString("\033[G\033[P").assertLinesAre("ice ", " ");
|
||||
withTerminalSized(5, 2).enterString("n\u0302ice").enterString("\033[2G\033[2P").assertLinesAre("n\u0302e ", " ");
|
||||
// With wide 枝 char, checking that putting char at part replaces other with whitespace:
|
||||
withTerminalSized(5, 2).enterString("枝ce").enterString("\033[Ga").assertLinesAre("a ce ", " ");
|
||||
withTerminalSized(5, 2).enterString("枝ce").enterString("\033[2Ga").assertLinesAre(" ace ", " ");
|
||||
// With wide 枝 char, deleting either part replaces other with whitespace:
|
||||
withTerminalSized(5, 2).enterString("枝ce").enterString("\033[G\033[P").assertLinesAre(" ce ", " ");
|
||||
withTerminalSized(5, 2).enterString("枝ce").enterString("\033[2G\033[P").assertLinesAre(" ce ", " ");
|
||||
withTerminalSized(5, 2).enterString("枝ce").enterString("\033[2G\033[2P").assertLinesAre(" e ", " ");
|
||||
withTerminalSized(5, 2).enterString("枝ce").enterString("\033[G\033[2P").assertLinesAre("ce ", " ");
|
||||
withTerminalSized(5, 2).enterString("a枝ce").enterString("\033[G\033[P").assertLinesAre("枝ce ", " ");
|
||||
}
|
||||
|
||||
public void testInsertMode() {
|
||||
// "${CSI}4h" enables insert mode.
|
||||
withTerminalSized(5, 2).enterString("nice").enterString("\033[G\033[4hA").assertLinesAre("Anice", " ");
|
||||
withTerminalSized(5, 2).enterString("nice").enterString("\033[2G\033[4hA").assertLinesAre("nAice", " ");
|
||||
withTerminalSized(5, 2).enterString("nice").enterString("\033[G\033[4hABC").assertLinesAre("ABCni", " ");
|
||||
// With combining char U+0302.
|
||||
withTerminalSized(5, 2).enterString("n\u0302ice").enterString("\033[G\033[4hA").assertLinesAre("An\u0302ice", " ");
|
||||
withTerminalSized(5, 2).enterString("n\u0302ice").enterString("\033[G\033[4hAB").assertLinesAre("ABn\u0302ic", " ");
|
||||
withTerminalSized(5, 2).enterString("n\u0302ic\u0302e").enterString("\033[2G\033[4hA").assertLinesAre("n\u0302Aic\u0302e", " ");
|
||||
// ... but without insert mode, combining char should be overwritten:
|
||||
withTerminalSized(5, 2).enterString("n\u0302ice").enterString("\033[GA").assertLinesAre("Aice ", " ");
|
||||
// ... also with two combining:
|
||||
withTerminalSized(5, 2).enterString("n\u0302\u0302i\u0302ce").enterString("\033[GA").assertLinesAre("Ai\u0302ce ", " ");
|
||||
// ... and in last column:
|
||||
withTerminalSized(5, 2).enterString("n\u0302\u0302ice!\u0302").enterString("\033[5GA").assertLinesAre("n\u0302\u0302iceA", " ");
|
||||
withTerminalSized(5, 2).enterString("nic\u0302e!\u0302").enterString("\033[4G枝").assertLinesAre("nic\u0302枝", " ");
|
||||
withTerminalSized(5, 2).enterString("nic枝\u0302").enterString("\033[3GA").assertLinesAre("niA枝\u0302", " ");
|
||||
withTerminalSized(5, 2).enterString("nic枝\u0302").enterString("\033[3GA").assertLinesAre("niA枝\u0302", " ");
|
||||
// With wide 枝 char.
|
||||
withTerminalSized(5, 2).enterString("nice").enterString("\033[G\033[4h枝").assertLinesAre("枝nic", " ");
|
||||
withTerminalSized(5, 2).enterString("nice").enterString("\033[2G\033[4h枝").assertLinesAre("n枝ic", " ");
|
||||
withTerminalSized(5, 2).enterString("n枝ce").enterString("\033[G\033[4ha").assertLinesAre("an枝c", " ");
|
||||
}
|
||||
|
||||
/** HPA—Horizontal Position Absolute (http://www.vt100.net/docs/vt510-rm/HPA) */
|
||||
public void testCursorHorizontalPositionAbsolute() {
|
||||
withTerminalSized(4, 4).enterString("ABC\033[`").assertCursorAt(0, 0);
|
||||
enterString("\033[1`").assertCursorAt(0, 0).enterString("\033[2`").assertCursorAt(0, 1);
|
||||
enterString("\r\n\033[3`").assertCursorAt(1, 2).enterString("\033[22`").assertCursorAt(1, 3);
|
||||
// Enable and configure right and left margins, first without origin mode:
|
||||
enterString("\033[?69h\033[2;3s\033[`").assertCursorAt(0, 0).enterString("\033[22`").assertCursorAt(0, 3);
|
||||
// .. now with origin mode:
|
||||
enterString("\033[?6h\033[`").assertCursorAt(0, 1).enterString("\033[22`").assertCursorAt(0, 2);
|
||||
}
|
||||
|
||||
public void testCursorForward() {
|
||||
// "${CSI}${N:=1}C" moves cursor forward N columns:
|
||||
withTerminalSized(6, 2).enterString("A\033[CB\033[2CC").assertLinesAre("A B C", " ");
|
||||
// If an attempt is made to move the cursor to the right of the right margin, the cursor stops at the right margin:
|
||||
withTerminalSized(6, 2).enterString("A\033[44CB").assertLinesAre("A B", " ");
|
||||
// Enable right margin and verify that CUF ends at the set right margin:
|
||||
withTerminalSized(6, 2).enterString("\033[?69h\033[1;3s\033[44CAB").assertLinesAre(" A ", "B ");
|
||||
}
|
||||
|
||||
public void testCursorBack() {
|
||||
// "${CSI}${N:=1}D" moves cursor back N columns:
|
||||
withTerminalSized(3, 2).enterString("A\033[DB").assertLinesAre("B ", " ");
|
||||
withTerminalSized(3, 2).enterString("AB\033[2DC").assertLinesAre("CB ", " ");
|
||||
// If an attempt is made to move the cursor to the left of the left margin, the cursor stops at the left margin:
|
||||
withTerminalSized(3, 2).enterString("AB\033[44DC").assertLinesAre("CB ", " ");
|
||||
// Enable left margin and verify that CUB ends at the set left margin:
|
||||
withTerminalSized(6, 2).enterString("ABCD\033[?69h\033[2;6s\033[44DE").assertLinesAre("AECD ", " ");
|
||||
}
|
||||
|
||||
public void testCursorUp() {
|
||||
// "${CSI}${N:=1}A" moves cursor up N rows:
|
||||
withTerminalSized(3, 3).enterString("ABCDEFG\033[AH").assertLinesAre("ABC", "DHF", "G ");
|
||||
withTerminalSized(3, 3).enterString("ABCDEFG\033[2AH").assertLinesAre("AHC", "DEF", "G ");
|
||||
// If an attempt is made to move the cursor above the top margin, the cursor stops at the top margin:
|
||||
withTerminalSized(3, 3).enterString("ABCDEFG\033[44AH").assertLinesAre("AHC", "DEF", "G ");
|
||||
}
|
||||
|
||||
public void testCursorDown() {
|
||||
// "${CSI}${N:=1}B" moves cursor down N rows:
|
||||
withTerminalSized(3, 3).enterString("AB\033[BC").assertLinesAre("AB ", " C", " ");
|
||||
withTerminalSized(3, 3).enterString("AB\033[2BC").assertLinesAre("AB ", " ", " C");
|
||||
// If an attempt is made to move the cursor below the bottom margin, the cursor stops at the bottom margin:
|
||||
withTerminalSized(3, 3).enterString("AB\033[44BC").assertLinesAre("AB ", " ", " C");
|
||||
}
|
||||
|
||||
public void testReportCursorPosition() {
|
||||
withTerminalSized(10, 10);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
enterString("\033[" + (i + 1) + ";" + (j + 1) + "H"); // CUP cursor position.
|
||||
assertCursorAt(i, j);
|
||||
// Device Status Report (DSR):
|
||||
assertEnteringStringGivesResponse("\033[6n", "\033[" + (i + 1) + ";" + (j + 1) + "R");
|
||||
// DECXCPR — Extended Cursor Position. Note that http://www.vt100.net/docs/vt510-rm/DECXCPR says
|
||||
// the response is "${CSI}${LINE};${COLUMN};${PAGE}R" while xterm (http://invisible-island.net/xterm/ctlseqs/ctlseqs.html)
|
||||
// drops the question mark. Expect xterm behaviour here.
|
||||
assertEnteringStringGivesResponse("\033[?6n", "\033[?" + (i + 1) + ";" + (j + 1) + ";1R");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See comments on horizontal tab handling in TerminalEmulator.java.
|
||||
* <p/>
|
||||
* We do not want to color already written cells when tabbing over them.
|
||||
*/
|
||||
public void DISABLED_testHorizontalTabColorsBackground() {
|
||||
withTerminalSized(10, 3).enterString("\033[48;5;15m").enterString("\t");
|
||||
assertCursorAt(0, 8);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
int expectedColor = i < 8 ? 15 : TextStyle.COLOR_INDEX_BACKGROUND;
|
||||
assertEquals(expectedColor, TextStyle.decodeBackColor(getStyleAt(0, i)));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test interactions between the cursor overflow bit and various escape sequences.
|
||||
* <p/>
|
||||
* Adapted from hterm:
|
||||
* https://chromium.googlesource.com/chromiumos/platform/assets/+/2337afa5c063127d5ce40ec7fec9b602d096df86%5E%21/#F2
|
||||
*/
|
||||
public void testClearingOfAutowrap() {
|
||||
// Fill a row with the last hyphen wrong, then run a command that
|
||||
// modifies the screen, then add a hyphen. The wrap bit should be
|
||||
// cleared, so the extra hyphen can fix the row.
|
||||
withTerminalSized(15, 6);
|
||||
|
||||
enterString("----- 1 ----X");
|
||||
enterString("\033[K-"); // EL
|
||||
|
||||
enterString("----- 2 ----X");
|
||||
enterString("\033[J-"); // ED
|
||||
|
||||
enterString("----- 3 ----X");
|
||||
enterString("\033[@-"); // ICH
|
||||
|
||||
enterString("----- 4 ----X");
|
||||
enterString("\033[P-"); // DCH
|
||||
|
||||
enterString("----- 5 ----X");
|
||||
enterString("\033[X-"); // ECH
|
||||
|
||||
// DL will delete the entire line but clear the wrap bit, so we
|
||||
// expect a hyphen at the end and nothing else.
|
||||
enterString("XXXXXXXXXXXXXXX");
|
||||
enterString("\033[M-"); // DL
|
||||
|
||||
assertLinesAre(
|
||||
"----- 1 -----",
|
||||
"----- 2 -----",
|
||||
"----- 3 -----",
|
||||
"----- 4 -----",
|
||||
"----- 5 -----",
|
||||
" -");
|
||||
}
|
||||
|
||||
public void testBackspaceAcrossWrappedLines() {
|
||||
// Backspace should not go to previous line if not auto-wrapped:
|
||||
withTerminalSized(3, 3).enterString("hi\r\n\b\byou").assertLinesAre("hi ", "you", " ");
|
||||
// Backspace should go to previous line if auto-wrapped:
|
||||
withTerminalSized(3, 3).enterString("hi y").assertLinesAre("hi ", "y ", " ").enterString("\b\b#").assertLinesAre("hi#", "y ", " ");
|
||||
// Initial backspace should do nothing:
|
||||
withTerminalSized(3, 3).enterString("\b\b\b\bhi").assertLinesAre("hi ", " ", " ");
|
||||
}
|
||||
|
||||
public void testCursorSaveRestoreLocation() {
|
||||
// DEC save/restore
|
||||
withTerminalSized(4, 2).enterString("t\0337est\r\nme\0338ry ").assertLinesAre("try ", "me ");
|
||||
// ANSI.SYS save/restore
|
||||
withTerminalSized(4, 2).enterString("t\033[sest\r\nme\033[ury ").assertLinesAre("try ", "me ");
|
||||
// Alternate screen enter/exit
|
||||
withTerminalSized(4, 2).enterString("t\033[?1049h\033[Hest\r\nme").assertLinesAre("est ", "me ").enterString("\033[?1049lry").assertLinesAre("try ", " ");
|
||||
}
|
||||
|
||||
public void testCursorSaveRestoreTextStyle() {
|
||||
long s;
|
||||
|
||||
// DEC save/restore
|
||||
withTerminalSized(4, 2).enterString("\033[31;42;4m..\0337\033[36;47;24m\0338..");
|
||||
s = getStyleAt(0, 3);
|
||||
Assert.assertEquals(1, TextStyle.decodeForeColor(s));
|
||||
Assert.assertEquals(2, TextStyle.decodeBackColor(s));
|
||||
Assert.assertEquals(TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE, TextStyle.decodeEffect(s));
|
||||
|
||||
// ANSI.SYS save/restore
|
||||
withTerminalSized(4, 2).enterString("\033[31;42;4m..\033[s\033[36;47;24m\033[u..");
|
||||
s = getStyleAt(0, 3);
|
||||
Assert.assertEquals(1, TextStyle.decodeForeColor(s));
|
||||
Assert.assertEquals(2, TextStyle.decodeBackColor(s));
|
||||
Assert.assertEquals(TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE, TextStyle.decodeEffect(s));
|
||||
|
||||
// Alternate screen enter/exit
|
||||
withTerminalSized(4, 2);
|
||||
enterString("\033[31;42;4m..\033[?1049h\033[H\033[36;47;24m.");
|
||||
s = getStyleAt(0, 0);
|
||||
Assert.assertEquals(6, TextStyle.decodeForeColor(s));
|
||||
Assert.assertEquals(7, TextStyle.decodeBackColor(s));
|
||||
Assert.assertEquals(0, TextStyle.decodeEffect(s));
|
||||
enterString("\033[?1049l..");
|
||||
s = getStyleAt(0, 3);
|
||||
Assert.assertEquals(1, TextStyle.decodeForeColor(s));
|
||||
Assert.assertEquals(2, TextStyle.decodeBackColor(s));
|
||||
Assert.assertEquals(TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE, TextStyle.decodeEffect(s));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
package com.itsaky.terminal;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* "CSI ? Pm h", DEC Private Mode Set (DECSET)
|
||||
* </pre>
|
||||
* <p/>
|
||||
* and
|
||||
* <p/>
|
||||
* <pre>
|
||||
* "CSI ? Pm l", DEC Private Mode Reset (DECRST)
|
||||
* </pre>
|
||||
* <p/>
|
||||
* controls various aspects of the terminal
|
||||
*/
|
||||
public class DecSetTest extends TerminalTestCase {
|
||||
|
||||
/** DECSET 25, DECTCEM, controls visibility of the cursor. */
|
||||
public void testEnableDisableCursor() {
|
||||
withTerminalSized(3, 3);
|
||||
assertTrue("Initially the cursor should be enabled", mTerminal.isCursorEnabled());
|
||||
enterString("\033[?25l"); // Disable Cursor (DECTCEM).
|
||||
assertFalse(mTerminal.isCursorEnabled());
|
||||
enterString("\033[?25h"); // Enable Cursor (DECTCEM).
|
||||
assertTrue(mTerminal.isCursorEnabled());
|
||||
|
||||
enterString("\033[?25l"); // Disable Cursor (DECTCEM), again.
|
||||
assertFalse(mTerminal.isCursorEnabled());
|
||||
mTerminal.reset();
|
||||
assertTrue("Resetting the terminal should enable the cursor", mTerminal.isCursorEnabled());
|
||||
|
||||
enterString("\033[?25l");
|
||||
assertFalse(mTerminal.isCursorEnabled());
|
||||
enterString("\033c"); // RIS resetting should enabled cursor.
|
||||
assertTrue(mTerminal.isCursorEnabled());
|
||||
}
|
||||
|
||||
/** DECSET 2004, controls bracketed paste mode. */
|
||||
public void testBracketedPasteMode() {
|
||||
withTerminalSized(3, 3);
|
||||
|
||||
mTerminal.paste("a");
|
||||
assertEquals("Pasting 'a' should output 'a' when bracketed paste mode is disabled", "a", mOutput.getOutputAndClear());
|
||||
|
||||
enterString("\033[?2004h"); // Enable bracketed paste mode.
|
||||
mTerminal.paste("a");
|
||||
assertEquals("Pasting when in bracketed paste mode should be bracketed", "\033[200~a\033[201~", mOutput.getOutputAndClear());
|
||||
|
||||
enterString("\033[?2004l"); // Disable bracketed paste mode.
|
||||
mTerminal.paste("a");
|
||||
assertEquals("Pasting 'a' should output 'a' when bracketed paste mode is disabled", "a", mOutput.getOutputAndClear());
|
||||
|
||||
enterString("\033[?2004h"); // Enable bracketed paste mode, again.
|
||||
mTerminal.paste("a");
|
||||
assertEquals("Pasting when in bracketed paste mode again should be bracketed", "\033[200~a\033[201~", mOutput.getOutputAndClear());
|
||||
|
||||
mTerminal.paste("\033ab\033cd\033");
|
||||
assertEquals("Pasting an escape character should not input it", "\033[200~abcd\033[201~", mOutput.getOutputAndClear());
|
||||
mTerminal.paste("\u0081ab\u0081cd\u009F");
|
||||
assertEquals("Pasting C1 control codes should not input it", "\033[200~abcd\033[201~", mOutput.getOutputAndClear());
|
||||
|
||||
mTerminal.reset();
|
||||
mTerminal.paste("a");
|
||||
assertEquals("Terminal reset() should disable bracketed paste mode", "a", mOutput.getOutputAndClear());
|
||||
}
|
||||
|
||||
/** DECSET 7, DECAWM, controls wraparound mode. */
|
||||
public void testWrapAroundMode() {
|
||||
// Default with wraparound:
|
||||
withTerminalSized(3, 3).enterString("abcd").assertLinesAre("abc", "d ", " ");
|
||||
// With wraparound disabled:
|
||||
withTerminalSized(3, 3).enterString("\033[?7labcd").assertLinesAre("abd", " ", " ");
|
||||
enterString("efg").assertLinesAre("abg", " ", " ");
|
||||
// Re-enabling wraparound:
|
||||
enterString("\033[?7hhij").assertLinesAre("abh", "ij ", " ");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package com.itsaky.terminal;
|
||||
|
||||
/**
|
||||
* "\033P" is a device control string.
|
||||
*/
|
||||
public class DeviceControlStringTest extends TerminalTestCase {
|
||||
|
||||
private static String hexEncode(String s) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (int i = 0; i < s.length(); i++)
|
||||
result.append(String.format("%02X", (int) s.charAt(i)));
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
private void assertCapabilityResponse(String cap, String expectedResponse) {
|
||||
String input = "\033P+q" + hexEncode(cap) + "\033\\";
|
||||
assertEnteringStringGivesResponse(input, "\033P1+r" + hexEncode(cap) + "=" + hexEncode(expectedResponse) + "\033\\");
|
||||
}
|
||||
|
||||
public void testReportColorsAndName() {
|
||||
// Request Termcap/Terminfo String. The string following the "q" is a list of names encoded in
|
||||
// hexadecimal (2 digits per character) separated by ; which correspond to termcap or terminfo key
|
||||
// names.
|
||||
// Two special features are also recognized, which are not key names: Co for termcap colors (or colors
|
||||
// for terminfo colors), and TN for termcap name (or name for terminfo name).
|
||||
// xterm responds with DCS 1 + r P t ST for valid requests, adding to P t an = , and the value of the
|
||||
// corresponding string that xterm would send, or DCS 0 + r P t ST for invalid requests. The strings are
|
||||
// encoded in hexadecimal (2 digits per character).
|
||||
withTerminalSized(3, 3).enterString("A");
|
||||
assertCapabilityResponse("Co", "256");
|
||||
assertCapabilityResponse("colors", "256");
|
||||
assertCapabilityResponse("TN", "xterm");
|
||||
assertCapabilityResponse("name", "xterm");
|
||||
enterString("B").assertLinesAre("AB ", " ", " ");
|
||||
}
|
||||
|
||||
public void testReportKeys() {
|
||||
withTerminalSized(3, 3);
|
||||
assertCapabilityResponse("kB", "\033[Z");
|
||||
}
|
||||
|
||||
public void testReallyLongDeviceControlString() {
|
||||
withTerminalSized(3, 3).enterString("\033P");
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
enterString("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
|
||||
}
|
||||
// The terminal should ignore the overlong DCS sequence and continue printing "aaa." and fill at least the first two lines with
|
||||
// them:
|
||||
assertLineIs(0, "aaa");
|
||||
assertLineIs(1, "aaa");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package com.itsaky.terminal;
|
||||
|
||||
|
||||
public class HistoryTest extends TerminalTestCase {
|
||||
|
||||
public void testHistory() {
|
||||
final int rows = 3;
|
||||
final int cols = 3;
|
||||
withTerminalSized(cols, rows).enterString("111222333444555666777888999");
|
||||
assertCursorAt(2, 2);
|
||||
assertLinesAre("777", "888", "999");
|
||||
assertHistoryStartsWith("666", "555");
|
||||
|
||||
mTerminal.resize(cols, 2);
|
||||
assertHistoryStartsWith("777", "666", "555");
|
||||
|
||||
mTerminal.resize(cols, 3);
|
||||
assertHistoryStartsWith("666", "555");
|
||||
}
|
||||
|
||||
public void testHistoryWithScrollRegion() {
|
||||
// "CSI P_s ; P_s r" - set Scrolling Region [top;bottom] (default = full size of window) (DECSTBM).
|
||||
withTerminalSized(3, 4).enterString("111222333444");
|
||||
assertLinesAre("111", "222", "333", "444");
|
||||
enterString("\033[2;3r");
|
||||
// NOTE: "DECSTBM moves the cursor to column 1, line 1 of the page."
|
||||
assertCursorAt(0, 0);
|
||||
enterString("\nCDEFGH").assertLinesAre("111", "CDE", "FGH", "444");
|
||||
enterString("IJK").assertLinesAre("111", "FGH", "IJK", "444").assertHistoryStartsWith("CDE");
|
||||
enterString("LMN").assertLinesAre("111", "IJK", "LMN", "444").assertHistoryStartsWith("FGH", "CDE");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,203 +0,0 @@
|
||||
package com.itsaky.terminal;
|
||||
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
public class KeyHandlerTest extends TestCase {
|
||||
|
||||
private static String stringToHex(String s) {
|
||||
if (s == null) return null;
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
if (buffer.length() > 0) {
|
||||
buffer.append(" ");
|
||||
}
|
||||
buffer.append("0x");
|
||||
buffer.append(Integer.toHexString(s.charAt(i)));
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
private static void assertKeysEquals(String expected, String actual) {
|
||||
if (!expected.equals(actual)) {
|
||||
assertEquals(stringToHex(expected), stringToHex(actual));
|
||||
}
|
||||
}
|
||||
|
||||
/** See http://pubs.opengroup.org/onlinepubs/7990989799/xcurses/terminfo.html */
|
||||
public void testTermCaps() {
|
||||
// Backspace.
|
||||
assertKeysEquals("\u007f", KeyHandler.getCodeFromTermcap("kb", false, false));
|
||||
|
||||
// Back tab.
|
||||
assertKeysEquals("\033[Z", KeyHandler.getCodeFromTermcap("kB", false, false));
|
||||
|
||||
// Arrow keys (up/down/right/left):
|
||||
assertKeysEquals("\033[A", KeyHandler.getCodeFromTermcap("ku", false, false));
|
||||
assertKeysEquals("\033[B", KeyHandler.getCodeFromTermcap("kd", false, false));
|
||||
assertKeysEquals("\033[C", KeyHandler.getCodeFromTermcap("kr", false, false));
|
||||
assertKeysEquals("\033[D", KeyHandler.getCodeFromTermcap("kl", false, false));
|
||||
// .. shifted:
|
||||
assertKeysEquals("\033[1;2A", KeyHandler.getCodeFromTermcap("kUP", false, false));
|
||||
assertKeysEquals("\033[1;2B", KeyHandler.getCodeFromTermcap("kDN", false, false));
|
||||
assertKeysEquals("\033[1;2C", KeyHandler.getCodeFromTermcap("%i", false, false));
|
||||
assertKeysEquals("\033[1;2D", KeyHandler.getCodeFromTermcap("#4", false, false));
|
||||
|
||||
// Home/end keys:
|
||||
assertKeysEquals("\033[H", KeyHandler.getCodeFromTermcap("kh", false, false));
|
||||
assertKeysEquals("\033[F", KeyHandler.getCodeFromTermcap("@7", false, false));
|
||||
// ... shifted:
|
||||
assertKeysEquals("\033[1;2H", KeyHandler.getCodeFromTermcap("#2", false, false));
|
||||
assertKeysEquals("\033[1;2F", KeyHandler.getCodeFromTermcap("*7", false, false));
|
||||
|
||||
// The traditional keyboard keypad:
|
||||
// [Insert] [Home] [Page Up ]
|
||||
// [Delete] [End] [Page Down]
|
||||
//
|
||||
// Termcap names (with xterm response in parenthesis):
|
||||
// K1=Upper left of keypad (xterm sends same "<ESC>[H" = Home).
|
||||
// K2=Center of keypad (xterm sends invalid response).
|
||||
// K3=Upper right of keypad (xterm sends "<ESC>[5~" = Page Up).
|
||||
// K4=Lower left of keypad (xterm sends "<ESC>[F" = End key).
|
||||
// K5=Lower right of keypad (xterm sends "<ESC>[6~" = Page Down).
|
||||
//
|
||||
// vim/neovim (runtime/doc/term.txt):
|
||||
// t_K1 <kHome> keypad home key
|
||||
// t_K3 <kPageUp> keypad page-up key
|
||||
// t_K4 <kEnd> keypad end key
|
||||
// t_K5 <kPageDown> keypad page-down key
|
||||
//
|
||||
assertKeysEquals("\033[H", KeyHandler.getCodeFromTermcap("K1", false, false));
|
||||
assertKeysEquals("\033OH", KeyHandler.getCodeFromTermcap("K1", true, false));
|
||||
assertKeysEquals("\033[5~", KeyHandler.getCodeFromTermcap("K3", false, false));
|
||||
assertKeysEquals("\033[F", KeyHandler.getCodeFromTermcap("K4", false, false));
|
||||
assertKeysEquals("\033OF", KeyHandler.getCodeFromTermcap("K4", true, false));
|
||||
assertKeysEquals("\033[6~", KeyHandler.getCodeFromTermcap("K5", false, false));
|
||||
|
||||
// Function keys F1-F12:
|
||||
assertKeysEquals("\033OP", KeyHandler.getCodeFromTermcap("k1", false, false));
|
||||
assertKeysEquals("\033OQ", KeyHandler.getCodeFromTermcap("k2", false, false));
|
||||
assertKeysEquals("\033OR", KeyHandler.getCodeFromTermcap("k3", false, false));
|
||||
assertKeysEquals("\033OS", KeyHandler.getCodeFromTermcap("k4", false, false));
|
||||
assertKeysEquals("\033[15~", KeyHandler.getCodeFromTermcap("k5", false, false));
|
||||
assertKeysEquals("\033[17~", KeyHandler.getCodeFromTermcap("k6", false, false));
|
||||
assertKeysEquals("\033[18~", KeyHandler.getCodeFromTermcap("k7", false, false));
|
||||
assertKeysEquals("\033[19~", KeyHandler.getCodeFromTermcap("k8", false, false));
|
||||
assertKeysEquals("\033[20~", KeyHandler.getCodeFromTermcap("k9", false, false));
|
||||
assertKeysEquals("\033[21~", KeyHandler.getCodeFromTermcap("k;", false, false));
|
||||
assertKeysEquals("\033[23~", KeyHandler.getCodeFromTermcap("F1", false, false));
|
||||
assertKeysEquals("\033[24~", KeyHandler.getCodeFromTermcap("F2", false, false));
|
||||
// Function keys F13-F24 (same as shifted F1-F12):
|
||||
assertKeysEquals("\033[1;2P", KeyHandler.getCodeFromTermcap("F3", false, false));
|
||||
assertKeysEquals("\033[1;2Q", KeyHandler.getCodeFromTermcap("F4", false, false));
|
||||
assertKeysEquals("\033[1;2R", KeyHandler.getCodeFromTermcap("F5", false, false));
|
||||
assertKeysEquals("\033[1;2S", KeyHandler.getCodeFromTermcap("F6", false, false));
|
||||
assertKeysEquals("\033[15;2~", KeyHandler.getCodeFromTermcap("F7", false, false));
|
||||
assertKeysEquals("\033[17;2~", KeyHandler.getCodeFromTermcap("F8", false, false));
|
||||
assertKeysEquals("\033[18;2~", KeyHandler.getCodeFromTermcap("F9", false, false));
|
||||
assertKeysEquals("\033[19;2~", KeyHandler.getCodeFromTermcap("FA", false, false));
|
||||
assertKeysEquals("\033[20;2~", KeyHandler.getCodeFromTermcap("FB", false, false));
|
||||
assertKeysEquals("\033[21;2~", KeyHandler.getCodeFromTermcap("FC", false, false));
|
||||
assertKeysEquals("\033[23;2~", KeyHandler.getCodeFromTermcap("FD", false, false));
|
||||
assertKeysEquals("\033[24;2~", KeyHandler.getCodeFromTermcap("FE", false, false));
|
||||
}
|
||||
|
||||
public void testKeyCodes() {
|
||||
// Return sends carriage return (\r), which normally gets translated by the device driver to newline (\n) unless the ICRNL termios
|
||||
// flag has been set.
|
||||
assertKeysEquals("\r", KeyHandler.getCode(KeyEvent.KEYCODE_ENTER, 0, false, false));
|
||||
|
||||
// Backspace.
|
||||
assertKeysEquals("\u007f", KeyHandler.getCode(KeyEvent.KEYCODE_DEL, 0, false, false));
|
||||
|
||||
// Space.
|
||||
assertNull(KeyHandler.getCode(KeyEvent.KEYCODE_SPACE, 0, false, false));
|
||||
assertKeysEquals("\u0000", KeyHandler.getCode(KeyEvent.KEYCODE_SPACE, KeyHandler.KEYMOD_CTRL, false, false));
|
||||
|
||||
// Back tab.
|
||||
assertKeysEquals("\033[Z", KeyHandler.getCode(KeyEvent.KEYCODE_TAB, KeyHandler.KEYMOD_SHIFT, false, false));
|
||||
|
||||
// Arrow keys (up/down/right/left):
|
||||
assertKeysEquals("\033[A", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_UP, 0, false, false));
|
||||
assertKeysEquals("\033[B", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_DOWN, 0, false, false));
|
||||
assertKeysEquals("\033[C", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_RIGHT, 0, false, false));
|
||||
assertKeysEquals("\033[D", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_LEFT, 0, false, false));
|
||||
// .. shifted:
|
||||
assertKeysEquals("\033[1;2A", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_UP, KeyHandler.KEYMOD_SHIFT, false, false));
|
||||
assertKeysEquals("\033[1;2B", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_DOWN, KeyHandler.KEYMOD_SHIFT, false, false));
|
||||
assertKeysEquals("\033[1;2C", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_RIGHT, KeyHandler.KEYMOD_SHIFT, false, false));
|
||||
assertKeysEquals("\033[1;2D", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_LEFT, KeyHandler.KEYMOD_SHIFT, false, false));
|
||||
// .. ctrl:ed:
|
||||
assertKeysEquals("\033[1;5A", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_UP, KeyHandler.KEYMOD_CTRL, false, false));
|
||||
assertKeysEquals("\033[1;5B", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_DOWN, KeyHandler.KEYMOD_CTRL, false, false));
|
||||
assertKeysEquals("\033[1;5C", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_RIGHT, KeyHandler.KEYMOD_CTRL, false, false));
|
||||
assertKeysEquals("\033[1;5D", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_LEFT, KeyHandler.KEYMOD_CTRL, false, false));
|
||||
// .. ctrl:ed and shifted:
|
||||
int mod = KeyHandler.KEYMOD_CTRL | KeyHandler.KEYMOD_SHIFT;
|
||||
assertKeysEquals("\033[1;6A", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_UP, mod, false, false));
|
||||
assertKeysEquals("\033[1;6B", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_DOWN, mod, false, false));
|
||||
assertKeysEquals("\033[1;6C", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_RIGHT, mod, false, false));
|
||||
assertKeysEquals("\033[1;6D", KeyHandler.getCode(KeyEvent.KEYCODE_DPAD_LEFT, mod, false, false));
|
||||
|
||||
// Home/end keys:
|
||||
assertKeysEquals("\033[H", KeyHandler.getCode(KeyEvent.KEYCODE_MOVE_HOME, 0, false, false));
|
||||
assertKeysEquals("\033[F", KeyHandler.getCode(KeyEvent.KEYCODE_MOVE_END, 0, false, false));
|
||||
// ... shifted:
|
||||
assertKeysEquals("\033[1;2H", KeyHandler.getCode(KeyEvent.KEYCODE_MOVE_HOME, KeyHandler.KEYMOD_SHIFT, false, false));
|
||||
assertKeysEquals("\033[1;2F", KeyHandler.getCode(KeyEvent.KEYCODE_MOVE_END, KeyHandler.KEYMOD_SHIFT, false, false));
|
||||
|
||||
// Function keys F1-F12:
|
||||
assertKeysEquals("\033OP", KeyHandler.getCode(KeyEvent.KEYCODE_F1, 0, false, false));
|
||||
assertKeysEquals("\033OQ", KeyHandler.getCode(KeyEvent.KEYCODE_F2, 0, false, false));
|
||||
assertKeysEquals("\033OR", KeyHandler.getCode(KeyEvent.KEYCODE_F3, 0, false, false));
|
||||
assertKeysEquals("\033OS", KeyHandler.getCode(KeyEvent.KEYCODE_F4, 0, false, false));
|
||||
assertKeysEquals("\033[15~", KeyHandler.getCode(KeyEvent.KEYCODE_F5, 0, false, false));
|
||||
assertKeysEquals("\033[17~", KeyHandler.getCode(KeyEvent.KEYCODE_F6, 0, false, false));
|
||||
assertKeysEquals("\033[18~", KeyHandler.getCode(KeyEvent.KEYCODE_F7, 0, false, false));
|
||||
assertKeysEquals("\033[19~", KeyHandler.getCode(KeyEvent.KEYCODE_F8, 0, false, false));
|
||||
assertKeysEquals("\033[20~", KeyHandler.getCode(KeyEvent.KEYCODE_F9, 0, false, false));
|
||||
assertKeysEquals("\033[21~", KeyHandler.getCode(KeyEvent.KEYCODE_F10, 0, false, false));
|
||||
assertKeysEquals("\033[23~", KeyHandler.getCode(KeyEvent.KEYCODE_F11, 0, false, false));
|
||||
assertKeysEquals("\033[24~", KeyHandler.getCode(KeyEvent.KEYCODE_F12, 0, false, false));
|
||||
// Function keys F13-F24 (same as shifted F1-F12):
|
||||
assertKeysEquals("\033[1;2P", KeyHandler.getCode(KeyEvent.KEYCODE_F1, KeyHandler.KEYMOD_SHIFT, false, false));
|
||||
assertKeysEquals("\033[1;2Q", KeyHandler.getCode(KeyEvent.KEYCODE_F2, KeyHandler.KEYMOD_SHIFT, false, false));
|
||||
assertKeysEquals("\033[1;2R", KeyHandler.getCode(KeyEvent.KEYCODE_F3, KeyHandler.KEYMOD_SHIFT, false, false));
|
||||
assertKeysEquals("\033[1;2S", KeyHandler.getCode(KeyEvent.KEYCODE_F4, KeyHandler.KEYMOD_SHIFT, false, false));
|
||||
assertKeysEquals("\033[15;2~", KeyHandler.getCode(KeyEvent.KEYCODE_F5, KeyHandler.KEYMOD_SHIFT, false, false));
|
||||
assertKeysEquals("\033[17;2~", KeyHandler.getCode(KeyEvent.KEYCODE_F6, KeyHandler.KEYMOD_SHIFT, false, false));
|
||||
assertKeysEquals("\033[18;2~", KeyHandler.getCode(KeyEvent.KEYCODE_F7, KeyHandler.KEYMOD_SHIFT, false, false));
|
||||
assertKeysEquals("\033[19;2~", KeyHandler.getCode(KeyEvent.KEYCODE_F8, KeyHandler.KEYMOD_SHIFT, false, false));
|
||||
assertKeysEquals("\033[20;2~", KeyHandler.getCode(KeyEvent.KEYCODE_F9, KeyHandler.KEYMOD_SHIFT, false, false));
|
||||
assertKeysEquals("\033[21;2~", KeyHandler.getCode(KeyEvent.KEYCODE_F10, KeyHandler.KEYMOD_SHIFT, false, false));
|
||||
assertKeysEquals("\033[23;2~", KeyHandler.getCode(KeyEvent.KEYCODE_F11, KeyHandler.KEYMOD_SHIFT, false, false));
|
||||
assertKeysEquals("\033[24;2~", KeyHandler.getCode(KeyEvent.KEYCODE_F12, KeyHandler.KEYMOD_SHIFT, false, false));
|
||||
|
||||
assertKeysEquals("0", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_0, KeyHandler.KEYMOD_NUM_LOCK, false, false));
|
||||
assertKeysEquals("1", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_1, KeyHandler.KEYMOD_NUM_LOCK, false, false));
|
||||
assertKeysEquals("2", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_2, KeyHandler.KEYMOD_NUM_LOCK, false, false));
|
||||
assertKeysEquals("3", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_3, KeyHandler.KEYMOD_NUM_LOCK, false, false));
|
||||
assertKeysEquals("4", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_4, KeyHandler.KEYMOD_NUM_LOCK, false, false));
|
||||
assertKeysEquals("5", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_5, KeyHandler.KEYMOD_NUM_LOCK, false, false));
|
||||
assertKeysEquals("6", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_6, KeyHandler.KEYMOD_NUM_LOCK, false, false));
|
||||
assertKeysEquals("7", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_7, KeyHandler.KEYMOD_NUM_LOCK, false, false));
|
||||
assertKeysEquals("8", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_8, KeyHandler.KEYMOD_NUM_LOCK, false, false));
|
||||
assertKeysEquals("9", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_9, KeyHandler.KEYMOD_NUM_LOCK, false, false));
|
||||
assertKeysEquals(",", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_COMMA, KeyHandler.KEYMOD_NUM_LOCK, false, false));
|
||||
assertKeysEquals(".", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_DOT, KeyHandler.KEYMOD_NUM_LOCK, false, false));
|
||||
|
||||
assertKeysEquals("\033[2~", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_0, 0, false, false));
|
||||
assertKeysEquals("\033[F", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_1, 0, false, false));
|
||||
assertKeysEquals("\033[B", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_2, 0, false, false));
|
||||
assertKeysEquals("\033[6~", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_3, 0, false, false));
|
||||
assertKeysEquals("\033[D", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_4, 0, false, false));
|
||||
assertKeysEquals("5", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_5, 0, false, false));
|
||||
assertKeysEquals("\033[C", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_6, 0, false, false));
|
||||
assertKeysEquals("\033[H", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_7, 0, false, false));
|
||||
assertKeysEquals("\033[A", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_8, 0, false, false));
|
||||
assertKeysEquals("\033[5~", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_9, 0, false, false));
|
||||
assertKeysEquals("\033[3~", KeyHandler.getCode(KeyEvent.KEYCODE_NUMPAD_DOT, 0, false, false));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
package com.itsaky.terminal;
|
||||
|
||||
import android.util.Base64;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/** "ESC ]" is the Operating System Command. */
|
||||
public class OperatingSystemControlTest extends TerminalTestCase {
|
||||
|
||||
public void testSetTitle() throws Exception {
|
||||
List<ChangedTitle> expectedTitleChanges = new ArrayList<>();
|
||||
|
||||
withTerminalSized(10, 10);
|
||||
enterString("\033]0;Hello, world\007");
|
||||
assertEquals("Hello, world", mTerminal.getTitle());
|
||||
expectedTitleChanges.add(new ChangedTitle(null, "Hello, world"));
|
||||
assertEquals(expectedTitleChanges, mOutput.titleChanges);
|
||||
|
||||
enterString("\033]0;Goodbye, world\007");
|
||||
assertEquals("Goodbye, world", mTerminal.getTitle());
|
||||
expectedTitleChanges.add(new ChangedTitle("Hello, world", "Goodbye, world"));
|
||||
assertEquals(expectedTitleChanges, mOutput.titleChanges);
|
||||
|
||||
enterString("\033]0;Goodbye, \u00F1 world\007");
|
||||
assertEquals("Goodbye, \uu00F1 world", mTerminal.getTitle());
|
||||
expectedTitleChanges.add(new ChangedTitle("Goodbye, world", "Goodbye, \uu00F1 world"));
|
||||
assertEquals(expectedTitleChanges, mOutput.titleChanges);
|
||||
|
||||
// 2 should work as well (0 sets both title and icon).
|
||||
enterString("\033]2;Updated\007");
|
||||
assertEquals("Updated", mTerminal.getTitle());
|
||||
expectedTitleChanges.add(new ChangedTitle("Goodbye, \uu00F1 world", "Updated"));
|
||||
assertEquals(expectedTitleChanges, mOutput.titleChanges);
|
||||
|
||||
enterString("\033[22;0t");
|
||||
enterString("\033]0;FIRST\007");
|
||||
expectedTitleChanges.add(new ChangedTitle("Updated", "FIRST"));
|
||||
assertEquals("FIRST", mTerminal.getTitle());
|
||||
assertEquals(expectedTitleChanges, mOutput.titleChanges);
|
||||
|
||||
enterString("\033[22;0t");
|
||||
enterString("\033]0;SECOND\007");
|
||||
assertEquals("SECOND", mTerminal.getTitle());
|
||||
|
||||
expectedTitleChanges.add(new ChangedTitle("FIRST", "SECOND"));
|
||||
assertEquals(expectedTitleChanges, mOutput.titleChanges);
|
||||
|
||||
enterString("\033[23;0t");
|
||||
assertEquals("FIRST", mTerminal.getTitle());
|
||||
|
||||
expectedTitleChanges.add(new ChangedTitle("SECOND", "FIRST"));
|
||||
assertEquals(expectedTitleChanges, mOutput.titleChanges);
|
||||
|
||||
enterString("\033[23;0t");
|
||||
expectedTitleChanges.add(new ChangedTitle("FIRST", "Updated"));
|
||||
assertEquals(expectedTitleChanges, mOutput.titleChanges);
|
||||
|
||||
enterString("\033[22;0t");
|
||||
enterString("\033[22;0t");
|
||||
enterString("\033[22;0t");
|
||||
// Popping to same title should not cause changes.
|
||||
enterString("\033[23;0t");
|
||||
enterString("\033[23;0t");
|
||||
enterString("\033[23;0t");
|
||||
assertEquals(expectedTitleChanges, mOutput.titleChanges);
|
||||
}
|
||||
|
||||
public void testTitleStack() throws Exception {
|
||||
// echo -ne '\e]0;BEFORE\007' # set title
|
||||
// echo -ne '\e[22t' # push to stack
|
||||
// echo -ne '\e]0;AFTER\007' # set new title
|
||||
// echo -ne '\e[23t' # retrieve from stack
|
||||
|
||||
withTerminalSized(10, 10);
|
||||
enterString("\033]0;InitialTitle\007");
|
||||
assertEquals("InitialTitle", mTerminal.getTitle());
|
||||
enterString("\033[22t");
|
||||
assertEquals("InitialTitle", mTerminal.getTitle());
|
||||
enterString("\033]0;UpdatedTitle\007");
|
||||
assertEquals("UpdatedTitle", mTerminal.getTitle());
|
||||
enterString("\033[23t");
|
||||
assertEquals("InitialTitle", mTerminal.getTitle());
|
||||
enterString("\033[23t\033[23t\033[23t");
|
||||
assertEquals("InitialTitle", mTerminal.getTitle());
|
||||
}
|
||||
|
||||
public void testSetColor() throws Exception {
|
||||
// "OSC 4; $INDEX; $COLORSPEC BEL" => Change color $INDEX to the color specified by $COLORSPEC.
|
||||
withTerminalSized(4, 4).enterString("\033]4;5;#00FF00\007");
|
||||
assertEquals(Integer.toHexString(0xFF00FF00), Integer.toHexString(mTerminal.mColors.mCurrentColors[5]));
|
||||
enterString("\033]4;5;#00FFAB\007");
|
||||
assertEquals(mTerminal.mColors.mCurrentColors[5], 0xFF00FFAB);
|
||||
enterString("\033]4;255;#ABFFAB\007");
|
||||
assertEquals(mTerminal.mColors.mCurrentColors[255], 0xFFABFFAB);
|
||||
// Two indexed colors at once:
|
||||
enterString("\033]4;7;#00FF00;8;#0000FF\007");
|
||||
assertEquals(mTerminal.mColors.mCurrentColors[7], 0xFF00FF00);
|
||||
assertEquals(mTerminal.mColors.mCurrentColors[8], 0xFF0000FF);
|
||||
}
|
||||
|
||||
void assertIndexColorsMatch(int[] expected) {
|
||||
for (int i = 0; i < 255; i++)
|
||||
assertEquals("index=" + i, expected[i], mTerminal.mColors.mCurrentColors[i]);
|
||||
}
|
||||
|
||||
public void testResetColor() throws Exception {
|
||||
withTerminalSized(4, 4);
|
||||
int[] initialColors = new int[TextStyle.NUM_INDEXED_COLORS];
|
||||
System.arraycopy(mTerminal.mColors.mCurrentColors, 0, initialColors, 0, initialColors.length);
|
||||
int[] expectedColors = new int[initialColors.length];
|
||||
System.arraycopy(mTerminal.mColors.mCurrentColors, 0, expectedColors, 0, expectedColors.length);
|
||||
Random rand = new Random();
|
||||
for (int endType = 0; endType < 3; endType++) {
|
||||
// Both BEL (7) and ST (ESC \) can end an OSC sequence.
|
||||
String ender = (endType == 0) ? "\007" : "\033\\";
|
||||
for (int i = 0; i < 255; i++) {
|
||||
expectedColors[i] = 0xFF000000 + (rand.nextInt() & 0xFFFFFF);
|
||||
int r = (expectedColors[i] >> 16) & 0xFF;
|
||||
int g = (expectedColors[i] >> 8) & 0xFF;
|
||||
int b = expectedColors[i] & 0xFF;
|
||||
String rgbHex = String.format("%02x", r) + String.format("%02x", g) + String.format("%02x", b);
|
||||
enterString("\033]4;" + i + ";#" + rgbHex + ender);
|
||||
assertEquals(expectedColors[i], mTerminal.mColors.mCurrentColors[i]);
|
||||
}
|
||||
}
|
||||
|
||||
enterString("\033]104;0\007");
|
||||
expectedColors[0] = TerminalColors.COLOR_SCHEME.mDefaultColors[0];
|
||||
assertIndexColorsMatch(expectedColors);
|
||||
enterString("\033]104;1;2\007");
|
||||
expectedColors[1] = TerminalColors.COLOR_SCHEME.mDefaultColors[1];
|
||||
expectedColors[2] = TerminalColors.COLOR_SCHEME.mDefaultColors[2];
|
||||
assertIndexColorsMatch(expectedColors);
|
||||
enterString("\033]104\007"); // Reset all colors.
|
||||
assertIndexColorsMatch(TerminalColors.COLOR_SCHEME.mDefaultColors);
|
||||
}
|
||||
|
||||
public void disabledTestSetClipboard() {
|
||||
// Cannot run this as a unit test since Base64 is a android.util class.
|
||||
enterString("\033]52;c;" + Base64.encodeToString("Hello, world".getBytes(), 0) + "\007");
|
||||
}
|
||||
|
||||
public void testResettingTerminalResetsColor() throws Exception {
|
||||
// "OSC 4; $INDEX; $COLORSPEC BEL" => Change color $INDEX to the color specified by $COLORSPEC.
|
||||
withTerminalSized(4, 4).enterString("\033]4;5;#00FF00\007");
|
||||
enterString("\033]4;5;#00FFAB\007").assertColor(5, 0xFF00FFAB);
|
||||
enterString("\033]4;255;#ABFFAB\007").assertColor(255, 0xFFABFFAB);
|
||||
mTerminal.reset();
|
||||
assertIndexColorsMatch(TerminalColors.COLOR_SCHEME.mDefaultColors);
|
||||
}
|
||||
|
||||
public void testSettingDynamicColors() {
|
||||
// "${OSC}${DYNAMIC};${COLORSPEC}${BEL_OR_STRINGTERMINATOR}" => Change ${DYNAMIC} color to the color specified by $COLORSPEC where:
|
||||
// DYNAMIC=10: Text foreground color.
|
||||
// DYNAMIC=11: Text background color.
|
||||
// DYNAMIC=12: Text cursor color.
|
||||
withTerminalSized(3, 3).enterString("\033]10;#ABCD00\007").assertColor(TextStyle.COLOR_INDEX_FOREGROUND, 0xFFABCD00);
|
||||
enterString("\033]11;#0ABCD0\007").assertColor(TextStyle.COLOR_INDEX_BACKGROUND, 0xFF0ABCD0);
|
||||
enterString("\033]12;#00ABCD\007").assertColor(TextStyle.COLOR_INDEX_CURSOR, 0xFF00ABCD);
|
||||
// Two special colors at once
|
||||
// ("Each successive parameter changes the next color in the list. The value of P s tells the starting point in the list"):
|
||||
enterString("\033]10;#FF0000;#00FF00\007").assertColor(TextStyle.COLOR_INDEX_FOREGROUND, 0xFFFF0000);
|
||||
assertColor(TextStyle.COLOR_INDEX_BACKGROUND, 0xFF00FF00);
|
||||
// Three at once:
|
||||
enterString("\033]10;#0000FF;#00FF00;#FF0000\007").assertColor(TextStyle.COLOR_INDEX_FOREGROUND, 0xFF0000FF);
|
||||
assertColor(TextStyle.COLOR_INDEX_BACKGROUND, 0xFF00FF00).assertColor(TextStyle.COLOR_INDEX_CURSOR, 0xFFFF0000);
|
||||
|
||||
// Without ending semicolon:
|
||||
enterString("\033]10;#FF0000\007").assertColor(TextStyle.COLOR_INDEX_FOREGROUND, 0xFFFF0000);
|
||||
// For background and cursor:
|
||||
enterString("\033]11;#FFFF00;\007").assertColor(TextStyle.COLOR_INDEX_BACKGROUND, 0xFFFFFF00);
|
||||
enterString("\033]12;#00FFFF;\007").assertColor(TextStyle.COLOR_INDEX_CURSOR, 0xFF00FFFF);
|
||||
|
||||
// Using string terminator:
|
||||
String stringTerminator = "\033\\";
|
||||
enterString("\033]10;#FF0000" + stringTerminator).assertColor(TextStyle.COLOR_INDEX_FOREGROUND, 0xFFFF0000);
|
||||
// For background and cursor:
|
||||
enterString("\033]11;#FFFF00;" + stringTerminator).assertColor(TextStyle.COLOR_INDEX_BACKGROUND, 0xFFFFFF00);
|
||||
enterString("\033]12;#00FFFF;" + stringTerminator).assertColor(TextStyle.COLOR_INDEX_CURSOR, 0xFF00FFFF);
|
||||
}
|
||||
|
||||
public void testReportSpecialColors() {
|
||||
// "${OSC}${DYNAMIC};?${BEL}" => Terminal responds with the control sequence which would set the current color.
|
||||
// Both xterm and libvte (gnome-terminal and others) use the longest color representation, which means that
|
||||
// the response is "${OSC}rgb:RRRR/GGGG/BBBB"
|
||||
withTerminalSized(3, 3).enterString("\033]10;#ABCD00\007").assertColor(TextStyle.COLOR_INDEX_FOREGROUND, 0xFFABCD00);
|
||||
assertEnteringStringGivesResponse("\033]10;?\007", "\033]10;rgb:abab/cdcd/0000\007");
|
||||
// Same as above but with string terminator. xterm uses the same string terminator in the response, which
|
||||
// e.g. script posted at http://superuser.com/questions/157563/programmatic-access-to-current-xterm-background-color
|
||||
// relies on:
|
||||
assertEnteringStringGivesResponse("\033]10;?\033\\", "\033]10;rgb:abab/cdcd/0000\033\\");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
package com.itsaky.terminal;
|
||||
|
||||
public class RectangularAreasTest extends TerminalTestCase {
|
||||
|
||||
/** http://www.vt100.net/docs/vt510-rm/DECFRA */
|
||||
public void testFillRectangularArea() {
|
||||
withTerminalSized(3, 3).enterString("\033[88$x").assertLinesAre("XXX", "XXX", "XXX");
|
||||
withTerminalSized(3, 3).enterString("\033[88;1;1;2;10$x").assertLinesAre("XXX", "XXX", " ");
|
||||
withTerminalSized(3, 3).enterString("\033[88;2;1;3;10$x").assertLinesAre(" ", "XXX", "XXX");
|
||||
withTerminalSized(3, 3).enterString("\033[88;1;1;100;1$x").assertLinesAre("X ", "X ", "X ");
|
||||
withTerminalSized(3, 3).enterString("\033[88;1;1;100;2$x").assertLinesAre("XX ", "XX ", "XX ");
|
||||
withTerminalSized(3, 3).enterString("\033[88;100;1;100;2$x").assertLinesAre(" ", " ", " ");
|
||||
}
|
||||
|
||||
/** http://www.vt100.net/docs/vt510-rm/DECERA */
|
||||
public void testEraseRectangularArea() {
|
||||
withTerminalSized(3, 3).enterString("ABCDEFGHI\033[$z").assertLinesAre(" ", " ", " ");
|
||||
withTerminalSized(3, 3).enterString("ABCDEFGHI\033[1;1;2;10$z").assertLinesAre(" ", " ", "GHI");
|
||||
withTerminalSized(3, 3).enterString("ABCDEFGHI\033[2;1;3;10$z").assertLinesAre("ABC", " ", " ");
|
||||
withTerminalSized(3, 3).enterString("ABCDEFGHI\033[1;1;100;1$z").assertLinesAre(" BC", " EF", " HI");
|
||||
withTerminalSized(3, 3).enterString("ABCDEFGHI\033[1;1;100;2$z").assertLinesAre(" C", " F", " I");
|
||||
withTerminalSized(3, 3).enterString("ABCDEFGHI\033[100;1;100;2$z").assertLinesAre("ABC", "DEF", "GHI");
|
||||
|
||||
withTerminalSized(3, 3).enterString("A\033[$zBC").assertLinesAre(" BC", " ", " ");
|
||||
}
|
||||
|
||||
/** http://www.vt100.net/docs/vt510-rm/DECSED */
|
||||
public void testSelectiveEraseInDisplay() {
|
||||
// ${CSI}1"q enables protection, ${CSI}0"q disables it.
|
||||
// ${CSI}?${0,1,2}J" erases (0=cursor to end, 1=start to cursor, 2=complete display).
|
||||
withTerminalSized(3, 3).enterString("ABCDEFGHI\033[?2J").assertLinesAre(" ", " ", " ");
|
||||
withTerminalSized(3, 3).enterString("ABC\033[1\"qDE\033[0\"qFGHI\033[?2J").assertLinesAre(" ", "DE ", " ");
|
||||
withTerminalSized(3, 3).enterString("\033[1\"qABCDE\033[0\"qFGHI\033[?2J").assertLinesAre("ABC", "DE ", " ");
|
||||
}
|
||||
|
||||
/** http://vt100.net/docs/vt510-rm/DECSEL */
|
||||
public void testSelectiveEraseInLine() {
|
||||
// ${CSI}1"q enables protection, ${CSI}0"q disables it.
|
||||
// ${CSI}?${0,1,2}K" erases (0=cursor to end, 1=start to cursor, 2=complete line).
|
||||
withTerminalSized(3, 3).enterString("ABCDEFGHI\033[?2K").assertLinesAre("ABC", "DEF", " ");
|
||||
withTerminalSized(3, 3).enterString("ABCDE\033[?0KFGHI").assertLinesAre("ABC", "DEF", "GHI");
|
||||
withTerminalSized(3, 3).enterString("ABCDE\033[?1KFGHI").assertLinesAre("ABC", " F", "GHI");
|
||||
withTerminalSized(3, 3).enterString("ABCDE\033[?2KFGHI").assertLinesAre("ABC", " F", "GHI");
|
||||
withTerminalSized(3, 3).enterString("ABCDEFGHI\033[2;2H\033[?0K").assertLinesAre("ABC", "D ", "GHI");
|
||||
withTerminalSized(3, 3).enterString("ABC\033[1\"qD\033[0\"qE\033[?2KFGHI").assertLinesAre("ABC", "D F", "GHI");
|
||||
}
|
||||
|
||||
/** http://www.vt100.net/docs/vt510-rm/DECSERA */
|
||||
public void testSelectiveEraseInRectangle() {
|
||||
// ${CSI}1"q enables protection, ${CSI}0"q disables it.
|
||||
// ${CSI}?${TOP};${LEFT};${BOTTOM};${RIGHT}${" erases.
|
||||
withTerminalSized(3, 3).enterString("ABCDEFGHI\033[${").assertLinesAre(" ", " ", " ");
|
||||
withTerminalSized(3, 3).enterString("ABCDEFGHI\033[1;1;2;10${").assertLinesAre(" ", " ", "GHI");
|
||||
withTerminalSized(3, 3).enterString("ABCDEFGHI\033[2;1;3;10${").assertLinesAre("ABC", " ", " ");
|
||||
withTerminalSized(3, 3).enterString("ABCDEFGHI\033[1;1;100;1${").assertLinesAre(" BC", " EF", " HI");
|
||||
withTerminalSized(3, 3).enterString("ABCDEFGHI\033[1;1;100;2${").assertLinesAre(" C", " F", " I");
|
||||
withTerminalSized(3, 3).enterString("ABCDEFGHI\033[100;1;100;2${").assertLinesAre("ABC", "DEF", "GHI");
|
||||
|
||||
withTerminalSized(3, 3).enterString("ABCD\033[1\"qE\033[0\"qFGHI\033[${").assertLinesAre(" ", " E ", " ");
|
||||
withTerminalSized(3, 3).enterString("ABCD\033[1\"qE\033[0\"qFGHI\033[1;1;2;10${").assertLinesAre(" ", " E ", "GHI");
|
||||
}
|
||||
|
||||
/** http://vt100.net/docs/vt510-rm/DECCRA */
|
||||
public void testRectangularCopy() {
|
||||
// "${CSI}${SRC_TOP};${SRC_LEFT};${SRC_BOTTOM};${SRC_RIGHT};${SRC_PAGE};${DST_TOP};${DST_LEFT};${DST_PAGE}\$v"
|
||||
withTerminalSized(7, 3).enterString("ABC\r\nDEF\r\nGHI\033[1;1;2;2;1;2;5;1$v").assertLinesAre("ABC ", "DEF AB ", "GHI DE ");
|
||||
withTerminalSized(7, 3).enterString("ABC\r\nDEF\r\nGHI\033[1;1;3;3;1;1;4;1$v").assertLinesAre("ABCABC ", "DEFDEF ", "GHIGHI ");
|
||||
withTerminalSized(7, 3).enterString("ABC\r\nDEF\r\nGHI\033[1;1;3;3;1;1;3;1$v").assertLinesAre("ABABC ", "DEDEF ", "GHGHI ");
|
||||
withTerminalSized(7, 3).enterString(" ABC\r\n DEF\r\n GHI\033[1;4;3;6;1;1;1;1$v").assertLinesAre("ABCABC ", "DEFDEF ",
|
||||
"GHIGHI ");
|
||||
withTerminalSized(7, 3).enterString(" ABC\r\n DEF\r\n GHI\033[1;4;3;6;1;1;2;1$v").assertLinesAre(" ABCBC ", " DEFEF ",
|
||||
" GHIHI ");
|
||||
withTerminalSized(3, 3).enterString("ABC\r\nDEF\r\nGHI\033[1;1;2;2;1;2;2;1$v").assertLinesAre("ABC", "DAB", "GDE");
|
||||
|
||||
// Enable ${CSI}?6h origin mode (DECOM) and ${CSI}?69h for left/right margin (DECLRMM) enabling, ${CSI}${LEFTMARGIN};${RIGHTMARGIN}s
|
||||
// for DECSLRM margin setting.
|
||||
withTerminalSized(5, 5).enterString("\033[?6h\033[?69h\033[2;4s");
|
||||
enterString("ABCDEFGHIJK").assertLinesAre(" ABC ", " DEF ", " GHI ", " JK ", " ");
|
||||
enterString("\033[1;1;2;2;1;2;2;1$v").assertLinesAre(" ABC ", " DAB ", " GDE ", " JK ", " ");
|
||||
}
|
||||
|
||||
/** http://vt100.net/docs/vt510-rm/DECCARA */
|
||||
public void testChangeAttributesInRectangularArea() {
|
||||
final int b = TextStyle.CHARACTER_ATTRIBUTE_BOLD;
|
||||
// "${CSI}${TOP};${LEFT};${BOTTOM};${RIGHT};${ATTRIBUTES}\$r"
|
||||
withTerminalSized(3, 3).enterString("ABCDEFGHI\033[1;1;2;2;1$r").assertLinesAre("ABC", "DEF", "GHI");
|
||||
assertEffectAttributesSet(effectLine(b, b, b), effectLine(b, b, 0), effectLine(0, 0, 0));
|
||||
|
||||
// Now with http://www.vt100.net/docs/vt510-rm/DECSACE ("${CSI}2*x") specifying rectangle:
|
||||
withTerminalSized(3, 3).enterString("\033[2*xABCDEFGHI\033[1;1;2;2;1$r").assertLinesAre("ABC", "DEF", "GHI");
|
||||
assertEffectAttributesSet(effectLine(b, b, 0), effectLine(b, b, 0), effectLine(0, 0, 0));
|
||||
}
|
||||
|
||||
/** http://vt100.net/docs/vt510-rm/DECCARA */
|
||||
public void testReverseAttributesInRectangularArea() {
|
||||
final int b = TextStyle.CHARACTER_ATTRIBUTE_BOLD;
|
||||
final int u = TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE;
|
||||
final int bu = TextStyle.CHARACTER_ATTRIBUTE_BOLD | TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE;
|
||||
// "${CSI}${TOP};${LEFT};${BOTTOM};${RIGHT};${ATTRIBUTES}\$t"
|
||||
withTerminalSized(3, 3).enterString("ABCDEFGHI\033[1;1;2;2;1$t").assertLinesAre("ABC", "DEF", "GHI");
|
||||
assertEffectAttributesSet(effectLine(b, b, b), effectLine(b, b, 0), effectLine(0, 0, 0));
|
||||
|
||||
// Now with http://www.vt100.net/docs/vt510-rm/DECSACE ("${CSI}2*x") specifying rectangle:
|
||||
withTerminalSized(3, 3).enterString("\033[2*xABCDEFGHI\033[1;1;2;2;1$t").assertLinesAre("ABC", "DEF", "GHI");
|
||||
assertEffectAttributesSet(effectLine(b, b, 0), effectLine(b, b, 0), effectLine(0, 0, 0));
|
||||
|
||||
// Check reversal by initially bolding the B:
|
||||
withTerminalSized(3, 3).enterString("\033[2*xA\033[1mB\033[0mCDEFGHI\033[1;1;2;2;1$t").assertLinesAre("ABC", "DEF", "GHI");
|
||||
assertEffectAttributesSet(effectLine(b, 0, 0), effectLine(b, b, 0), effectLine(0, 0, 0));
|
||||
|
||||
// Check reversal by initially underlining A, bolding B, then reversing both bold and underline:
|
||||
withTerminalSized(3, 3).enterString("\033[2*x\033[4mA\033[0m\033[1mB\033[0mCDEFGHI\033[1;1;2;2;1;4$t").assertLinesAre("ABC", "DEF",
|
||||
"GHI");
|
||||
assertEffectAttributesSet(effectLine(b, u, 0), effectLine(bu, bu, 0), effectLine(0, 0, 0));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,212 +0,0 @@
|
||||
package com.itsaky.terminal;
|
||||
|
||||
public class ResizeTest extends TerminalTestCase {
|
||||
|
||||
public void testResizeWhenHasHistory() {
|
||||
final int cols = 3;
|
||||
withTerminalSized(cols, 3).enterString("111222333444555666777888999").assertCursorAt(2, 2).assertLinesAre("777", "888", "999");
|
||||
resize(cols, 5).assertCursorAt(4, 2).assertLinesAre("555", "666", "777", "888", "999");
|
||||
resize(cols, 3).assertCursorAt(2, 2).assertLinesAre("777", "888", "999");
|
||||
}
|
||||
|
||||
public void testResizeWhenInAltBuffer() {
|
||||
final int rows = 3, cols = 3;
|
||||
withTerminalSized(cols, rows).enterString("a\r\ndef$").assertLinesAre("a ", "def", "$ ").assertCursorAt(2, 1);
|
||||
|
||||
// Resize and back again while in main buffer:
|
||||
resize(cols, 5).assertLinesAre("a ", "def", "$ ", " ", " ").assertCursorAt(2, 1);
|
||||
resize(cols, rows).assertLinesAre("a ", "def", "$ ").assertCursorAt(2, 1);
|
||||
|
||||
// Switch to alt buffer:
|
||||
enterString("\033[?1049h").assertLinesAre(" ", " ", " ").assertCursorAt(2, 1);
|
||||
enterString("h").assertLinesAre(" ", " ", " h ").assertCursorAt(2, 2);
|
||||
|
||||
resize(cols, 5).resize(cols, rows);
|
||||
|
||||
// Switch from alt buffer:
|
||||
enterString("\033[?1049l").assertLinesAre("a ", "def", "$ ").assertCursorAt(2, 1);
|
||||
}
|
||||
|
||||
public void testShrinkingInAltBuffer() {
|
||||
final int rows = 5;
|
||||
final int cols = 3;
|
||||
withTerminalSized(cols, rows).enterString("A\r\nB\r\nC\r\nD\r\nE").assertLinesAre("A ", "B ", "C ", "D ", "E ");
|
||||
enterString("\033[?1049h").assertLinesAre(" ", " ", " ", " ", " ");
|
||||
resize(3, 3).enterString("\033[?1049lF").assertLinesAre("C ", "D ", "EF ");
|
||||
}
|
||||
|
||||
public void testResizeAfterNewlineWhenInAltBuffer() {
|
||||
final int rows = 3;
|
||||
final int cols = 3;
|
||||
withTerminalSized(cols, rows);
|
||||
enterString("a\r\nb\r\nc\r\nd\r\ne\r\nf\r\n").assertLinesAre("e ", "f ", " ").assertCursorAt(2, 0);
|
||||
assertLineWraps(false, false, false);
|
||||
|
||||
// Switch to alt buffer:
|
||||
enterString("\033[?1049h").assertLinesAre(" ", " ", " ").assertCursorAt(2, 0);
|
||||
enterString("h").assertLinesAre(" ", " ", "h ").assertCursorAt(2, 1);
|
||||
|
||||
// Grow by two rows:
|
||||
resize(cols, 5).assertLinesAre(" ", " ", "h ", " ", " ").assertCursorAt(2, 1);
|
||||
resize(cols, rows).assertLinesAre(" ", " ", "h ").assertCursorAt(2, 1);
|
||||
|
||||
// Switch from alt buffer:
|
||||
enterString("\033[?1049l").assertLinesAre("e ", "f ", " ").assertCursorAt(2, 0);
|
||||
}
|
||||
|
||||
public void testResizeAfterHistoryWraparound() {
|
||||
final int rows = 3;
|
||||
final int cols = 10;
|
||||
withTerminalSized(cols, rows);
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
String s = Integer.toString(i);
|
||||
enterString(s);
|
||||
buffer.setLength(0);
|
||||
buffer.append(s);
|
||||
while (buffer.length() < cols)
|
||||
buffer.append(' ');
|
||||
if (i > rows) {
|
||||
assertLineIs(rows - 1, buffer.toString());
|
||||
}
|
||||
enterString("\r\n");
|
||||
}
|
||||
assertLinesAre("998 ", "999 ", " ");
|
||||
mTerminal.resize(cols, 2);
|
||||
assertLinesAre("999 ", " ");
|
||||
mTerminal.resize(cols, 5);
|
||||
assertLinesAre("996 ", "997 ", "998 ", "999 ", " ");
|
||||
mTerminal.resize(cols, rows);
|
||||
assertLinesAre("998 ", "999 ", " ");
|
||||
}
|
||||
|
||||
public void testVerticalResize() {
|
||||
final int rows = 5;
|
||||
final int cols = 3;
|
||||
|
||||
withTerminalSized(cols, rows);
|
||||
// Foreground color to 119:
|
||||
enterString("\033[38;5;119m");
|
||||
// Background color to 129:
|
||||
enterString("\033[48;5;129m");
|
||||
// Clear with ED, Erase in Display:
|
||||
enterString("\033[2J");
|
||||
for (int r = 0; r < rows; r++) {
|
||||
for (int c = 0; c < cols; c++) {
|
||||
long style = getStyleAt(r, c);
|
||||
assertEquals(119, TextStyle.decodeForeColor(style));
|
||||
assertEquals(129, TextStyle.decodeBackColor(style));
|
||||
}
|
||||
}
|
||||
enterString("11\r\n22");
|
||||
assertLinesAre("11 ", "22 ", " ", " ", " ").assertLineWraps(false, false, false, false, false);
|
||||
resize(cols, rows - 2).assertLinesAre("11 ", "22 ", " ");
|
||||
|
||||
// After resize, screen should still be same color:
|
||||
for (int r = 0; r < rows - 2; r++) {
|
||||
for (int c = 0; c < cols; c++) {
|
||||
long style = getStyleAt(r, c);
|
||||
assertEquals(119, TextStyle.decodeForeColor(style));
|
||||
assertEquals(129, TextStyle.decodeBackColor(style));
|
||||
}
|
||||
}
|
||||
|
||||
// Background color to 200 and grow back size (which should be cleared to the new background color):
|
||||
enterString("\033[48;5;200m");
|
||||
resize(cols, rows);
|
||||
for (int r = 0; r < rows; r++) {
|
||||
for (int c = 0; c < cols; c++) {
|
||||
long style = getStyleAt(r, c);
|
||||
assertEquals(119, TextStyle.decodeForeColor(style));
|
||||
assertEquals("wrong at row=" + r, r >= 3 ? 200 : 129, TextStyle.decodeBackColor(style));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testHorizontalResize() {
|
||||
final int rows = 5;
|
||||
final int cols = 5;
|
||||
|
||||
withTerminalSized(cols, rows);
|
||||
// Background color to 129:
|
||||
// enterString("\033[48;5;129m").assertLinesAre(" ", " ", " ", " ", " ");
|
||||
enterString("1111\r\n2222\r\n3333\r\n4444\r\n5555").assertCursorAt(4, 4);
|
||||
// assertEquals(129, TextStyle.decodeBackColor(getStyleAt(2, 2)));
|
||||
assertLinesAre("1111 ", "2222 ", "3333 ", "4444 ", "5555 ").assertLineWraps(false, false, false, false, false);
|
||||
resize(cols + 2, rows).assertLinesAre("1111 ", "2222 ", "3333 ", "4444 ", "5555 ").assertCursorAt(4, 4);
|
||||
assertLineWraps(false, false, false, false, false);
|
||||
resize(cols, rows).assertLinesAre("1111 ", "2222 ", "3333 ", "4444 ", "5555 ").assertCursorAt(4, 4);
|
||||
assertLineWraps(false, false, false, false, false);
|
||||
resize(cols - 1, rows).assertLinesAre("2222", "3333", "4444", "5555", " ").assertCursorAt(4, 0);
|
||||
assertLineWraps(false, false, false, true, false);
|
||||
resize(cols - 2, rows).assertLinesAre("3 ", "444", "4 ", "555", "5 ").assertCursorAt(4, 1);
|
||||
assertLineWraps(false, true, false, true, false);
|
||||
// Back to original size:
|
||||
resize(cols, rows).assertLinesAre("1111 ", "2222 ", "3333 ", "4444 ", "5555 ").assertCursorAt(4, 4);
|
||||
assertLineWraps(false, false, false, false, false);
|
||||
}
|
||||
|
||||
public void testLineWrap() {
|
||||
final int rows = 3, cols = 5;
|
||||
withTerminalSized(cols, rows).enterString("111111").assertLinesAre("11111", "1 ", " ");
|
||||
assertCursorAt(1, 1).assertLineWraps(true, false, false);
|
||||
|
||||
resize(7, rows).assertCursorAt(0, 6).assertLinesAre("111111 ", " ", " ").assertLineWraps(false, false, false);
|
||||
resize(cols, rows).assertCursorAt(1, 1).assertLinesAre("11111", "1 ", " ").assertLineWraps(true, false, false);
|
||||
|
||||
enterString("2").assertLinesAre("11111", "12 ", " ").assertLineWraps(true, false, false);
|
||||
enterString("123").assertLinesAre("11111", "12123", " ").assertLineWraps(true, false, false);
|
||||
enterString("W").assertLinesAre("11111", "12123", "W ").assertLineWraps(true, true, false);
|
||||
|
||||
withTerminalSized(cols, rows).enterString("1234512345");
|
||||
assertLinesAre("12345", "12345", " ").assertLineWraps(true, false, false);
|
||||
enterString("W").assertLinesAre("12345", "12345", "W ").assertLineWraps(true, true, false);
|
||||
}
|
||||
|
||||
public void testCursorPositionWhenShrinking() {
|
||||
final int rows = 5, cols = 3;
|
||||
withTerminalSized(cols, rows).enterString("$ ").assertLinesAre("$ ", " ", " ", " ", " ").assertCursorAt(0, 2);
|
||||
resize(3, 3).assertLinesAre("$ ", " ", " ").assertCursorAt(0, 2);
|
||||
resize(cols, rows).assertLinesAre("$ ", " ", " ", " ", " ").assertCursorAt(0, 2);
|
||||
}
|
||||
|
||||
public void testResizeWithCombiningCharInLastColumn() {
|
||||
withTerminalSized(3, 3).enterString("ABC\u0302DEF").assertLinesAre("ABC\u0302", "DEF", " ");
|
||||
resize(4, 3).assertLinesAre("ABC\u0302D", "EF ", " ");
|
||||
|
||||
// Same as above but with colors:
|
||||
withTerminalSized(3, 3).enterString("\033[37mA\033[35mB\033[33mC\u0302\033[32mD\033[31mE\033[34mF").assertLinesAre("ABC\u0302",
|
||||
"DEF", " ");
|
||||
resize(4, 3).assertLinesAre("ABC\u0302D", "EF ", " ");
|
||||
assertForegroundIndices(effectLine(7, 5, 3, 2), effectLine(1, 4, 4, 4), effectLine(4, 4, 4, 4));
|
||||
}
|
||||
|
||||
public void testResizeWithLineWrappingContinuing() {
|
||||
withTerminalSized(5, 3).enterString("\r\nAB DE").assertLinesAre(" ", "AB DE", " ");
|
||||
resize(4, 3).assertLinesAre("AB D", "E ", " ");
|
||||
resize(3, 3).assertLinesAre("AB ", "DE ", " ");
|
||||
resize(5, 3).assertLinesAre(" ", "AB DE", " ");
|
||||
}
|
||||
|
||||
public void testResizeWithWideChars() {
|
||||
final int rows = 3, cols = 4;
|
||||
String twoCharsWidthOne = new String(Character.toChars(TerminalRowTest.TWO_JAVA_CHARS_DISPLAY_WIDTH_ONE_1));
|
||||
withTerminalSized(cols, rows).enterString(twoCharsWidthOne).enterString("\r\n");
|
||||
enterString(twoCharsWidthOne).assertLinesAre(twoCharsWidthOne + " ", twoCharsWidthOne + " ", " ");
|
||||
resize(3, 3).assertLinesAre(twoCharsWidthOne + " ", twoCharsWidthOne + " ", " ");
|
||||
enterString(twoCharsWidthOne).assertLinesAre(twoCharsWidthOne + " ", twoCharsWidthOne + twoCharsWidthOne + " ", " ");
|
||||
}
|
||||
|
||||
public void testResizeWithMoreWideChars() {
|
||||
final int rows = 4, cols = 5;
|
||||
|
||||
withTerminalSized(cols, rows).enterString("qqrr").assertLinesAre("qqrr ", " ", " ", " ");
|
||||
resize(2, rows).assertLinesAre("qq", "rr", " ", " ");
|
||||
resize(5, rows).assertLinesAre("qqrr ", " ", " ", " ");
|
||||
|
||||
withTerminalSized(cols, rows).enterString("QR").assertLinesAre("QR ", " ", " ", " ");
|
||||
resize(2, rows).assertLinesAre("Q", "R", " ", " ");
|
||||
resize(5, rows).assertLinesAre("QR ", " ", " ", " ");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package com.itsaky.terminal;
|
||||
|
||||
public class ScreenBufferTest extends TerminalTestCase {
|
||||
|
||||
public void testBasics() {
|
||||
TerminalBuffer screen = new TerminalBuffer(5, 3, 3);
|
||||
assertEquals("", screen.getTranscriptText());
|
||||
screen.setChar(0, 0, 'a', 0);
|
||||
assertEquals("a", screen.getTranscriptText());
|
||||
screen.setChar(0, 0, 'b', 0);
|
||||
assertEquals("b", screen.getTranscriptText());
|
||||
screen.setChar(2, 0, 'c', 0);
|
||||
assertEquals("b c", screen.getTranscriptText());
|
||||
screen.setChar(2, 2, 'f', 0);
|
||||
assertEquals("b c\n\n f", screen.getTranscriptText());
|
||||
screen.blockSet(0, 0, 2, 2, 'X', 0);
|
||||
}
|
||||
|
||||
public void testBlockSet() {
|
||||
TerminalBuffer screen = new TerminalBuffer(5, 3, 3);
|
||||
screen.blockSet(0, 0, 2, 2, 'X', 0);
|
||||
assertEquals("XX\nXX", screen.getTranscriptText());
|
||||
screen.blockSet(1, 1, 2, 2, 'Y', 0);
|
||||
assertEquals("XX\nXYY\n YY", screen.getTranscriptText());
|
||||
}
|
||||
|
||||
public void testGetSelectedText() {
|
||||
withTerminalSized(5, 3).enterString("ABCDEFGHIJ").assertLinesAre("ABCDE", "FGHIJ", " ");
|
||||
assertEquals("AB", mTerminal.getSelectedText(0, 0, 1, 0));
|
||||
assertEquals("BC", mTerminal.getSelectedText(1, 0, 2, 0));
|
||||
assertEquals("CDE", mTerminal.getSelectedText(2, 0, 4, 0));
|
||||
assertEquals("FG", mTerminal.getSelectedText(0, 1, 1, 1));
|
||||
assertEquals("GH", mTerminal.getSelectedText(1, 1, 2, 1));
|
||||
assertEquals("HIJ", mTerminal.getSelectedText(2, 1, 4, 1));
|
||||
|
||||
assertEquals("ABCDEFG", mTerminal.getSelectedText(0, 0, 1, 1));
|
||||
withTerminalSized(5, 3).enterString("ABCDE\r\nFGHIJ").assertLinesAre("ABCDE", "FGHIJ", " ");
|
||||
assertEquals("ABCDE\nFG", mTerminal.getSelectedText(0, 0, 1, 1));
|
||||
}
|
||||
|
||||
public void testGetSelectedTextJoinFullLines() {
|
||||
withTerminalSized(5, 3).enterString("ABCDE\r\nFG");
|
||||
assertEquals("ABCDEFG", mTerminal.getScreen().getSelectedText(0, 0, 1, 1, true, true));
|
||||
|
||||
withTerminalSized(5, 3).enterString("ABC\r\nFG");
|
||||
assertEquals("ABC\nFG", mTerminal.getScreen().getSelectedText(0, 0, 1, 1, true, true));
|
||||
}
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
package com.itsaky.terminal;
|
||||
|
||||
/**
|
||||
* ${CSI}${top};${bottom}r" - set Scrolling Region [top;bottom] (default = full size of window) (DECSTBM).
|
||||
* <p/>
|
||||
* "DECSTBM moves the cursor to column 1, line 1 of the page" (http://www.vt100.net/docs/vt510-rm/DECSTBM).
|
||||
*/
|
||||
public class ScrollRegionTest extends TerminalTestCase {
|
||||
|
||||
public void testScrollRegionTop() {
|
||||
withTerminalSized(3, 4).enterString("111222333444").assertLinesAre("111", "222", "333", "444");
|
||||
enterString("\033[2r").assertCursorAt(0, 0);
|
||||
enterString("\r\n\r\n\r\n\r\nCDEFGH").assertLinesAre("111", "444", "CDE", "FGH").assertHistoryStartsWith("333");
|
||||
enterString("IJK").assertLinesAre("111", "CDE", "FGH", "IJK").assertHistoryStartsWith("444");
|
||||
// Reset scroll region and enter line:
|
||||
enterString("\033[r").enterString("\r\n\r\n\r\n").enterString("LMNOPQ").assertLinesAre("CDE", "FGH", "LMN", "OPQ");
|
||||
}
|
||||
|
||||
public void testScrollRegionBottom() {
|
||||
withTerminalSized(3, 4).enterString("111222333444");
|
||||
assertLinesAre("111", "222", "333", "444");
|
||||
enterString("\033[1;3r").assertCursorAt(0, 0);
|
||||
enterString("\r\n\r\nCDEFGH").assertLinesAre("222", "CDE", "FGH", "444").assertHistoryStartsWith("111");
|
||||
// Reset scroll region and enter line:
|
||||
enterString("\033[r").enterString("\r\n\r\n\r\n").enterString("IJKLMN").assertLinesAre("CDE", "FGH", "IJK", "LMN");
|
||||
}
|
||||
|
||||
public void testScrollRegionResetWithOriginMode() {
|
||||
withTerminalSized(3, 4).enterString("111222333444");
|
||||
assertLinesAre("111", "222", "333", "444");
|
||||
// "\033[?6h" sets origin mode, so that the later DECSTBM resets cursor to below margin:
|
||||
enterString("\033[?6h\033[2r").assertCursorAt(1, 0);
|
||||
}
|
||||
|
||||
public void testScrollRegionLeft() {
|
||||
// ${CSI}?69h for DECLRMM enabling, ${CSI}${LEFTMARGIN};${RIGHTMARGIN}s for DECSLRM margin setting.
|
||||
withTerminalSized(3, 3).enterString("\033[?69h\033[2sABCDEFG").assertLinesAre("ABC", " DE", " FG");
|
||||
enterString("HI").assertLinesAre("ADE", " FG", " HI").enterString("JK").assertLinesAre("AFG", " HI", " JK");
|
||||
enterString("\n").assertLinesAre("AHI", " JK", " ");
|
||||
}
|
||||
|
||||
public void testScrollRegionRight() {
|
||||
// ${CSI}?69h for DECLRMM enabling, ${CSI}${LEFTMARGIN};${RIGHTMARGIN}s for DECSLRM margin setting.
|
||||
withTerminalSized(3, 3).enterString("YYY\033[?69h\033[1;2sABCDEF").assertLinesAre("ABY", "CD ", "EF ");
|
||||
enterString("GH").assertLinesAre("CDY", "EF ", "GH ").enterString("IJ").assertLinesAre("EFY", "GH ", "IJ ");
|
||||
enterString("\n").assertLinesAre("GHY", "IJ ", " ");
|
||||
}
|
||||
|
||||
public void testScrollRegionOnAllSides() {
|
||||
// ${CSI}?69h for DECLRMM enabling, ${CSI}${LEFTMARGIN};${RIGHTMARGIN}s for DECSLRM margin setting.
|
||||
withTerminalSized(4, 4).enterString("ABCDEFGHIJKLMNOP").assertLinesAre("ABCD", "EFGH", "IJKL", "MNOP");
|
||||
// http://www.vt100.net/docs/vt510-rm/DECOM
|
||||
enterString("\033[?6h\033[2;3r").assertCursorAt(1, 0);
|
||||
enterString("\033[?69h\033[2;3s").assertCursorAt(1, 1);
|
||||
enterString("QRST").assertLinesAre("ABCD", "EQRH", "ISTL", "MNOP");
|
||||
enterString("UV").assertLinesAre("ABCD", "ESTH", "IUVL", "MNOP");
|
||||
}
|
||||
|
||||
public void testDECCOLMResetsScrollMargin() {
|
||||
// DECCOLM — Select 80 or 132 Columns per Page (http://www.vt100.net/docs/vt510-rm/DECCOLM) has the important
|
||||
// side effect to clear scroll margins, which is useful for e.g. the "reset" utility to clear scroll margins.
|
||||
withTerminalSized(3, 4).enterString("111222333444").assertLinesAre("111", "222", "333", "444");
|
||||
enterString("\033[2r\033[?3h\r\nABCDEFGHIJKL").assertLinesAre("ABC", "DEF", "GHI", "JKL");
|
||||
}
|
||||
|
||||
public void testScrollOutsideVerticalRegion() {
|
||||
withTerminalSized(3, 4).enterString("\033[0;2rhi\033[4;0Hyou").assertLinesAre("hi ", " ", " ", "you");
|
||||
//enterString("see").assertLinesAre("hi ", " ", " ", "see");
|
||||
}
|
||||
|
||||
public void testNELRespectsLeftMargin() {
|
||||
// vttest "Menu 11.3.2: VT420 Cursor-Movement Test", select "10. Test other movement (CR/HT/LF/FF) within margins".
|
||||
// The NEL (ESC E) sequence moves cursor to first position on next line, where first position depends on origin mode and margin.
|
||||
withTerminalSized(3, 3).enterString("\033[?69h\033[2sABC\033ED").assertLinesAre("ABC", "D ", " ");
|
||||
withTerminalSized(3, 3).enterString("\033[?69h\033[2sABC\033[?6h\033ED").assertLinesAre("ABC", " D ", " ");
|
||||
}
|
||||
|
||||
public void testBackwardIndex() {
|
||||
// vttest "Menu 11.3.2: VT420 Cursor-Movement Test", test 7.
|
||||
// Without margins:
|
||||
withTerminalSized(3, 3).enterString("ABCDEF\0336H").assertLinesAre("ABC", "DHF", " ");
|
||||
enterString("\0336\0336I").assertLinesAre("ABC", "IHF", " ");
|
||||
enterString("\0336\0336").assertLinesAre(" AB", " IH", " ");
|
||||
// With left margin:
|
||||
withTerminalSized(3, 3).enterString("\033[?69h\033[2sABCDEF\0336\0336").assertLinesAre("A B", " D", " F");
|
||||
}
|
||||
|
||||
public void testForwardIndex() {
|
||||
// vttest "Menu 11.3.2: VT420 Cursor-Movement Test", test 8.
|
||||
// Without margins:
|
||||
withTerminalSized(3, 3).enterString("ABCD\0339E").assertLinesAre("ABC", "D E", " ");
|
||||
enterString("\0339").assertLinesAre("BC ", " E ", " ");
|
||||
// With right margin:
|
||||
withTerminalSized(3, 3).enterString("\033[?69h\033[0;2sABCD\0339").assertLinesAre("B ", "D ", " ");
|
||||
}
|
||||
|
||||
public void testScrollDownWithScrollRegion() {
|
||||
withTerminalSized(2, 5).enterString("1\r\n2\r\n3\r\n4\r\n5").assertLinesAre("1 ", "2 ", "3 ", "4 ", "5 ");
|
||||
enterString("\033[3r").enterString("\033[2T").assertLinesAre("1 ", "2 ", " ", " ", "3 ");
|
||||
}
|
||||
|
||||
public void testScrollDownBelowScrollRegion() {
|
||||
withTerminalSized(2, 5).enterString("1\r\n2\r\n3\r\n4\r\n5").assertLinesAre("1 ", "2 ", "3 ", "4 ", "5 ");
|
||||
enterString("\033[1;3r"); // DECSTBM margins.
|
||||
enterString("\033[4;1H"); // Place cursor just below bottom margin.
|
||||
enterString("QQ\r\nRR\r\n\r\n\r\nYY");
|
||||
assertLinesAre("1 ", "2 ", "3 ", "QQ", "YY");
|
||||
}
|
||||
|
||||
/** See https://github.com/termux/termux-app/issues/1340 */
|
||||
public void testScrollRegionDoesNotLimitCursorMovement() {
|
||||
withTerminalSized(6, 4)
|
||||
.enterString("\033[4;7r\033[3;1Haaa\033[Axxx")
|
||||
.assertLinesAre(
|
||||
" ",
|
||||
" xxx",
|
||||
"aaa ",
|
||||
" "
|
||||
);
|
||||
|
||||
withTerminalSized(6, 4)
|
||||
.enterString("\033[1;3r\033[3;1Haaa\033[Bxxx")
|
||||
.assertLinesAre(
|
||||
" ",
|
||||
" ",
|
||||
"aaa ",
|
||||
" xxx"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,432 +0,0 @@
|
||||
package com.itsaky.terminal;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
|
||||
public class TerminalRowTest extends TestCase {
|
||||
|
||||
/** The properties of these code points are validated in {@link #testStaticConstants()}. */
|
||||
private static final int ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1 = 0x679C;
|
||||
private static final int ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_2 = 0x679D;
|
||||
private static final int TWO_JAVA_CHARS_DISPLAY_WIDTH_TWO_1 = 0x2070E;
|
||||
private static final int TWO_JAVA_CHARS_DISPLAY_WIDTH_TWO_2 = 0x20731;
|
||||
|
||||
/** Unicode Character 'MUSICAL SYMBOL G CLEF' (U+1D11E). Two java chars required for this. */
|
||||
static final int TWO_JAVA_CHARS_DISPLAY_WIDTH_ONE_1 = 0x1D11E;
|
||||
/** Unicode Character 'MUSICAL SYMBOL G CLEF OTTAVA ALTA' (U+1D11F). Two java chars required for this. */
|
||||
private static final int TWO_JAVA_CHARS_DISPLAY_WIDTH_ONE_2 = 0x1D11F;
|
||||
|
||||
private final int COLUMNS = 80;
|
||||
|
||||
/** A combining character. */
|
||||
private static final int DIARESIS_CODEPOINT = 0x0308;
|
||||
|
||||
private TerminalRow row;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
row = new TerminalRow(COLUMNS, TextStyle.NORMAL);
|
||||
}
|
||||
|
||||
private void assertLineStartsWith(int... codePoints) {
|
||||
char[] chars = row.mText;
|
||||
int charIndex = 0;
|
||||
for (int i = 0; i < codePoints.length; i++) {
|
||||
int lineCodePoint = chars[charIndex++];
|
||||
if (Character.isHighSurrogate((char) lineCodePoint)) {
|
||||
lineCodePoint = Character.toCodePoint((char) lineCodePoint, chars[charIndex++]);
|
||||
}
|
||||
assertEquals("Differing a code point index=" + i, codePoints[i], lineCodePoint);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertColumnCharIndicesStartsWith(int... indices) {
|
||||
for (int i = 0; i < indices.length; i++) {
|
||||
int expected = indices[i];
|
||||
int actual = row.findStartOfColumn(i);
|
||||
assertEquals("At index=" + i, expected, actual);
|
||||
}
|
||||
}
|
||||
|
||||
public void testSimpleDiaresis() {
|
||||
row.setChar(0, DIARESIS_CODEPOINT, 0);
|
||||
assertEquals(81, row.getSpaceUsed());
|
||||
row.setChar(0, DIARESIS_CODEPOINT, 0);
|
||||
assertEquals(82, row.getSpaceUsed());
|
||||
assertLineStartsWith(' ', DIARESIS_CODEPOINT, DIARESIS_CODEPOINT, ' ');
|
||||
}
|
||||
|
||||
public void testStaticConstants() {
|
||||
assertEquals(1, Character.charCount(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1));
|
||||
assertEquals(1, Character.charCount(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_2));
|
||||
assertEquals(2, WcWidth.width(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1));
|
||||
assertEquals(2, WcWidth.width(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_2));
|
||||
|
||||
assertEquals(2, Character.charCount(TWO_JAVA_CHARS_DISPLAY_WIDTH_ONE_1));
|
||||
assertEquals(2, Character.charCount(TWO_JAVA_CHARS_DISPLAY_WIDTH_ONE_2));
|
||||
assertEquals(1, WcWidth.width(TWO_JAVA_CHARS_DISPLAY_WIDTH_ONE_1));
|
||||
assertEquals(1, WcWidth.width(TWO_JAVA_CHARS_DISPLAY_WIDTH_ONE_2));
|
||||
|
||||
assertEquals(2, Character.charCount(TWO_JAVA_CHARS_DISPLAY_WIDTH_TWO_1));
|
||||
assertEquals(2, Character.charCount(TWO_JAVA_CHARS_DISPLAY_WIDTH_TWO_2));
|
||||
assertEquals(2, WcWidth.width(TWO_JAVA_CHARS_DISPLAY_WIDTH_TWO_1));
|
||||
assertEquals(2, WcWidth.width(TWO_JAVA_CHARS_DISPLAY_WIDTH_TWO_2));
|
||||
|
||||
assertEquals(1, Character.charCount(DIARESIS_CODEPOINT));
|
||||
assertEquals(0, WcWidth.width(DIARESIS_CODEPOINT));
|
||||
}
|
||||
|
||||
public void testOneColumn() {
|
||||
assertEquals(0, row.findStartOfColumn(0));
|
||||
row.setChar(0, 'a', 0);
|
||||
assertEquals(0, row.findStartOfColumn(0));
|
||||
}
|
||||
|
||||
public void testAscii() {
|
||||
assertEquals(0, row.findStartOfColumn(0));
|
||||
row.setChar(0, 'a', 0);
|
||||
assertLineStartsWith('a', ' ', ' ');
|
||||
assertEquals(1, row.findStartOfColumn(1));
|
||||
assertEquals(80, row.getSpaceUsed());
|
||||
row.setChar(0, 'b', 0);
|
||||
assertEquals(1, row.findStartOfColumn(1));
|
||||
assertEquals(2, row.findStartOfColumn(2));
|
||||
assertEquals(80, row.getSpaceUsed());
|
||||
assertColumnCharIndicesStartsWith(0, 1, 2, 3);
|
||||
|
||||
char[] someChars = new char[]{'a', 'c', 'e', '4', '5', '6', '7', '8'};
|
||||
|
||||
char[] rawLine = new char[80];
|
||||
Arrays.fill(rawLine, ' ');
|
||||
Random random = new Random();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
int lineIndex = random.nextInt(rawLine.length);
|
||||
int charIndex = random.nextInt(someChars.length);
|
||||
rawLine[lineIndex] = someChars[charIndex];
|
||||
row.setChar(lineIndex, someChars[charIndex], 0);
|
||||
}
|
||||
char[] lineChars = row.mText;
|
||||
for (int i = 0; i < rawLine.length; i++) {
|
||||
assertEquals(rawLine[i], lineChars[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public void testUnicode() {
|
||||
assertEquals(0, row.findStartOfColumn(0));
|
||||
assertEquals(80, row.getSpaceUsed());
|
||||
|
||||
row.setChar(0, TWO_JAVA_CHARS_DISPLAY_WIDTH_ONE_1, 0);
|
||||
assertEquals(81, row.getSpaceUsed());
|
||||
assertEquals(0, row.findStartOfColumn(0));
|
||||
assertEquals(2, row.findStartOfColumn(1));
|
||||
assertLineStartsWith(TWO_JAVA_CHARS_DISPLAY_WIDTH_ONE_1, ' ', ' ');
|
||||
assertColumnCharIndicesStartsWith(0, 2, 3, 4);
|
||||
|
||||
row.setChar(0, 'a', 0);
|
||||
assertEquals(80, row.getSpaceUsed());
|
||||
assertEquals(0, row.findStartOfColumn(0));
|
||||
assertEquals(1, row.findStartOfColumn(1));
|
||||
assertLineStartsWith('a', ' ', ' ');
|
||||
assertColumnCharIndicesStartsWith(0, 1, 2, 3);
|
||||
|
||||
row.setChar(0, TWO_JAVA_CHARS_DISPLAY_WIDTH_ONE_1, 0);
|
||||
row.setChar(1, 'a', 0);
|
||||
assertLineStartsWith(TWO_JAVA_CHARS_DISPLAY_WIDTH_ONE_1, 'a', ' ');
|
||||
|
||||
row.setChar(0, TWO_JAVA_CHARS_DISPLAY_WIDTH_ONE_1, 0);
|
||||
row.setChar(1, TWO_JAVA_CHARS_DISPLAY_WIDTH_ONE_2, 0);
|
||||
assertLineStartsWith(TWO_JAVA_CHARS_DISPLAY_WIDTH_ONE_1, TWO_JAVA_CHARS_DISPLAY_WIDTH_ONE_2, ' ');
|
||||
assertColumnCharIndicesStartsWith(0, 2, 4, 5);
|
||||
assertEquals(82, row.getSpaceUsed());
|
||||
}
|
||||
|
||||
public void testDoubleWidth() {
|
||||
row.setChar(0, 'a', 0);
|
||||
row.setChar(1, ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_2, 0);
|
||||
assertLineStartsWith('a', ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_2, ' ');
|
||||
assertColumnCharIndicesStartsWith(0, 1, 1, 2);
|
||||
row.setChar(0, ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, 0);
|
||||
assertLineStartsWith(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, ' ', ' ');
|
||||
assertColumnCharIndicesStartsWith(0, 0, 1, 2);
|
||||
|
||||
row.setChar(0, ' ', 0);
|
||||
assertLineStartsWith(' ', ' ', ' ', ' ');
|
||||
assertColumnCharIndicesStartsWith(0, 1, 2, 3, 4);
|
||||
row.setChar(0, ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, 0);
|
||||
row.setChar(2, ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_2, 0);
|
||||
assertLineStartsWith(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_2);
|
||||
assertColumnCharIndicesStartsWith(0, 0, 1, 1, 2);
|
||||
row.setChar(0, 'a', 0);
|
||||
assertLineStartsWith('a', ' ', ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_2, ' ');
|
||||
}
|
||||
|
||||
/** Just as {@link #testDoubleWidth()} but requires a surrogate pair. */
|
||||
public void testDoubleWidthSurrogage() {
|
||||
row.setChar(0, 'a', 0);
|
||||
assertColumnCharIndicesStartsWith(0, 1, 2, 3, 4);
|
||||
|
||||
row.setChar(1, TWO_JAVA_CHARS_DISPLAY_WIDTH_TWO_2, 0);
|
||||
assertColumnCharIndicesStartsWith(0, 1, 1, 3, 4);
|
||||
assertLineStartsWith('a', TWO_JAVA_CHARS_DISPLAY_WIDTH_TWO_2, ' ');
|
||||
row.setChar(0, TWO_JAVA_CHARS_DISPLAY_WIDTH_TWO_1, 0);
|
||||
assertColumnCharIndicesStartsWith(0, 0, 2, 3, 4);
|
||||
assertLineStartsWith(TWO_JAVA_CHARS_DISPLAY_WIDTH_TWO_1, ' ', ' ', ' ');
|
||||
|
||||
row.setChar(0, ' ', 0);
|
||||
assertLineStartsWith(' ', ' ', ' ', ' ');
|
||||
row.setChar(0, TWO_JAVA_CHARS_DISPLAY_WIDTH_TWO_1, 0);
|
||||
row.setChar(1, TWO_JAVA_CHARS_DISPLAY_WIDTH_TWO_2, 0);
|
||||
assertLineStartsWith(' ', TWO_JAVA_CHARS_DISPLAY_WIDTH_TWO_2, ' ');
|
||||
row.setChar(0, 'a', 0);
|
||||
assertLineStartsWith('a', TWO_JAVA_CHARS_DISPLAY_WIDTH_TWO_2, ' ');
|
||||
}
|
||||
|
||||
public void testReplacementChar() {
|
||||
row.setChar(0, TerminalEmulator.UNICODE_REPLACEMENT_CHAR, 0);
|
||||
row.setChar(1, 'Y', 0);
|
||||
assertLineStartsWith(TerminalEmulator.UNICODE_REPLACEMENT_CHAR, 'Y', ' ', ' ');
|
||||
}
|
||||
|
||||
public void testSurrogateCharsWithNormalDisplayWidth() {
|
||||
// These requires a UTF-16 surrogate pair, and has a display width of one.
|
||||
int first = 0x1D306;
|
||||
int second = 0x1D307;
|
||||
// Assert the above statement:
|
||||
assertEquals(2, Character.toChars(first).length);
|
||||
assertEquals(2, Character.toChars(second).length);
|
||||
|
||||
row.setChar(0, second, 0);
|
||||
assertEquals(second, Character.toCodePoint(row.mText[0], row.mText[1]));
|
||||
assertEquals(' ', row.mText[2]);
|
||||
assertEquals(2, row.findStartOfColumn(1));
|
||||
|
||||
row.setChar(0, first, 0);
|
||||
assertEquals(first, Character.toCodePoint(row.mText[0], row.mText[1]));
|
||||
assertEquals(' ', row.mText[2]);
|
||||
assertEquals(2, row.findStartOfColumn(1));
|
||||
|
||||
row.setChar(1, second, 0);
|
||||
row.setChar(2, 'a', 0);
|
||||
assertEquals(first, Character.toCodePoint(row.mText[0], row.mText[1]));
|
||||
assertEquals(second, Character.toCodePoint(row.mText[2], row.mText[3]));
|
||||
assertEquals('a', row.mText[4]);
|
||||
assertEquals(' ', row.mText[5]);
|
||||
assertEquals(0, row.findStartOfColumn(0));
|
||||
assertEquals(2, row.findStartOfColumn(1));
|
||||
assertEquals(4, row.findStartOfColumn(2));
|
||||
assertEquals(5, row.findStartOfColumn(3));
|
||||
assertEquals(6, row.findStartOfColumn(4));
|
||||
|
||||
row.setChar(0, ' ', 0);
|
||||
assertEquals(' ', row.mText[0]);
|
||||
assertEquals(second, Character.toCodePoint(row.mText[1], row.mText[2]));
|
||||
assertEquals('a', row.mText[3]);
|
||||
assertEquals(' ', row.mText[4]);
|
||||
assertEquals(0, row.findStartOfColumn(0));
|
||||
assertEquals(1, row.findStartOfColumn(1));
|
||||
assertEquals(3, row.findStartOfColumn(2));
|
||||
assertEquals(4, row.findStartOfColumn(3));
|
||||
assertEquals(5, row.findStartOfColumn(4));
|
||||
|
||||
for (int i = 0; i < 80; i++) {
|
||||
row.setChar(i, i % 2 == 0 ? first : second, 0);
|
||||
}
|
||||
for (int i = 0; i < 80; i++) {
|
||||
int idx = row.findStartOfColumn(i);
|
||||
assertEquals(i % 2 == 0 ? first : second, Character.toCodePoint(row.mText[idx], row.mText[idx + 1]));
|
||||
}
|
||||
for (int i = 0; i < 80; i++) {
|
||||
row.setChar(i, i % 2 == 0 ? 'a' : 'b', 0);
|
||||
}
|
||||
for (int i = 0; i < 80; i++) {
|
||||
int idx = row.findStartOfColumn(i);
|
||||
assertEquals(i, idx);
|
||||
assertEquals(i % 2 == 0 ? 'a' : 'b', row.mText[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public void testOverwritingDoubleDisplayWidthWithNormalDisplayWidth() {
|
||||
// Initial "OO "
|
||||
row.setChar(0, ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, 0);
|
||||
assertEquals(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, row.mText[0]);
|
||||
assertEquals(' ', row.mText[1]);
|
||||
assertEquals(0, row.findStartOfColumn(0));
|
||||
assertEquals(0, row.findStartOfColumn(1));
|
||||
assertEquals(1, row.findStartOfColumn(2));
|
||||
|
||||
// Setting first column to a clears second: "a "
|
||||
row.setChar(0, 'a', 0);
|
||||
assertEquals('a', row.mText[0]);
|
||||
assertEquals(' ', row.mText[1]);
|
||||
assertEquals(0, row.findStartOfColumn(0));
|
||||
assertEquals(1, row.findStartOfColumn(1));
|
||||
assertEquals(2, row.findStartOfColumn(2));
|
||||
|
||||
// Back to initial "OO "
|
||||
row.setChar(0, ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, 0);
|
||||
assertEquals(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, row.mText[0]);
|
||||
assertEquals(' ', row.mText[1]);
|
||||
assertEquals(0, row.findStartOfColumn(0));
|
||||
assertEquals(0, row.findStartOfColumn(1));
|
||||
assertEquals(1, row.findStartOfColumn(2));
|
||||
|
||||
// Setting first column to a clears first: " a "
|
||||
row.setChar(1, 'a', 0);
|
||||
assertEquals(' ', row.mText[0]);
|
||||
assertEquals('a', row.mText[1]);
|
||||
assertEquals(' ', row.mText[2]);
|
||||
assertEquals(0, row.findStartOfColumn(0));
|
||||
assertEquals(1, row.findStartOfColumn(1));
|
||||
assertEquals(2, row.findStartOfColumn(2));
|
||||
}
|
||||
|
||||
public void testOverwritingDoubleDisplayWidthWithSelf() {
|
||||
row.setChar(0, ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, 0);
|
||||
row.setChar(0, ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, 0);
|
||||
assertEquals(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, row.mText[0]);
|
||||
assertEquals(' ', row.mText[1]);
|
||||
assertEquals(0, row.findStartOfColumn(0));
|
||||
assertEquals(0, row.findStartOfColumn(1));
|
||||
assertEquals(1, row.findStartOfColumn(2));
|
||||
}
|
||||
|
||||
public void testNormalCharsWithDoubleDisplayWidth() {
|
||||
// These fit in one java char, and has a display width of two.
|
||||
assertTrue(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1 != ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_2);
|
||||
assertEquals(1, Character.charCount(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1));
|
||||
assertEquals(1, Character.charCount(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_2));
|
||||
assertEquals(2, WcWidth.width(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1));
|
||||
assertEquals(2, WcWidth.width(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_2));
|
||||
|
||||
row.setChar(0, ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, 0);
|
||||
assertEquals(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, row.mText[0]);
|
||||
assertEquals(0, row.findStartOfColumn(1));
|
||||
assertEquals(' ', row.mText[1]);
|
||||
|
||||
row.setChar(0, 'a', 0);
|
||||
assertEquals('a', row.mText[0]);
|
||||
assertEquals(' ', row.mText[1]);
|
||||
assertEquals(1, row.findStartOfColumn(1));
|
||||
|
||||
row.setChar(0, ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, 0);
|
||||
assertEquals(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, row.mText[0]);
|
||||
// The first character fills both first columns.
|
||||
assertEquals(0, row.findStartOfColumn(1));
|
||||
row.setChar(2, 'a', 0);
|
||||
assertEquals(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, row.mText[0]);
|
||||
assertEquals('a', row.mText[1]);
|
||||
assertEquals(1, row.findStartOfColumn(2));
|
||||
|
||||
row.setChar(0, 'c', 0);
|
||||
assertEquals('c', row.mText[0]);
|
||||
assertEquals(' ', row.mText[1]);
|
||||
assertEquals('a', row.mText[2]);
|
||||
assertEquals(' ', row.mText[3]);
|
||||
assertEquals(0, row.findStartOfColumn(0));
|
||||
assertEquals(1, row.findStartOfColumn(1));
|
||||
assertEquals(2, row.findStartOfColumn(2));
|
||||
}
|
||||
|
||||
public void testNormalCharsWithDoubleDisplayWidthOverlapping() {
|
||||
// These fit in one java char, and has a display width of two.
|
||||
row.setChar(0, ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, 0);
|
||||
row.setChar(2, ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_2, 0);
|
||||
row.setChar(4, 'a', 0);
|
||||
// O = ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO
|
||||
// A = ANOTHER_JAVA_CHAR_DISPLAY_WIDTH_TWO
|
||||
// "OOAAa "
|
||||
assertEquals(0, row.findStartOfColumn(0));
|
||||
assertEquals(0, row.findStartOfColumn(1));
|
||||
assertEquals(1, row.findStartOfColumn(2));
|
||||
assertEquals(1, row.findStartOfColumn(3));
|
||||
assertEquals(2, row.findStartOfColumn(4));
|
||||
assertEquals(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1, row.mText[0]);
|
||||
assertEquals(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_2, row.mText[1]);
|
||||
assertEquals('a', row.mText[2]);
|
||||
assertEquals(' ', row.mText[3]);
|
||||
|
||||
row.setChar(1, ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_2, 0);
|
||||
// " AA a "
|
||||
assertEquals(' ', row.mText[0]);
|
||||
assertEquals(ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_2, row.mText[1]);
|
||||
assertEquals(' ', row.mText[2]);
|
||||
assertEquals('a', row.mText[3]);
|
||||
assertEquals(' ', row.mText[4]);
|
||||
assertEquals(0, row.findStartOfColumn(0));
|
||||
assertEquals(1, row.findStartOfColumn(1));
|
||||
assertEquals(1, row.findStartOfColumn(2));
|
||||
assertEquals(2, row.findStartOfColumn(3));
|
||||
assertEquals(3, row.findStartOfColumn(4));
|
||||
}
|
||||
|
||||
// https://github.com/jackpal/Android-Terminal-Emulator/issues/145
|
||||
public void testCrashATE145() {
|
||||
// 0xC2541 is unassigned, use display width 1 for UNICODE_REPLACEMENT_CHAR.
|
||||
// assertEquals(1, WcWidth.width(0xC2541));
|
||||
assertEquals(2, Character.charCount(0xC2541));
|
||||
|
||||
assertEquals(2, WcWidth.width(0x73EE));
|
||||
assertEquals(1, Character.charCount(0x73EE));
|
||||
|
||||
assertEquals(0, WcWidth.width(0x009F));
|
||||
assertEquals(1, Character.charCount(0x009F));
|
||||
|
||||
int[] points = new int[]{0xC2541, 'a', '8', 0x73EE, 0x009F, 0x881F, 0x8324, 0xD4C9, 0xFFFD, 'B', 0x009B, 0x61C9, 'Z'};
|
||||
// int[] expected = new int[] { TerminalEmulator.UNICODE_REPLACEMENT_CHAR, 'a', '8', 0x73EE, 0x009F, 0x881F, 0x8324, 0xD4C9, 0xFFFD,
|
||||
// 'B', 0x009B, 0x61C9, 'Z' };
|
||||
int currentColumn = 0;
|
||||
for (int point : points) {
|
||||
row.setChar(currentColumn, point, 0);
|
||||
currentColumn += WcWidth.width(point);
|
||||
}
|
||||
// assertLineStartsWith(points);
|
||||
// assertEquals(Character.highSurrogate(0xC2541), line.mText[0]);
|
||||
// assertEquals(Character.lowSurrogate(0xC2541), line.mText[1]);
|
||||
// assertEquals('a', line.mText[2]);
|
||||
// assertEquals('8', line.mText[3]);
|
||||
// assertEquals(Character.highSurrogate(0x73EE), line.mText[4]);
|
||||
// assertEquals(Character.lowSurrogate(0x73EE), line.mText[5]);
|
||||
//
|
||||
// char[] chars = line.mText;
|
||||
// int charIndex = 0;
|
||||
// for (int i = 0; i < points.length; i++) {
|
||||
// char c = chars[charIndex];
|
||||
// charIndex++;
|
||||
// int thisPoint = (int) c;
|
||||
// if (Character.isHighSurrogate(c)) {
|
||||
// thisPoint = Character.toCodePoint(c, chars[charIndex]);
|
||||
// charIndex++;
|
||||
// }
|
||||
// assertEquals("At index=" + i + ", charIndex=" + charIndex + ", char=" + (char) thisPoint, points[i], thisPoint);
|
||||
// }
|
||||
}
|
||||
|
||||
public void testNormalization() {
|
||||
// int lowerCaseN = 0x006E;
|
||||
// int combiningTilde = 0x0303;
|
||||
// int combined = 0x00F1;
|
||||
row.setChar(0, 0x006E, 0);
|
||||
assertEquals(80, row.getSpaceUsed());
|
||||
row.setChar(0, 0x0303, 0);
|
||||
assertEquals(81, row.getSpaceUsed());
|
||||
// assertEquals("\u00F1 ", new String(term.getScreen().getLine(0)));
|
||||
assertLineStartsWith(0x006E, 0x0303, ' ');
|
||||
}
|
||||
|
||||
public void testInsertWideAtLastColumn() {
|
||||
row.setChar(COLUMNS - 2, 'Z', 0);
|
||||
row.setChar(COLUMNS - 1, 'a', 0);
|
||||
assertEquals('Z', row.mText[row.findStartOfColumn(COLUMNS - 2)]);
|
||||
assertEquals('a', row.mText[row.findStartOfColumn(COLUMNS - 1)]);
|
||||
row.setChar(COLUMNS - 1, 'ö', 0);
|
||||
assertEquals('Z', row.mText[row.findStartOfColumn(COLUMNS - 2)]);
|
||||
assertEquals('ö', row.mText[row.findStartOfColumn(COLUMNS - 1)]);
|
||||
// line.setChar(COLUMNS - 1, ONE_JAVA_CHAR_DISPLAY_WIDTH_TWO_1);
|
||||
// assertEquals('Z', line.mText[line.findStartOfColumn(COLUMNS - 2)]);
|
||||
// assertEquals(' ', line.mText[line.findStartOfColumn(COLUMNS - 1)]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,297 +0,0 @@
|
||||
package com.itsaky.terminal;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
public class TerminalTest extends TerminalTestCase {
|
||||
|
||||
public void testCursorPositioning() throws Exception {
|
||||
withTerminalSized(10, 10).placeCursorAndAssert(1, 2).placeCursorAndAssert(3, 5).placeCursorAndAssert(2, 2).enterString("A")
|
||||
.assertCursorAt(2, 3);
|
||||
}
|
||||
|
||||
public void testScreen() throws UnsupportedEncodingException {
|
||||
withTerminalSized(3, 3);
|
||||
assertLinesAre(" ", " ", " ");
|
||||
|
||||
assertEquals("", mTerminal.getScreen().getTranscriptText());
|
||||
enterString("hi").assertLinesAre("hi ", " ", " ");
|
||||
assertEquals("hi", mTerminal.getScreen().getTranscriptText());
|
||||
enterString("\r\nu");
|
||||
assertEquals("hi\nu", mTerminal.getScreen().getTranscriptText());
|
||||
mTerminal.reset();
|
||||
assertEquals("hi\nu", mTerminal.getScreen().getTranscriptText());
|
||||
|
||||
withTerminalSized(3, 3).enterString("hello");
|
||||
assertEquals("hello", mTerminal.getScreen().getTranscriptText());
|
||||
enterString("\r\nworld");
|
||||
assertEquals("hello\nworld", mTerminal.getScreen().getTranscriptText());
|
||||
}
|
||||
|
||||
public void testScrollDownInAltBuffer() {
|
||||
withTerminalSized(3, 3).enterString("\033[?1049h");
|
||||
enterString("\033[38;5;111m1\r\n");
|
||||
enterString("\033[38;5;112m2\r\n");
|
||||
enterString("\033[38;5;113m3\r\n");
|
||||
enterString("\033[38;5;114m4\r\n");
|
||||
enterString("\033[38;5;115m5");
|
||||
assertLinesAre("3 ", "4 ", "5 ");
|
||||
assertForegroundColorAt(0, 0, 113);
|
||||
assertForegroundColorAt(1, 0, 114);
|
||||
assertForegroundColorAt(2, 0, 115);
|
||||
}
|
||||
|
||||
public void testMouseClick() throws Exception {
|
||||
withTerminalSized(10, 10);
|
||||
assertFalse(mTerminal.isMouseTrackingActive());
|
||||
enterString("\033[?1000h");
|
||||
assertTrue(mTerminal.isMouseTrackingActive());
|
||||
enterString("\033[?1000l");
|
||||
assertFalse(mTerminal.isMouseTrackingActive());
|
||||
enterString("\033[?1000h");
|
||||
assertTrue(mTerminal.isMouseTrackingActive());
|
||||
|
||||
enterString("\033[?1006h");
|
||||
mTerminal.sendMouseEvent(TerminalEmulator.MOUSE_LEFT_BUTTON, 3, 4, true);
|
||||
assertEquals("\033[<0;3;4M", mOutput.getOutputAndClear());
|
||||
mTerminal.sendMouseEvent(TerminalEmulator.MOUSE_LEFT_BUTTON, 3, 4, false);
|
||||
assertEquals("\033[<0;3;4m", mOutput.getOutputAndClear());
|
||||
|
||||
// When the client says that a click is outside (which could happen when pixels are outside
|
||||
// the terminal area, see https://github.com/termux/termux-app/issues/501) the terminal
|
||||
// sends a click at the edge.
|
||||
mTerminal.sendMouseEvent(TerminalEmulator.MOUSE_LEFT_BUTTON, 0, 0, true);
|
||||
assertEquals("\033[<0;1;1M", mOutput.getOutputAndClear());
|
||||
mTerminal.sendMouseEvent(TerminalEmulator.MOUSE_LEFT_BUTTON, 11, 11, false);
|
||||
assertEquals("\033[<0;10;10m", mOutput.getOutputAndClear());
|
||||
}
|
||||
|
||||
public void testNormalization() throws UnsupportedEncodingException {
|
||||
// int lowerCaseN = 0x006E;
|
||||
// int combiningTilde = 0x0303;
|
||||
// int combined = 0x00F1;
|
||||
withTerminalSized(3, 3).assertLinesAre(" ", " ", " ");
|
||||
enterString("\u006E\u0303");
|
||||
assertEquals(1, WcWidth.width("\u006E\u0303".toCharArray(), 0));
|
||||
// assertEquals("\u00F1 ", new String(mTerminal.getScreen().getLine(0)));
|
||||
assertLinesAre("\u006E\u0303 ", " ", " ");
|
||||
}
|
||||
|
||||
/** On "\e[18t" xterm replies with "\e[8;${HEIGHT};${WIDTH}t" */
|
||||
public void testReportTerminalSize() throws Exception {
|
||||
withTerminalSized(5, 5);
|
||||
assertEnteringStringGivesResponse("\033[18t", "\033[8;5;5t");
|
||||
for (int width = 3; width < 12; width++) {
|
||||
for (int height = 3; height < 12; height++) {
|
||||
mTerminal.resize(width, height);
|
||||
assertEnteringStringGivesResponse("\033[18t", "\033[8;" + height + ";" + width + "t");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Device Status Report (DSR) and Report Cursor Position (CPR). */
|
||||
public void testDeviceStatusReport() throws Exception {
|
||||
withTerminalSized(5, 5);
|
||||
assertEnteringStringGivesResponse("\033[5n", "\033[0n");
|
||||
|
||||
assertEnteringStringGivesResponse("\033[6n", "\033[1;1R");
|
||||
enterString("AB");
|
||||
assertEnteringStringGivesResponse("\033[6n", "\033[1;3R");
|
||||
enterString("\r\n");
|
||||
assertEnteringStringGivesResponse("\033[6n", "\033[2;1R");
|
||||
}
|
||||
|
||||
/** Test the cursor shape changes using DECSCUSR. */
|
||||
public void testSetCursorStyle() throws Exception {
|
||||
withTerminalSized(5, 5);
|
||||
assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_BLOCK, mTerminal.getCursorStyle());
|
||||
enterString("\033[3 q");
|
||||
assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_UNDERLINE, mTerminal.getCursorStyle());
|
||||
enterString("\033[5 q");
|
||||
assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_BAR, mTerminal.getCursorStyle());
|
||||
enterString("\033[0 q");
|
||||
assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_BLOCK, mTerminal.getCursorStyle());
|
||||
enterString("\033[6 q");
|
||||
assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_BAR, mTerminal.getCursorStyle());
|
||||
enterString("\033[4 q");
|
||||
assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_UNDERLINE, mTerminal.getCursorStyle());
|
||||
enterString("\033[1 q");
|
||||
assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_BLOCK, mTerminal.getCursorStyle());
|
||||
enterString("\033[4 q");
|
||||
assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_UNDERLINE, mTerminal.getCursorStyle());
|
||||
enterString("\033[2 q");
|
||||
assertEquals(TerminalEmulator.TERMINAL_CURSOR_STYLE_BLOCK, mTerminal.getCursorStyle());
|
||||
}
|
||||
|
||||
public void testPaste() {
|
||||
withTerminalSized(5, 5);
|
||||
mTerminal.paste("hi");
|
||||
assertEquals("hi", mOutput.getOutputAndClear());
|
||||
|
||||
enterString("\033[?2004h");
|
||||
mTerminal.paste("hi");
|
||||
assertEquals("\033[200~" + "hi" + "\033[201~", mOutput.getOutputAndClear());
|
||||
|
||||
enterString("\033[?2004l");
|
||||
mTerminal.paste("hi");
|
||||
assertEquals("hi", mOutput.getOutputAndClear());
|
||||
}
|
||||
|
||||
public void testSelectGraphics() {
|
||||
withTerminalSized(5, 5);
|
||||
enterString("\033[31m");
|
||||
assertEquals(mTerminal.mForeColor, 1);
|
||||
enterString("\033[32m");
|
||||
assertEquals(mTerminal.mForeColor, 2);
|
||||
enterString("\033[43m");
|
||||
assertEquals(2, mTerminal.mForeColor);
|
||||
assertEquals(3, mTerminal.mBackColor);
|
||||
|
||||
// SGR 0 should reset both foreground and background color.
|
||||
enterString("\033[0m");
|
||||
assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor);
|
||||
assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor);
|
||||
|
||||
// 256 colors:
|
||||
enterString("\033[38;5;119m");
|
||||
assertEquals(119, mTerminal.mForeColor);
|
||||
assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor);
|
||||
enterString("\033[48;5;129m");
|
||||
assertEquals(119, mTerminal.mForeColor);
|
||||
assertEquals(129, mTerminal.mBackColor);
|
||||
|
||||
// Invalid parameter:
|
||||
enterString("\033[48;8;129m");
|
||||
assertEquals(119, mTerminal.mForeColor);
|
||||
assertEquals(129, mTerminal.mBackColor);
|
||||
|
||||
// Multiple parameters at once:
|
||||
enterString("\033[38;5;178;48;5;179;m");
|
||||
assertEquals(178, mTerminal.mForeColor);
|
||||
assertEquals(179, mTerminal.mBackColor);
|
||||
|
||||
// 24 bit colors:
|
||||
enterString(("\033[0m")); // Reset fg and bg colors.
|
||||
enterString("\033[38;2;255;127;2m");
|
||||
int expectedForeground = 0xff000000 | (255 << 16) | (127 << 8) | 2;
|
||||
assertEquals(expectedForeground, mTerminal.mForeColor);
|
||||
assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor);
|
||||
enterString("\033[48;2;1;2;254m");
|
||||
int expectedBackground = 0xff000000 | (1 << 16) | (2 << 8) | 254;
|
||||
assertEquals(expectedForeground, mTerminal.mForeColor);
|
||||
assertEquals(expectedBackground, mTerminal.mBackColor);
|
||||
|
||||
// 24 bit colors, set fg and bg at once:
|
||||
enterString(("\033[0m")); // Reset fg and bg colors.
|
||||
assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, mTerminal.mForeColor);
|
||||
assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, mTerminal.mBackColor);
|
||||
enterString("\033[38;2;255;127;2;48;2;1;2;254m");
|
||||
assertEquals(expectedForeground, mTerminal.mForeColor);
|
||||
assertEquals(expectedBackground, mTerminal.mBackColor);
|
||||
|
||||
// 24 bit colors, invalid input:
|
||||
enterString("\033[38;2;300;127;2;48;2;1;300;254m");
|
||||
assertEquals(expectedForeground, mTerminal.mForeColor);
|
||||
assertEquals(expectedBackground, mTerminal.mBackColor);
|
||||
}
|
||||
|
||||
public void testBackgroundColorErase() {
|
||||
final int rows = 3;
|
||||
final int cols = 3;
|
||||
withTerminalSized(cols, rows);
|
||||
for (int r = 0; r < rows; r++) {
|
||||
for (int c = 0; c < cols; c++) {
|
||||
long style = getStyleAt(r, c);
|
||||
assertEquals(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.decodeForeColor(style));
|
||||
assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, TextStyle.decodeBackColor(style));
|
||||
}
|
||||
}
|
||||
// Foreground color to 119:
|
||||
enterString("\033[38;5;119m");
|
||||
// Background color to 129:
|
||||
enterString("\033[48;5;129m");
|
||||
// Clear with ED, Erase in Display:
|
||||
enterString("\033[2J");
|
||||
for (int r = 0; r < rows; r++) {
|
||||
for (int c = 0; c < cols; c++) {
|
||||
long style = getStyleAt(r, c);
|
||||
assertEquals(119, TextStyle.decodeForeColor(style));
|
||||
assertEquals(129, TextStyle.decodeBackColor(style));
|
||||
}
|
||||
}
|
||||
// Background color to 139:
|
||||
enterString("\033[48;5;139m");
|
||||
// Insert two blank lines.
|
||||
enterString("\033[2L");
|
||||
for (int r = 0; r < rows; r++) {
|
||||
for (int c = 0; c < cols; c++) {
|
||||
long style = getStyleAt(r, c);
|
||||
assertEquals((r == 0 || r == 1) ? 139 : 129, TextStyle.decodeBackColor(style));
|
||||
}
|
||||
}
|
||||
|
||||
withTerminalSized(cols, rows);
|
||||
// Background color to 129:
|
||||
enterString("\033[48;5;129m");
|
||||
// Erase two characters, filling them with background color:
|
||||
enterString("\033[2X");
|
||||
assertEquals(129, TextStyle.decodeBackColor(getStyleAt(0, 0)));
|
||||
assertEquals(129, TextStyle.decodeBackColor(getStyleAt(0, 1)));
|
||||
assertEquals(TextStyle.COLOR_INDEX_BACKGROUND, TextStyle.decodeBackColor(getStyleAt(0, 2)));
|
||||
}
|
||||
|
||||
public void testParseColor() {
|
||||
assertEquals(0xFF0000FA, TerminalColors.parse("#0000FA"));
|
||||
assertEquals(0xFF000000, TerminalColors.parse("#000000"));
|
||||
assertEquals(0xFF000000, TerminalColors.parse("#000"));
|
||||
assertEquals(0xFF000000, TerminalColors.parse("#000000000"));
|
||||
assertEquals(0xFF53186f, TerminalColors.parse("#53186f"));
|
||||
|
||||
assertEquals(0xFFFF00FF, TerminalColors.parse("rgb:F/0/F"));
|
||||
assertEquals(0xFF0000FA, TerminalColors.parse("rgb:00/00/FA"));
|
||||
assertEquals(0xFF53186f, TerminalColors.parse("rgb:53/18/6f"));
|
||||
|
||||
assertEquals(0, TerminalColors.parse("invalid_0000FA"));
|
||||
assertEquals(0, TerminalColors.parse("#3456"));
|
||||
}
|
||||
|
||||
/** The ncurses library still uses this. */
|
||||
public void testLineDrawing() {
|
||||
// 016 - shift out / G1. 017 - shift in / G0. "ESC ) 0" - use line drawing for G1
|
||||
withTerminalSized(4, 2).enterString("q\033)0q\016q\017q").assertLinesAre("qq─q", " ");
|
||||
// "\0337", saving cursor should save G0, G1 and invoked charset and "ESC 8" should restore.
|
||||
withTerminalSized(4, 2).enterString("\033)0\016qqq\0337\017\0338q").assertLinesAre("────", " ");
|
||||
}
|
||||
|
||||
public void testSoftTerminalReset() {
|
||||
// See http://vt100.net/docs/vt510-rm/DECSTR and https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=650304
|
||||
// "\033[?7l" is DECRST to disable wrap-around, and DECSTR ("\033[!p") should reset it.
|
||||
withTerminalSized(3, 3).enterString("\033[?7lABCD").assertLinesAre("ABD", " ", " ");
|
||||
enterString("\033[!pEF").assertLinesAre("ABE", "F ", " ");
|
||||
}
|
||||
|
||||
public void testBel() {
|
||||
withTerminalSized(3, 3);
|
||||
assertEquals(0, mOutput.bellsRung);
|
||||
enterString("\07");
|
||||
assertEquals(1, mOutput.bellsRung);
|
||||
enterString("hello\07");
|
||||
assertEquals(2, mOutput.bellsRung);
|
||||
enterString("\07hello");
|
||||
assertEquals(3, mOutput.bellsRung);
|
||||
enterString("hello\07world");
|
||||
assertEquals(4, mOutput.bellsRung);
|
||||
}
|
||||
|
||||
public void testAutomargins() throws UnsupportedEncodingException {
|
||||
withTerminalSized(3, 3).enterString("abc").assertLinesAre("abc", " ", " ").assertCursorAt(0, 2);
|
||||
enterString("d").assertLinesAre("abc", "d ", " ").assertCursorAt(1, 1);
|
||||
|
||||
withTerminalSized(3, 3).enterString("abc\r ").assertLinesAre(" bc", " ", " ").assertCursorAt(0, 1);
|
||||
}
|
||||
|
||||
public void testTab() {
|
||||
withTerminalSized(11, 2).enterString("01234567890\r\tXX").assertLinesAre("01234567XX0", " ");
|
||||
withTerminalSized(11, 2).enterString("01234567890\033[44m\r\tXX").assertLinesAre("01234567XX0", " ");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,311 +0,0 @@
|
||||
package com.itsaky.terminal;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
public abstract class TerminalTestCase extends TestCase {
|
||||
|
||||
public static class MockTerminalOutput extends TerminalOutput {
|
||||
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
public final List<ChangedTitle> titleChanges = new ArrayList<>();
|
||||
public final List<String> clipboardPuts = new ArrayList<>();
|
||||
public int bellsRung = 0;
|
||||
public int colorsChanged = 0;
|
||||
|
||||
@Override
|
||||
public void write(byte[] data, int offset, int count) {
|
||||
baos.write(data, offset, count);
|
||||
}
|
||||
|
||||
public String getOutputAndClear() {
|
||||
String result = new String(baos.toByteArray(), StandardCharsets.UTF_8);
|
||||
baos.reset();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void titleChanged(String oldTitle, String newTitle) {
|
||||
titleChanges.add(new ChangedTitle(oldTitle, newTitle));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCopyTextToClipboard(String text) {
|
||||
clipboardPuts.add(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPasteTextFromClipboard() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBell() {
|
||||
bellsRung++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onColorsChanged() {
|
||||
colorsChanged++;
|
||||
}
|
||||
}
|
||||
|
||||
public TerminalEmulator mTerminal;
|
||||
public MockTerminalOutput mOutput;
|
||||
|
||||
public static final class ChangedTitle {
|
||||
final String oldTitle;
|
||||
final String newTitle;
|
||||
|
||||
public ChangedTitle(String oldTitle, String newTitle) {
|
||||
this.oldTitle = oldTitle;
|
||||
this.newTitle = newTitle;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof ChangedTitle)) return false;
|
||||
ChangedTitle other = (ChangedTitle) o;
|
||||
return Objects.equals(oldTitle, other.oldTitle) && Objects.equals(newTitle, other.newTitle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(oldTitle, newTitle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ChangedTitle[oldTitle=" + oldTitle + ", newTitle=" + newTitle + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public TerminalTestCase enterString(String s) {
|
||||
byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
|
||||
mTerminal.append(bytes, bytes.length);
|
||||
assertInvariants();
|
||||
return this;
|
||||
}
|
||||
|
||||
public void assertEnteringStringGivesResponse(String input, String expectedResponse) {
|
||||
enterString(input);
|
||||
String response = mOutput.getOutputAndClear();
|
||||
assertEquals(expectedResponse, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
mOutput = new MockTerminalOutput();
|
||||
}
|
||||
|
||||
protected TerminalTestCase withTerminalSized(int columns, int rows) {
|
||||
// The tests aren't currently using the client, so a null client will suffice, a dummy client should be implemented if needed
|
||||
mTerminal = new TerminalEmulator(mOutput, columns, rows, rows * 2, null);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void assertHistoryStartsWith(String... rows) {
|
||||
assertTrue("About to check " + rows.length + " lines, but only " + mTerminal.getScreen().getActiveTranscriptRows() + " in history",
|
||||
mTerminal.getScreen().getActiveTranscriptRows() >= rows.length);
|
||||
for (int i = 0; i < rows.length; i++) {
|
||||
assertLineIs(-i - 1, rows[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class LineWrapper {
|
||||
final TerminalRow mLine;
|
||||
|
||||
public LineWrapper(TerminalRow line) {
|
||||
mLine = line;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return System.identityHashCode(mLine);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
return o instanceof LineWrapper && ((LineWrapper) o).mLine == mLine;
|
||||
}
|
||||
}
|
||||
|
||||
protected TerminalTestCase assertInvariants() {
|
||||
TerminalBuffer screen = mTerminal.getScreen();
|
||||
TerminalRow[] lines = screen.mLines;
|
||||
|
||||
Set<LineWrapper> linesSet = new HashSet<>();
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
if (lines[i] == null) continue;
|
||||
assertTrue("Line exists at multiple places: " + i, linesSet.add(new LineWrapper(lines[i])));
|
||||
char[] text = lines[i].mText;
|
||||
int usedChars = lines[i].getSpaceUsed();
|
||||
int currentColumn = 0;
|
||||
for (int j = 0; j < usedChars; j++) {
|
||||
char c = text[j];
|
||||
int codePoint;
|
||||
if (Character.isHighSurrogate(c)) {
|
||||
char lowSurrogate = text[++j];
|
||||
assertTrue("High surrogate without following low surrogate", Character.isLowSurrogate(lowSurrogate));
|
||||
codePoint = Character.toCodePoint(c, lowSurrogate);
|
||||
} else {
|
||||
assertFalse("Low surrogate without preceding high surrogate", Character.isLowSurrogate(c));
|
||||
codePoint = c;
|
||||
}
|
||||
assertFalse("Screen should never contain unassigned characters", Character.getType(codePoint) == Character.UNASSIGNED);
|
||||
int width = WcWidth.width(codePoint);
|
||||
assertFalse("The first column should not start with combining character", currentColumn == 0 && width < 0);
|
||||
if (width > 0) currentColumn += width;
|
||||
}
|
||||
assertEquals("Line whose width does not match screens. line=" + new String(lines[i].mText, 0, lines[i].getSpaceUsed()),
|
||||
screen.mColumns, currentColumn);
|
||||
}
|
||||
|
||||
assertEquals("The alt buffer should have have no history", mTerminal.mAltBuffer.mTotalRows, mTerminal.mAltBuffer.mScreenRows);
|
||||
if (mTerminal.isAlternateBufferActive()) {
|
||||
assertEquals("The alt buffer should be the same size as the screen", mTerminal.mRows, mTerminal.mAltBuffer.mTotalRows);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
protected void assertLineIs(int line, String expected) {
|
||||
TerminalRow l = mTerminal.getScreen().allocateFullLineIfNecessary(mTerminal.getScreen().externalToInternalRow(line));
|
||||
char[] chars = l.mText;
|
||||
int textLen = l.getSpaceUsed();
|
||||
if (textLen != expected.length()) fail("Expected '" + expected + "' (len=" + expected.length() + "), was='"
|
||||
+ new String(chars, 0, textLen) + "' (len=" + textLen + ")");
|
||||
for (int i = 0; i < textLen; i++) {
|
||||
if (expected.charAt(i) != chars[i])
|
||||
fail("Expected '" + expected + "', was='" + new String(chars, 0, textLen) + "' - first different at index=" + i);
|
||||
}
|
||||
}
|
||||
|
||||
public TerminalTestCase assertLinesAre(String... lines) {
|
||||
assertEquals(lines.length, mTerminal.getScreen().mScreenRows);
|
||||
for (int i = 0; i < lines.length; i++)
|
||||
try {
|
||||
assertLineIs(i, lines[i]);
|
||||
} catch (AssertionFailedError e) {
|
||||
throw new AssertionFailedError("Line: " + i + " - " + e.getMessage());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public TerminalTestCase resize(int cols, int rows) {
|
||||
mTerminal.resize(cols, rows);
|
||||
assertInvariants();
|
||||
return this;
|
||||
}
|
||||
|
||||
public TerminalTestCase assertLineWraps(boolean... lines) {
|
||||
for (int i = 0; i < lines.length; i++)
|
||||
assertEquals("line=" + i, lines[i], mTerminal.getScreen().mLines[mTerminal.getScreen().externalToInternalRow(i)].mLineWrap);
|
||||
return this;
|
||||
}
|
||||
|
||||
protected TerminalTestCase assertLineStartsWith(int line, int... codePoints) {
|
||||
char[] chars = mTerminal.getScreen().mLines[mTerminal.getScreen().externalToInternalRow(line)].mText;
|
||||
int charIndex = 0;
|
||||
for (int i = 0; i < codePoints.length; i++) {
|
||||
int lineCodePoint = chars[charIndex++];
|
||||
if (Character.isHighSurrogate((char) lineCodePoint)) {
|
||||
lineCodePoint = Character.toCodePoint((char) lineCodePoint, chars[charIndex++]);
|
||||
}
|
||||
assertEquals("Differing a code point index=" + i, codePoints[i], lineCodePoint);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
protected TerminalTestCase placeCursorAndAssert(int row, int col) {
|
||||
// +1 due to escape sequence being one based.
|
||||
enterString("\033[" + (row + 1) + ";" + (col + 1) + "H");
|
||||
assertCursorAt(row, col);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TerminalTestCase assertCursorAt(int row, int col) {
|
||||
int actualRow = mTerminal.getCursorRow();
|
||||
int actualCol = mTerminal.getCursorCol();
|
||||
if (!(row == actualRow && col == actualCol))
|
||||
fail("Expected cursor at (row,col)=(" + row + ", " + col + ") but was (" + actualRow + ", " + actualCol + ")");
|
||||
return this;
|
||||
}
|
||||
|
||||
/** For testing only. Encoded style according to {@link TextStyle}. */
|
||||
public long getStyleAt(int externalRow, int column) {
|
||||
return mTerminal.getScreen().getStyleAt(externalRow, column);
|
||||
}
|
||||
|
||||
public static class EffectLine {
|
||||
final int[] styles;
|
||||
|
||||
public EffectLine(int[] styles) {
|
||||
this.styles = styles;
|
||||
}
|
||||
}
|
||||
|
||||
protected EffectLine effectLine(int... bits) {
|
||||
return new EffectLine(bits);
|
||||
}
|
||||
|
||||
public TerminalTestCase assertEffectAttributesSet(EffectLine... lines) {
|
||||
assertEquals(lines.length, mTerminal.getScreen().mScreenRows);
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
int[] line = lines[i].styles;
|
||||
for (int j = 0; j < line.length; j++) {
|
||||
int effectsAtCell = TextStyle.decodeEffect(getStyleAt(i, j));
|
||||
int attributes = line[j];
|
||||
if ((effectsAtCell & attributes) != attributes) fail("Line=" + i + ", column=" + j + ", expected "
|
||||
+ describeStyle(attributes) + " set, was " + describeStyle(effectsAtCell));
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public TerminalTestCase assertForegroundIndices(EffectLine... lines) {
|
||||
assertEquals(lines.length, mTerminal.getScreen().mScreenRows);
|
||||
for (int i = 0; i < lines.length; i++) {
|
||||
int[] line = lines[i].styles;
|
||||
for (int j = 0; j < line.length; j++) {
|
||||
int actualColor = TextStyle.decodeForeColor(getStyleAt(i, j));
|
||||
int expectedColor = line[j];
|
||||
if (actualColor != expectedColor) fail("Line=" + i + ", column=" + j + ", expected color "
|
||||
+ Integer.toHexString(expectedColor) + " set, was " + Integer.toHexString(actualColor));
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private static String describeStyle(int styleBits) {
|
||||
return "'" + ((styleBits & TextStyle.CHARACTER_ATTRIBUTE_BLINK) != 0 ? ":BLINK:" : "")
|
||||
+ ((styleBits & TextStyle.CHARACTER_ATTRIBUTE_BOLD) != 0 ? ":BOLD:" : "")
|
||||
+ ((styleBits & TextStyle.CHARACTER_ATTRIBUTE_INVERSE) != 0 ? ":INVERSE:" : "")
|
||||
+ ((styleBits & TextStyle.CHARACTER_ATTRIBUTE_INVISIBLE) != 0 ? ":INVISIBLE:" : "")
|
||||
+ ((styleBits & TextStyle.CHARACTER_ATTRIBUTE_ITALIC) != 0 ? ":ITALIC:" : "")
|
||||
+ ((styleBits & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED) != 0 ? ":PROTECTED:" : "")
|
||||
+ ((styleBits & TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH) != 0 ? ":STRIKETHROUGH:" : "")
|
||||
+ ((styleBits & TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE) != 0 ? ":UNDERLINE:" : "") + "'";
|
||||
}
|
||||
|
||||
public void assertForegroundColorAt(int externalRow, int column, int color) {
|
||||
long style = mTerminal.getScreen().mLines[mTerminal.getScreen().externalToInternalRow(externalRow)].getStyle(column);
|
||||
assertEquals(color, TextStyle.decodeForeColor(style));
|
||||
}
|
||||
|
||||
public TerminalTestCase assertColor(int colorIndex, int expected) {
|
||||
int actual = mTerminal.mColors.mCurrentColors[colorIndex];
|
||||
if (expected != actual) {
|
||||
fail("Color index=" + colorIndex + ", expected=" + Integer.toHexString(expected) + ", was=" + Integer.toHexString(actual));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package com.itsaky.terminal;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
public class TextStyleTest extends TestCase {
|
||||
|
||||
private static final int[] ALL_EFFECTS = new int[]{0, TextStyle.CHARACTER_ATTRIBUTE_BOLD, TextStyle.CHARACTER_ATTRIBUTE_ITALIC,
|
||||
TextStyle.CHARACTER_ATTRIBUTE_UNDERLINE, TextStyle.CHARACTER_ATTRIBUTE_BLINK, TextStyle.CHARACTER_ATTRIBUTE_INVERSE,
|
||||
TextStyle.CHARACTER_ATTRIBUTE_INVISIBLE, TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH, TextStyle.CHARACTER_ATTRIBUTE_PROTECTED,
|
||||
TextStyle.CHARACTER_ATTRIBUTE_DIM};
|
||||
|
||||
public void testEncodingSingle() {
|
||||
for (int fx : ALL_EFFECTS) {
|
||||
for (int fg = 0; fg < TextStyle.NUM_INDEXED_COLORS; fg++) {
|
||||
for (int bg = 0; bg < TextStyle.NUM_INDEXED_COLORS; bg++) {
|
||||
long encoded = TextStyle.encode(fg, bg, fx);
|
||||
assertEquals(fg, TextStyle.decodeForeColor(encoded));
|
||||
assertEquals(bg, TextStyle.decodeBackColor(encoded));
|
||||
assertEquals(fx, TextStyle.decodeEffect(encoded));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testEncoding24Bit() {
|
||||
int[] values = {255, 240, 127, 1, 0};
|
||||
for (int red : values) {
|
||||
for (int green : values) {
|
||||
for (int blue : values) {
|
||||
int argb = 0xFF000000 | (red << 16) | (green << 8) | blue;
|
||||
long encoded = TextStyle.encode(argb, 0, 0);
|
||||
assertEquals(argb, TextStyle.decodeForeColor(encoded));
|
||||
encoded = TextStyle.encode(0, argb, 0);
|
||||
assertEquals(argb, TextStyle.decodeBackColor(encoded));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void testEncodingCombinations() {
|
||||
for (int f1 : ALL_EFFECTS) {
|
||||
for (int f2 : ALL_EFFECTS) {
|
||||
int combined = f1 | f2;
|
||||
assertEquals(combined, TextStyle.decodeEffect(TextStyle.encode(0, 0, combined)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testEncodingStrikeThrough() {
|
||||
long encoded = TextStyle.encode(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.COLOR_INDEX_BACKGROUND,
|
||||
TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH);
|
||||
assertTrue((TextStyle.decodeEffect(encoded) & TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH) != 0);
|
||||
}
|
||||
|
||||
public void testEncodingProtected() {
|
||||
long encoded = TextStyle.encode(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.COLOR_INDEX_BACKGROUND,
|
||||
TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH);
|
||||
assertEquals(0, (TextStyle.decodeEffect(encoded) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED));
|
||||
encoded = TextStyle.encode(TextStyle.COLOR_INDEX_FOREGROUND, TextStyle.COLOR_INDEX_BACKGROUND,
|
||||
TextStyle.CHARACTER_ATTRIBUTE_STRIKETHROUGH | TextStyle.CHARACTER_ATTRIBUTE_PROTECTED);
|
||||
assertTrue((TextStyle.decodeEffect(encoded) & TextStyle.CHARACTER_ATTRIBUTE_PROTECTED) != 0);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
package com.itsaky.terminal;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
public class UnicodeInputTest extends TerminalTestCase {
|
||||
|
||||
public void testIllFormedUtf8SuccessorByteNotConsumed() throws Exception {
|
||||
// The Unicode Standard Version 6.2 – Core Specification (http://www.unicode.org/versions/Unicode6.2.0/ch03.pdf):
|
||||
// "If the converter encounters an ill-formed UTF-8 code unit sequence which starts with a valid first byte, but which does not
|
||||
// continue with valid successor bytes (see Table 3-7), it must not consume the successor bytes as part of the ill-formed
|
||||
// subsequence whenever those successor bytes themselves constitute part of a well-formed UTF-8 code unit subsequence."
|
||||
withTerminalSized(5, 5);
|
||||
mTerminal.append(new byte[]{(byte) 0b11101111, (byte) 'a'}, 2);
|
||||
assertLineIs(0, ((char) TerminalEmulator.UNICODE_REPLACEMENT_CHAR) + "a ");
|
||||
|
||||
// https://code.google.com/p/chromium/issues/detail?id=212704
|
||||
byte[] input = new byte[]{
|
||||
(byte) 0x61, (byte) 0xF1,
|
||||
(byte) 0x80, (byte) 0x80,
|
||||
(byte) 0xe1, (byte) 0x80,
|
||||
(byte) 0xc2, (byte) 0x62,
|
||||
(byte) 0x80, (byte) 0x63,
|
||||
(byte) 0x80, (byte) 0xbf,
|
||||
(byte) 0x64
|
||||
};
|
||||
withTerminalSized(10, 2);
|
||||
mTerminal.append(input, input.length);
|
||||
assertLinesAre("a\uFFFD\uFFFD\uFFFDb\uFFFDc\uFFFD\uFFFDd", " ");
|
||||
|
||||
// Surrogate pairs.
|
||||
withTerminalSized(5, 2);
|
||||
input = new byte[]{
|
||||
(byte) 0xed, (byte) 0xa0,
|
||||
(byte) 0x80, (byte) 0xed,
|
||||
(byte) 0xad, (byte) 0xbf,
|
||||
(byte) 0xed, (byte) 0xae,
|
||||
(byte) 0x80, (byte) 0xed,
|
||||
(byte) 0xbf, (byte) 0xbf
|
||||
};
|
||||
mTerminal.append(input, input.length);
|
||||
assertLinesAre("\uFFFD\uFFFD\uFFFD\uFFFD ", " ");
|
||||
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=746900: "with this patch 0xe0 0x80 is decoded as two U+FFFDs,
|
||||
// but 0xe0 0xa0 is decoded as a single U+FFFD, and this is correct according to the "Best Practices", but IE
|
||||
// and Chrome (Version 22.0.1229.94) decode both of them as two U+FFFDs. Opera 12.11 decodes both of them as
|
||||
// one U+FFFD".
|
||||
withTerminalSized(5, 2);
|
||||
input = new byte[]{(byte) 0xe0, (byte) 0xa0, ' '};
|
||||
mTerminal.append(input, input.length);
|
||||
assertLinesAre("\uFFFD ", " ");
|
||||
|
||||
// withTerminalSized(5, 2);
|
||||
// input = new byte[]{(byte) 0xe0, (byte) 0x80, 'a'};
|
||||
// mTerminal.append(input, input.length);
|
||||
// assertLinesAre("\uFFFD\uFFFDa ", " ");
|
||||
}
|
||||
|
||||
public void testUnassignedCodePoint() throws UnsupportedEncodingException {
|
||||
withTerminalSized(3, 3);
|
||||
// UTF-8 for U+C2541, an unassigned code point:
|
||||
byte[] b = new byte[]{(byte) 0xf3, (byte) 0x82, (byte) 0x95, (byte) 0x81};
|
||||
mTerminal.append(b, b.length);
|
||||
enterString("Y");
|
||||
assertEquals(1, Character.charCount(TerminalEmulator.UNICODE_REPLACEMENT_CHAR));
|
||||
assertLineStartsWith(0, TerminalEmulator.UNICODE_REPLACEMENT_CHAR, (int) 'Y', ' ');
|
||||
}
|
||||
|
||||
public void testStuff() {
|
||||
withTerminalSized(80, 24);
|
||||
byte[] b = new byte[]{(byte) 0xf3, (byte) 0x82, (byte) 0x95, (byte) 0x81, (byte) 0x61, (byte) 0x38, (byte) 0xe7, (byte) 0x8f,
|
||||
(byte) 0xae, (byte) 0xc2, (byte) 0x9f, (byte) 0xe8, (byte) 0xa0, (byte) 0x9f, (byte) 0xe8, (byte) 0x8c, (byte) 0xa4,
|
||||
(byte) 0xed, (byte) 0x93, (byte) 0x89, (byte) 0xef, (byte) 0xbf, (byte) 0xbd, (byte) 0x42, (byte) 0xc2, (byte) 0x9b,
|
||||
(byte) 0xe6, (byte) 0x87, (byte) 0x89, (byte) 0x5a};
|
||||
mTerminal.append(b, b.length);
|
||||
}
|
||||
|
||||
public void testSimpleCombining() throws Exception {
|
||||
withTerminalSized(3, 2).enterString(" a\u0302 ").assertLinesAre(" a\u0302 ", " ");
|
||||
}
|
||||
|
||||
public void testCombiningCharacterInFirstColumn() throws Exception {
|
||||
withTerminalSized(5, 3).enterString("test\r\nhi\r\n").assertLinesAre("test ", "hi ", " ");
|
||||
|
||||
// U+0302 is COMBINING CIRCUMFLEX ACCENT. Test case from mosh (http://mosh.mit.edu/).
|
||||
withTerminalSized(5, 5).enterString("test\r\nabc\r\n\u0302\r\ndef\r\n");
|
||||
assertLinesAre("test ", "abc ", " \u0302 ", "def ", " ");
|
||||
}
|
||||
|
||||
public void testCombiningCharacterInLastColumn() throws Exception {
|
||||
withTerminalSized(3, 2).enterString(" a\u0302").assertLinesAre(" a\u0302", " ");
|
||||
withTerminalSized(3, 2).enterString(" à̲").assertLinesAre(" à̲", " ");
|
||||
withTerminalSized(3, 2).enterString("Aà̲F").assertLinesAre("Aà̲F", " ");
|
||||
}
|
||||
|
||||
public void testWideCharacterInLastColumn() throws Exception {
|
||||
withTerminalSized(3, 2).enterString(" 枝\u0302").assertLinesAre(" ", "枝\u0302 ");
|
||||
|
||||
withTerminalSized(3, 2).enterString(" 枝").assertLinesAre(" 枝", " ").assertCursorAt(0, 2);
|
||||
enterString("a").assertLinesAre(" 枝", "a ");
|
||||
}
|
||||
|
||||
public void testWideCharacterDeletion() throws Exception {
|
||||
// CSI Ps D Cursor Backward Ps Times
|
||||
withTerminalSized(3, 2).enterString("枝\033[Da").assertLinesAre(" a ", " ");
|
||||
withTerminalSized(3, 2).enterString("枝\033[2Da").assertLinesAre("a ", " ");
|
||||
withTerminalSized(3, 2).enterString("枝\033[2D枝").assertLinesAre("枝 ", " ");
|
||||
withTerminalSized(3, 2).enterString("枝\033[1D枝").assertLinesAre(" 枝", " ");
|
||||
withTerminalSized(5, 2).enterString(" 枝 \033[Da").assertLinesAre(" 枝a ", " ");
|
||||
withTerminalSized(5, 2).enterString("a \033[D\u0302").assertLinesAre("a\u0302 ", " ");
|
||||
withTerminalSized(5, 2).enterString("枝 \033[D\u0302").assertLinesAre("枝\u0302 ", " ");
|
||||
enterString("Z").assertLinesAre("枝\u0302Z ", " ");
|
||||
enterString("\033[D ").assertLinesAre("枝\u0302 ", " ");
|
||||
// Go back two columns, standing at the second half of the wide character:
|
||||
enterString("\033[2DU").assertLinesAre(" U ", " ");
|
||||
}
|
||||
|
||||
public void testWideCharOverwriting() {
|
||||
withTerminalSized(3, 2).enterString("abc\033[3D枝").assertLinesAre("枝c", " ");
|
||||
}
|
||||
|
||||
public void testOverlongUtf8Encoding() throws Exception {
|
||||
// U+0020 should be encoded as 0x20, 0xc0 0xa0 is an overlong encoding
|
||||
// so should be replaced with the replacement char U+FFFD.
|
||||
withTerminalSized(5, 5).mTerminal.append(new byte[]{(byte) 0xc0, (byte) 0xa0, 'Y'}, 3);
|
||||
assertLineIs(0, "\uFFFDY ");
|
||||
}
|
||||
|
||||
public void testWideCharacterWithoutWrapping() throws Exception {
|
||||
// With wraparound disabled. The behaviour when a wide character is output with cursor in
|
||||
// the last column when autowrap is disabled is not obvious, but we expect the wide
|
||||
// character to be ignored here.
|
||||
withTerminalSized(3, 3).enterString("\033[?7l").enterString("枝枝枝").assertLinesAre("枝 ", " ", " ");
|
||||
enterString("a枝").assertLinesAre("枝a", " ", " ");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package com.itsaky.terminal;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
public class WcWidthTest extends TestCase {
|
||||
|
||||
private static void assertWidthIs(int expectedWidth, int codePoint) {
|
||||
int wcWidth = WcWidth.width(codePoint);
|
||||
assertEquals(expectedWidth, wcWidth);
|
||||
}
|
||||
|
||||
public void testPrintableAscii() {
|
||||
for (int i = 0x20; i <= 0x7E; i++) {
|
||||
assertWidthIs(1, i);
|
||||
}
|
||||
}
|
||||
|
||||
public void testSomeWidthOne() {
|
||||
assertWidthIs(1, 'å');
|
||||
assertWidthIs(1, 'ä');
|
||||
assertWidthIs(1, 'ö');
|
||||
assertWidthIs(1, 0x23F2);
|
||||
}
|
||||
|
||||
public void testSomeWide() {
|
||||
assertWidthIs(2, 'A');
|
||||
assertWidthIs(2, 'B');
|
||||
assertWidthIs(2, 'C');
|
||||
assertWidthIs(2, '中');
|
||||
assertWidthIs(2, '文');
|
||||
|
||||
assertWidthIs(2, 0x679C);
|
||||
assertWidthIs(2, 0x679D);
|
||||
|
||||
assertWidthIs(2, 0x2070E);
|
||||
assertWidthIs(2, 0x20731);
|
||||
|
||||
assertWidthIs(1, 0x1F781);
|
||||
}
|
||||
|
||||
public void testSomeNonWide() {
|
||||
assertWidthIs(1, 0x1D11E);
|
||||
assertWidthIs(1, 0x1D11F);
|
||||
}
|
||||
|
||||
public void testCombining() {
|
||||
assertWidthIs(0, 0x0302);
|
||||
assertWidthIs(0, 0x0308);
|
||||
assertWidthIs(0, 0xFE0F);
|
||||
}
|
||||
|
||||
public void testWordJoiner() {
|
||||
// https://en.wikipedia.org/wiki/Word_joiner
|
||||
// The word joiner (WJ) is a code point in Unicode used to separate words when using scripts
|
||||
// that do not use explicit spacing. It is encoded since Unicode version 3.2
|
||||
// (released in 2002) as U+2060 WORD JOINER (HTML ⁠).
|
||||
// The word joiner does not produce any space, and prohibits a line break at its position.
|
||||
assertWidthIs(0, 0x2060);
|
||||
}
|
||||
|
||||
public void testSofthyphen() {
|
||||
// http://osdir.com/ml/internationalization.linux/2003-05/msg00006.html:
|
||||
// "Existing implementation practice in terminals is that the SOFT HYPHEN is
|
||||
// a spacing graphical character, and the purpose of my wcwidth() was to
|
||||
// predict the advancement of the cursor position after a string is sent to
|
||||
// a terminal. Hence, I have no choice but to keep wcwidth(SOFT HYPHEN) = 1.
|
||||
// VT100-style terminals do not hyphenate."
|
||||
assertWidthIs(1, 0x00AD);
|
||||
}
|
||||
|
||||
public void testHangul() {
|
||||
assertWidthIs(1, 0x11A3);
|
||||
}
|
||||
|
||||
public void testEmojis() {
|
||||
assertWidthIs(2, 0x1F428); // KOALA.
|
||||
assertWidthIs(2, 0x231a); // WATCH.
|
||||
assertWidthIs(2, 0x1F643); // UPSIDE-DOWN FACE (Unicode 8).
|
||||
}
|
||||
|
||||
}
|
||||
@@ -271,6 +271,10 @@ public final class TerminalView extends View {
|
||||
// Some keyboards seems do not reset the internal state on TYPE_NULL.
|
||||
// Affects mostly Samsung stock keyboards.
|
||||
// https://github.com/termux/termux-app/issues/686
|
||||
// However, this is not a valid value as per AOSP since `InputType.TYPE_CLASS_*` is
|
||||
// not set and it logs a warning:
|
||||
// W/InputAttributes: Unexpected input class: inputType=0x00080090 imeOptions=0x02000000
|
||||
// https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:packages/inputmethods/LatinIME/java/src/com/android/inputmethod/latin/InputAttributes.java;l=79
|
||||
outAttrs.inputType = InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
|
||||
} else {
|
||||
// Using InputType.NULL is the most correct input type and avoids issues with other hacks.
|
||||
@@ -346,6 +350,10 @@ public final class TerminalView extends View {
|
||||
codePoint = firstChar;
|
||||
}
|
||||
|
||||
// Check onKeyDown() for details.
|
||||
if (mClient.readShiftKey())
|
||||
codePoint = Character.toUpperCase(codePoint);
|
||||
|
||||
boolean ctrlHeld = false;
|
||||
if (codePoint <= 31 && codePoint != 27) {
|
||||
if (codePoint == '\n') {
|
||||
@@ -576,6 +584,102 @@ public final class TerminalView extends View {
|
||||
return super.onKeyPreIme(keyCode, event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Key presses in software keyboards will generally NOT trigger this listener, although some
|
||||
* may elect to do so in some situations. Do not rely on this to catch software key presses.
|
||||
* Gboard calls this when shouldEnforceCharBasedInput() is disabled (InputType.TYPE_NULL) instead
|
||||
* of calling commitText(), with deviceId=-1. However, Hacker's Keyboard, OpenBoard, LG Keyboard
|
||||
* call commitText().
|
||||
*
|
||||
* This function may also be called directly without android calling it, like by
|
||||
* `TerminalExtraKeys` which generates a KeyEvent manually which uses {@link KeyCharacterMap#VIRTUAL_KEYBOARD}
|
||||
* as the device (deviceId=-1), as does Gboard. That would normally use mappings defined in
|
||||
* `/system/usr/keychars/Virtual.kcm`. You can run `dumpsys input` to find the `KeyCharacterMapFile`
|
||||
* used by virtual keyboard or hardware keyboard. Note that virtual keyboard device is not the
|
||||
* same as software keyboard, like Gboard, etc. Its a fake device used for generating events and
|
||||
* for testing.
|
||||
*
|
||||
* We handle shift key in `commitText()` to convert codepoint to uppercase case there with a
|
||||
* call to {@link Character#toUpperCase(int)}, but here we instead rely on getUnicodeChar() for
|
||||
* conversion of keyCode, for both hardware keyboard shift key (via effectiveMetaState) and
|
||||
* `mClient.readShiftKey()`, based on value in kcm files.
|
||||
* This may result in different behaviour depending on keyboard and android kcm files set for the
|
||||
* InputDevice for the event passed to this function. This will likely be an issue for non-english
|
||||
* languages since `Virtual.kcm` in english only by default or at least in AOSP. For both hardware
|
||||
* shift key (via effectiveMetaState) and `mClient.readShiftKey()`, `getUnicodeChar()` is used
|
||||
* for shift specific behaviour which usually is to uppercase.
|
||||
*
|
||||
* For fn key on hardware keyboard, android checks kcm files for hardware keyboards, which is
|
||||
* `Generic.kcm` by default, unless a vendor specific one is defined. The event passed will have
|
||||
* {@link KeyEvent#META_FUNCTION_ON} set. If the kcm file only defines a single character or unicode
|
||||
* code point `\\uxxxx`, then only one event is passed with that value. However, if kcm defines
|
||||
* a `fallback` key for fn or others, like `key DPAD_UP { ... fn: fallback PAGE_UP }`, then
|
||||
* android will first pass an event with original key `DPAD_UP` and {@link KeyEvent#META_FUNCTION_ON}
|
||||
* set. But this function will not consume it and android will pass another event with `PAGE_UP`
|
||||
* and {@link KeyEvent#META_FUNCTION_ON} not set, which will be consumed.
|
||||
*
|
||||
* Now there are some other issues as well, firstly ctrl and alt flags are not passed to
|
||||
* `getUnicodeChar()`, so modified key values in kcm are not used. Secondly, if the kcm file
|
||||
* for other modifiers like shift or fn define a non-alphabet, like { fn: '\u0015' } to act as
|
||||
* DPAD_LEFT, the `getUnicodeChar()` will correctly return `21` as the code point but action will
|
||||
* not happen because the `handleKeyCode()` function that transforms DPAD_LEFT to `\033[D`
|
||||
* escape sequence for the terminal to perform the left action would not be called since its
|
||||
* called before `getUnicodeChar()` and terminal will instead get `21 0x15 Negative Acknowledgement`.
|
||||
* The solution to such issues is calling `getUnicodeChar()` before the call to `handleKeyCode()`
|
||||
* if user has defined a custom kcm file, like done in POC mentioned in #2237. Note that
|
||||
* Hacker's Keyboard calls `commitText()` so don't test fn/shift with it for this function.
|
||||
* https://github.com/termux/termux-app/pull/2237
|
||||
* https://github.com/agnostic-apollo/termux-app/blob/terminal-code-point-custom-mapping/terminal-view/src/main/java/com/termux/view/TerminalView.java
|
||||
*
|
||||
* Key Character Map (kcm) and Key Layout (kl) files info:
|
||||
* https://source.android.com/devices/input/key-character-map-files
|
||||
* https://source.android.com/devices/input/key-layout-files
|
||||
* https://source.android.com/devices/input/keyboard-devices
|
||||
* AOSP kcm and kl files:
|
||||
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/base/data/keyboards
|
||||
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/base/packages/InputDevices/res/raw
|
||||
*
|
||||
* KeyCodes:
|
||||
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/base/core/java/android/view/KeyEvent.java
|
||||
* https://cs.android.com/android/platform/superproject/+/master:frameworks/native/include/android/keycodes.h
|
||||
*
|
||||
* `dumpsys input`:
|
||||
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/native/services/inputflinger/reader/EventHub.cpp;l=1917
|
||||
*
|
||||
* Loading of keymap:
|
||||
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/native/services/inputflinger/reader/EventHub.cpp;l=1644
|
||||
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/native/libs/input/Keyboard.cpp;l=41
|
||||
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/native/libs/input/InputDevice.cpp
|
||||
* OVERLAY keymaps for hardware keyboards may be combined as well:
|
||||
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/native/libs/input/KeyCharacterMap.cpp;l=165
|
||||
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/native/libs/input/KeyCharacterMap.cpp;l=831
|
||||
*
|
||||
* Parse kcm file:
|
||||
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/native/libs/input/KeyCharacterMap.cpp;l=727
|
||||
* Parse key value:
|
||||
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/native/libs/input/KeyCharacterMap.cpp;l=981
|
||||
*
|
||||
* `KeyEvent.getUnicodeChar()`
|
||||
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/base/core/java/android/view/KeyEvent.java;l=2716
|
||||
* https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/view/KeyCharacterMap.java;l=368
|
||||
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/base/core/jni/android_view_KeyCharacterMap.cpp;l=117
|
||||
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/native/libs/input/KeyCharacterMap.cpp;l=231
|
||||
*
|
||||
* Keyboard layouts advertised by applications, like for hardware keyboards via #ACTION_QUERY_KEYBOARD_LAYOUTS
|
||||
* Config is stored in `/data/system/input-manager-state.xml`
|
||||
* https://github.com/ris58h/custom-keyboard-layout
|
||||
* Loading from apps:
|
||||
* https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/input/InputManagerService.java;l=1221
|
||||
* Set:
|
||||
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/base/core/java/android/hardware/input/InputManager.java;l=89
|
||||
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/base/core/java/android/hardware/input/InputManager.java;l=543
|
||||
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:packages/apps/Settings/src/com/android/settings/inputmethod/KeyboardLayoutDialogFragment.java;l=167
|
||||
* https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/input/InputManagerService.java;l=1385
|
||||
* https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/input/PersistentDataStore.java
|
||||
* Get overlay keyboard layout
|
||||
* https://cs.android.com/android/platform/superproject/+/master:frameworks/base/services/core/java/com/android/server/input/InputManagerService.java;l=2158
|
||||
* https://cs.android.com/android/platform/superproject/+/android-11.0.0_r40:frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp;l=616
|
||||
*/
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (TERMINAL_VIEW_KEY_LOGGING_ENABLED)
|
||||
@@ -598,13 +702,15 @@ public final class TerminalView extends View {
|
||||
final int metaState = event.getMetaState();
|
||||
final boolean controlDown = event.isCtrlPressed() || mClient.readControlKey();
|
||||
final boolean leftAltDown = (metaState & KeyEvent.META_ALT_LEFT_ON) != 0 || mClient.readAltKey();
|
||||
final boolean shiftDown = event.isShiftPressed() || mClient.readShiftKey();
|
||||
final boolean rightAltDownFromEvent = (metaState & KeyEvent.META_ALT_RIGHT_ON) != 0;
|
||||
|
||||
int keyMod = 0;
|
||||
if (controlDown) keyMod |= KeyHandler.KEYMOD_CTRL;
|
||||
if (event.isAltPressed() || leftAltDown) keyMod |= KeyHandler.KEYMOD_ALT;
|
||||
if (event.isShiftPressed()) keyMod |= KeyHandler.KEYMOD_SHIFT;
|
||||
if (shiftDown) keyMod |= KeyHandler.KEYMOD_SHIFT;
|
||||
if (event.isNumLockOn()) keyMod |= KeyHandler.KEYMOD_NUM_LOCK;
|
||||
// https://github.com/termux/termux-app/issues/731
|
||||
if (!event.isFunctionPressed() && handleKeyCode(keyCode, keyMod)) {
|
||||
if (TERMINAL_VIEW_KEY_LOGGING_ENABLED) mClient.logInfo(LOG_TAG, "handleKeyCode() took key event");
|
||||
return true;
|
||||
@@ -620,6 +726,9 @@ public final class TerminalView extends View {
|
||||
}
|
||||
int effectiveMetaState = event.getMetaState() & ~bitsToClear;
|
||||
|
||||
if (shiftDown) effectiveMetaState |= KeyEvent.META_SHIFT_ON | KeyEvent.META_SHIFT_LEFT_ON;
|
||||
if (mClient.readFnKey()) effectiveMetaState |= KeyEvent.META_FUNCTION_ON;
|
||||
|
||||
int result = event.getUnicodeChar(effectiveMetaState);
|
||||
if (TERMINAL_VIEW_KEY_LOGGING_ENABLED)
|
||||
mClient.logInfo(LOG_TAG, "KeyEvent#getUnicodeChar(" + effectiveMetaState + ") returned: " + result);
|
||||
@@ -655,6 +764,10 @@ public final class TerminalView extends View {
|
||||
|
||||
if (mTermSession == null) return;
|
||||
|
||||
// Ensure cursor is shown when a key is pressed down like long hold on (arrow) keys
|
||||
if (mEmulator != null)
|
||||
mEmulator.setCursorBlinkState(true);
|
||||
|
||||
final boolean controlDown = controlDownFromEvent || mClient.readControlKey();
|
||||
final boolean altDown = leftAltDownFromEvent || mClient.readAltKey();
|
||||
|
||||
|
||||
@@ -54,6 +54,11 @@ public interface TerminalViewClient {
|
||||
|
||||
boolean readAltKey();
|
||||
|
||||
boolean readShiftKey();
|
||||
|
||||
boolean readFnKey();
|
||||
|
||||
|
||||
|
||||
boolean onCodePoint(int codePoint, boolean ctrlDown, TerminalSession session);
|
||||
|
||||
|
||||
+1
-1
@@ -119,7 +119,7 @@ public class TextSelectionCursorController implements CursorController {
|
||||
ClipboardManager clipboard = (ClipboardManager) terminalView.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
menu.add(Menu.NONE, ACTION_COPY, Menu.NONE, R.string.copy_text).setShowAsAction(show);
|
||||
menu.add(Menu.NONE, ACTION_PASTE, Menu.NONE, R.string.paste_text).setEnabled(clipboard.hasPrimaryClip()).setShowAsAction(show);
|
||||
|
||||
menu.add(Menu.NONE, ACTION_MORE, Menu.NONE, R.string.text_selection_more);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx512m
|
||||
org.gradle.jvmargs=-Xmx1024m
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
|
||||
+2
-11
@@ -25,15 +25,6 @@ android {
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
// LSP4J
|
||||
implementation 'org.eclipse.lsp4j:org.eclipse.lsp4j:0.12.0'
|
||||
|
||||
implementation 'com.google.code.gson:gson:2.8.5'
|
||||
implementation 'xerces:xercesImpl:2.12.1'
|
||||
implementation 'xml-apis:xml-apis:1.4.01'
|
||||
implementation 'xml-resolver:xml-resolver:1.2'
|
||||
implementation 'org.jsoup:jsoup:1.14.2'
|
||||
implementation ('com.kotcrab.remark:remark:1.0.0') {
|
||||
exclude group: 'org.jsoup', module: 'jsoup'
|
||||
}
|
||||
implementation 'com.google.code.gson:gson:2.8.7'
|
||||
implementation 'org.eclipse.xtend:org.eclipse.xtend.core:2.24.0'
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,24 +0,0 @@
|
||||
package com.itsaky.lemminx.utils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.logging.FileHandler;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.logging.SimpleFormatter;
|
||||
|
||||
public class LogUtils {
|
||||
|
||||
public static Logger newLogger(String name) {
|
||||
Logger logger = Logger.getLogger(name);
|
||||
try {
|
||||
final FileHandler handler = new FileHandler("/sdcard/ide_xlog/java_logger.txt");
|
||||
handler.setFormatter(new SimpleFormatter());
|
||||
logger.addHandler(handler);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} catch (SecurityException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return logger;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.itsaky.lsp.services;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java9.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.lsp4j.InitializeParams;
|
||||
import org.eclipse.lsp4j.InitializeResult;
|
||||
@@ -100,4 +100,4 @@ public interface IDELanguageServer {
|
||||
default void setTrace(SetTraceParams params) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.itsaky.lsp.services;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java9.util.concurrent.CompletableFuture;
|
||||
|
||||
import com.itsaky.lsp.SemanticHighlight;
|
||||
import com.itsaky.lsp.SemanticHighlightsParams;
|
||||
@@ -14,4 +14,4 @@ public interface IDETextDocumentService extends TextDocumentService{
|
||||
@JsonRequest ("semanticHighlights")
|
||||
CompletableFuture<List<SemanticHighlight>> semanticHighlights (SemanticHighlightsParams params) ;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.itsaky.lsp.services;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java9.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.eclipse.lsp4j.CreateFilesParams;
|
||||
import org.eclipse.lsp4j.DeleteFilesParams;
|
||||
@@ -147,4 +147,4 @@ public interface IDEWorkspaceService {
|
||||
default void didDeleteFiles(DeleteFilesParams params) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package java9.util.function;
|
||||
|
||||
import java9.util.function.Consumer;
|
||||
import java9.util.Objects;
|
||||
|
||||
/**
|
||||
* Represents an operation that accepts two input arguments and returns no
|
||||
* result. This is the two-arity specialization of {@link Consumer}.
|
||||
* Unlike most other functional interfaces, {@code BiConsumer} is expected
|
||||
* to operate via side-effects.
|
||||
*
|
||||
* <p>This is a <a href="package-summary.html">functional interface</a>
|
||||
* whose functional method is {@link #accept(Object, Object)}.
|
||||
*
|
||||
* @param <T> the type of the first argument to the operation
|
||||
* @param <U> the type of the second argument to the operation
|
||||
*
|
||||
* @see Consumer
|
||||
* @since 1.8
|
||||
*/
|
||||
public interface BiConsumer<T, U> {
|
||||
|
||||
/**
|
||||
* Performs this operation on the given arguments.
|
||||
*
|
||||
* @param t the first input argument
|
||||
* @param u the second input argument
|
||||
*/
|
||||
void accept(T t, U u);
|
||||
|
||||
/**
|
||||
* Returns a composed {@code BiConsumer} that performs, in sequence, this
|
||||
* operation followed by the {@code after} operation. If performing either
|
||||
* operation throws an exception, it is relayed to the caller of the
|
||||
* composed operation. If performing this operation throws an exception,
|
||||
* the {@code after} operation will not be performed.
|
||||
*
|
||||
* @param after the operation to perform after this operation
|
||||
* @return a composed {@code BiConsumer} that performs in sequence this
|
||||
* operation followed by the {@code after} operation
|
||||
* @throws NullPointerException if {@code after} is null
|
||||
*/
|
||||
default BiConsumer<T, U> andThen(BiConsumer<? super T, ? super U> after) {
|
||||
Objects.requireNonNull(after);
|
||||
|
||||
return (l, r) -> {
|
||||
accept(l, r);
|
||||
after.accept(l, r);
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user