From 46dfc25c0dd142ec7368760f9ac5aa465a9b824f Mon Sep 17 00:00:00 2001 From: RainbowC0 <15760207563@139.com> Date: Fri, 9 Aug 2024 13:57:26 +0800 Subject: [PATCH] Release 0.1.2(3) --- app/build.gradle | 10 +- app/src/main/AndroidManifest.xml | 8 +- .../main/java/cn/rbc/termuc/EditFragment.java | 120 ++++++---- .../main/java/cn/rbc/termuc/FileAdapter.java | 10 +- app/src/main/java/cn/rbc/termuc/Lsp.java | 114 +++++---- .../main/java/cn/rbc/termuc/MainActivity.java | 131 ++++++---- .../main/java/cn/rbc/termuc/MainHandler.java | 10 +- .../main/java/cn/rbc/termuc/SearchAction.java | 25 +- app/src/main/java/cn/rbc/termuc/Settings.java | 30 ++- .../java/cn/rbc/termuc/SettingsActivity.java | 14 +- .../main/java/cn/rbc/termuc/TextEditor.java | 22 +- app/src/main/java/cn/rbc/termuc/Utils.java | 51 +++- .../main/res/drawable/ic_chevron_left_24.xml | 9 + .../main/res/drawable/ic_chevron_right_24.xml | 9 + app/src/main/res/layout/search_action.xml | 1 + app/src/main/res/menu/search.xml | 2 + app/src/main/res/values-zh/strings.xml | 9 +- app/src/main/res/values/strings.xml | 48 +++- app/src/main/res/xml/settings.xml | 17 +- .../java/cn/rbc/codeeditor/util/Document.java | 224 ++++++------------ .../cn/rbc/codeeditor/util/TextBuffer.java | 1 - .../cn/rbc/codeeditor/util/Tokenizer.java | 43 ++-- .../rbc/codeeditor/view/ClipboardPanel.java | 193 ++++++++------- .../cn/rbc/codeeditor/view/ColorScheme.java | 9 +- .../rbc/codeeditor/view/ColorSchemeDark.java | 2 + .../rbc/codeeditor/view/ColorSchemeLight.java | 6 +- .../view/FreeScrollingTextField.java | 95 ++++---- .../codeeditor/view/TextFieldController.java | 132 ++++++----- .../view/autocomplete/AutoPanelAdapter.java | 20 +- .../metadata/android/en-US/changelogs/3.txt | 6 + 30 files changed, 774 insertions(+), 597 deletions(-) create mode 100644 app/src/main/res/drawable/ic_chevron_left_24.xml create mode 100644 app/src/main/res/drawable/ic_chevron_right_24.xml create mode 100644 fastlane/metadata/android/en-US/changelogs/3.txt diff --git a/app/build.gradle b/app/build.gradle index 0a2553f..5764c46 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,15 +2,15 @@ apply plugin: 'com.android.application' android { namespace 'cn.rbc.termuc' - compileSdkVersion 29 - buildToolsVersion "29.0.2" + compileSdkVersion 30 + buildToolsVersion "30.0.2" defaultConfig { applicationId "cn.rbc.termuc" minSdkVersion 21 - targetSdkVersion 29 - versionCode 2 - versionName "0.1.1" + targetSdkVersion 30 + versionCode 3 + versionName "0.1.2" } buildTypes { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f457b7f..9895af2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,7 +1,11 @@ - + - + + + + diff --git a/app/src/main/java/cn/rbc/termuc/EditFragment.java b/app/src/main/java/cn/rbc/termuc/EditFragment.java index a3cca2b..3a7beb8 100644 --- a/app/src/main/java/cn/rbc/termuc/EditFragment.java +++ b/app/src/main/java/cn/rbc/termuc/EditFragment.java @@ -11,18 +11,22 @@ import cn.rbc.codeeditor.lang.c.*; import cn.rbc.codeeditor.view.*; import android.content.*; +import android.content.res.*; +import android.util.DisplayMetrics; +import android.util.TypedValue; public class EditFragment extends Fragment -implements OnTextChangeListener, DialogInterface.OnClickListener, Formatter +implements OnTextChangeListener, DialogInterface.OnClickListener, Formatter, Runnable { - public final static int TYPE_C = 0; - public final static int TYPE_CPP = 1; - public final static int TYPE_H = 2; - public final static int TYPE_HPP = 3; - final static String FL = "f", TP = "t"; + public final static int TYPE_C = 1; + public final static int TYPE_CPP = 2; + public final static int TYPE_HEADER = 0x80000000; + public final static int TYPE_OTHER = 0; + public final static int TYPE_MASK = 0x7fffffff; + final static String FL = "f", TP = "t", CS = "c", TS = "s"; private File fl; private TextEditor ed; - int type; + int type = -1; private String C = "clang"; private long lastModified; private java.util.List changes = new java.util.ArrayList<>(); @@ -31,49 +35,51 @@ public EditFragment() { } public EditFragment(String path, int type) { - Bundle bd = new Bundle(); - bd.putString(FL, path); - bd.putInt(TP, type); - setArguments(bd); + fl = new File(path); + this.type = type; } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - setRetainInstance(true); - Bundle arg = getArguments(); - if (arg == null) - return null; - fl = new File(arg.getString(FL)); - lastModified = fl.lastModified(); - type = arg.getInt(TP); final MainActivity ma = (MainActivity)getActivity(); TextEditor editor = new TextEditor(ma); ed = editor; - editor.setVerticalScrollBarEnabled(true); if (Settings.dark_mode) editor.setColorScheme(ColorSchemeDark.getInstance()); + DisplayMetrics dm = getResources().getDisplayMetrics(); + editor.setTextSize((int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, Settings.textsize, dm)); editor.setWordWrap(Settings.wordwrap); editor.setShowNonPrinting(Settings.whitespace); editor.setLayoutParams(new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); - if ("s".equals(Settings.completion)) - editor.setFormatter(this); - try { + FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); + if (savedInstanceState!=null) { + fl = new File((String)savedInstanceState.getCharSequence(FL)); + type = savedInstanceState.getInt(TP, type); + editor.setDocument((Document)savedInstanceState.getCharSequence(CS)); + editor.setTextSize(savedInstanceState.getInt(TS)); + } else try { String s = load(); - if ((type&1) == 0) { + int tp = type&TYPE_MASK; + if (tp == TYPE_C) { C = "clang"; editor.setLanguage(CLanguage.getInstance()); - } else { + } else if (tp == TYPE_CPP) { C = "clang++"; editor.setLanguage(CppLanguage.getInstance()); + } else { + editor.setLanguage(LanguageNonProg.getInstance()); } ma.setEditor(editor); - MainActivity.lsp.didOpen(fl, (type&1)==1?"cpp":"c", s); + if (tp != TYPE_OTHER) + MainActivity.lsp.didOpen(fl, tp==TYPE_CPP?"cpp":"c", s); } catch(IOException fnf) { fnf.printStackTrace(); - Toast.makeText(ma, R.string.open_failed, Toast.LENGTH_SHORT).show(); + HelperUtils.show(Toast.makeText(ma, R.string.open_failed, Toast.LENGTH_SHORT)); } + if ((type&TYPE_MASK)!=TYPE_OTHER && "s".equals(Settings.completion)) + editor.setFormatter(this); + lastModified = fl.lastModified(); return editor; } @@ -90,7 +96,7 @@ public int createAutoIndent(CharSequence text) { public CharSequence format(CharSequence txt, int width) { int start = ed.getSelectionStart(), end = ed.getSelectionEnd(); if (start==end) - MainActivity.lsp.formatting(fl, width); + MainActivity.lsp.formatting(fl, width, ed.isUseSpace()); else { Range range = new Range(); Document text = ed.getText(); @@ -107,16 +113,18 @@ public CharSequence format(CharSequence txt, int width) { } range.stc = start - range.stc; range.enc = end - range.enc; - MainActivity.lsp.rangeFormatting(fl, range, width); + MainActivity.lsp.rangeFormatting(fl, range, width, ed.isUseSpace()); } return null; } - //private int lastVer = -1; + private int mVer; + private long mSendTime; - public void onChanged(CharSequence c, int start, final int ver, boolean ins, boolean typ) { - Document text = ed.getText(); - boolean wordwrap = ed.isWordWrap(); + public void onChanged(CharSequence c, int start, int ver, boolean ins, boolean typ) { + TextEditor editor = ed; + Document text = editor.getText(); + boolean wordwrap = editor.isWordWrap(); Range range = new Range(); if (wordwrap) { range.stl = text.findLineNumber(start); @@ -153,18 +161,19 @@ public void onChanged(CharSequence c, int start, final int ver, boolean ins, boo if (ins && typ && c.length()==1) lsp.completionTry(fl, range.enl, range.enc+1, c.charAt(0)); changes.clear(); - ed.postDelayed(new Runnable(){ - long sendTime = System.currentTimeMillis(); - public void run() { - Lsp lsp = MainActivity.lsp; - if (lsp.lastReceivedTime() +public class FileAdapter extends BaseAdapter implements Comparator, FilenameFilter { private Context mCont; private static FileItem parent; @@ -58,7 +58,7 @@ public void setPath(File path) { if (parent==null && mNRoot) parent = new FileItem(R.drawable.ic_folder_24, ".."); mPath = path; - File[] lst = path.listFiles(); + File[] lst = path.listFiles(Settings.show_hidden ? null : this); if (lst==null) lst = new File[0]; Arrays.sort(lst, this); @@ -76,12 +76,16 @@ public int compare(File a, File b) { :ad?-1:1; } + public boolean accept(File p1, String p2) { + return p2.charAt(0) != '.'; + } + private static int computeIcon(File f) { if (f.isDirectory()) return R.drawable.ic_folder_24; else { String n = f.getName(); - if (n.endsWith(".c")||n.endsWith(".cpp") + if (n.endsWith(".c")||n.endsWith(".cpp")||n.endsWith(".cxx") ||n.endsWith(".h")||n.endsWith(".hpp")) return R.drawable.ic_code_24; } diff --git a/app/src/main/java/cn/rbc/termuc/Lsp.java b/app/src/main/java/cn/rbc/termuc/Lsp.java index 6623ecc..a07a2e0 100644 --- a/app/src/main/java/cn/rbc/termuc/Lsp.java +++ b/app/src/main/java/cn/rbc/termuc/Lsp.java @@ -14,10 +14,11 @@ import android.util.JsonReader; import java.util.concurrent.*; -public class Lsp { +public class Lsp implements Runnable { final static int INITIALIZE = 0, INITIALIZED = 1, OPEN = 2, CLOSE = 3, - COMPLETION = 4, FIX = 5, CHANGE = 6, SAVE = 7, NOTI = 8; + COMPLETION = 4, FIX = 5, CHANGE = 6, SAVE = 7, NOTI = 8, + ERROR = -1; private final static String TAG = "LSP"; private final static byte[] CONTENTLEN = "Content-Length: ".getBytes(StandardCharsets.UTF_8); private int tp; @@ -25,58 +26,63 @@ public class Lsp { private ExecutorService mExecutor; private char[] compTrigs = {}; private long mLastReceivedTime; + private Handler mRead; - public void start(final Context mC, final Handler read) { + public void start(final Context mC, Handler read) { Utils.run(mC, "/system/bin/nc", new String[]{"-l", "-s", Settings.lsp_host, "-p", Integer.toString(Settings.lsp_port), "clangd", "--header-insertion-decorators=0", "--completion-style=bundled"}, Environment.getExternalStorageDirectory().getAbsolutePath(), true); - sk = new Socket(); mExecutor = Executors.newSingleThreadExecutor(); - new Thread(){ - public void run() { - try{ - int i=0; - for (; !sk.isConnected() && i<20; i++) { - Thread.sleep(100L); - try { - sk.connect(new InetSocketAddress(Settings.lsp_host, Settings.lsp_port)); - }catch(SocketException s){} - } - if (i==20) - throw new IOException("Connection failed"); - InputStream is = sk.getInputStream(); - byte[] b = new byte[16]; + sk = new Socket(); + mRead = read; + new Thread(this).start(); + } + + public void run() { + try{ + int i = 0; + do { + try { + sk = new Socket(Settings.lsp_host, Settings.lsp_port); + } catch (SocketException s) { + Thread.sleep(250L); + } + i++; + } while (i<=20 && !sk.isConnected()); + if (i>20) + throw new IOException("Connection failed"); + InputStream is = sk.getInputStream(); + byte[] b = new byte[16]; OUTER: while (true) { - for (i=0;i<16;i++) { - int t = is.read(); - if (t==-1) - break OUTER; - b[i] = (byte)t; - } - if (Arrays.equals(b, CONTENTLEN)) { - int len = 0; - while (Character.isDigit(i = is.read())) - len = len * 10 + i - 48; - mLastReceivedTime = System.currentTimeMillis(); - is.skip(3L); - byte[] strb = new byte[len]; - for (i=0; i hda; private FileAdapter adp; @@ -39,6 +39,7 @@ public class MainActivity extends Activity implements private ActionBar ab; private SearchAction mSearchAction; private String transStr; + private Dialog transDlg; private static MainHandler hand; public static Lsp lsp; @@ -89,7 +90,8 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_main); requestPermissions(new String[]{ android.Manifest.permission.READ_EXTERNAL_STORAGE, - android.Manifest.permission.WRITE_EXTERNAL_STORAGE + android.Manifest.permission.WRITE_EXTERNAL_STORAGE, + android.Manifest.permission.MANAGE_EXTERNAL_STORAGE }, PackageManager.PERMISSION_GRANTED); showlist = findViewById(R.id.show_list); keys = findViewById(R.id.keys); @@ -103,7 +105,6 @@ protected void onCreate(Bundle savedInstanceState) { l.setAdapter(adp); l.setOnItemClickListener(this); l.setOnItemLongClickListener(this); - refresh(); mSearchAction = new SearchAction(this); } @@ -112,7 +113,8 @@ public void onRequestPermissionsResult(int requestCode, String[] permissions, in super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode==PackageManager.PERMISSION_GRANTED && grantResults[0]==requestCode - && grantResults[1]==requestCode) + && grantResults[1]==requestCode + && grantResults[2]==requestCode) refresh(); } @@ -149,26 +151,28 @@ public void onItemClick(AdapterView av, View v, int i, long n) { pwd = new File(pwd, _it); if (pwd.isFile()) { _it = pwd.getAbsolutePath(); - int _i = hda.getCount(), _idx = -1; + int _i = hda.getCount(), _tp = -1; if (mFmgr.findFragmentByTag(_it)!=null) { for (_i--; -1<_i && !_it.equals(hda.getItem(_i)); _i--); } else { if (_it.endsWith(".c")) - _idx = EditFragment.TYPE_C; - else if (_it.endsWith(".cpp")) - _idx = EditFragment.TYPE_CPP; + _tp = EditFragment.TYPE_C; + else if (_it.endsWith(".cpp")||_it.endsWith(".cxx")) + _tp = EditFragment.TYPE_CPP; else if (_it.endsWith(".h")) - _idx = EditFragment.TYPE_H; + _tp = EditFragment.TYPE_C|EditFragment.TYPE_HEADER; else if (_it.endsWith(".hpp")) - _idx = EditFragment.TYPE_HPP; + _tp = EditFragment.TYPE_CPP|EditFragment.TYPE_HEADER; + else if (!Utils.isBlob(pwd)) + _tp = EditFragment.TYPE_OTHER|EditFragment.TYPE_HEADER; else _i = -1; - if (_idx != -1) { + if (_tp != -1) { if (!inited) { lsp.initialize(); inited = true; } - EditFragment ef = new EditFragment(pwd.getPath(), _idx); + EditFragment ef = new EditFragment(pwd.getPath(), _tp); FragmentTransaction mts = mFmgr.beginTransaction() .add(R.id.editFrag, ef, _it); if (lastFrag!=null) @@ -185,7 +189,7 @@ else if (_it.endsWith(".hpp")) ab.setDisplayShowTitleEnabled(false); msgEmpty.setVisibility(View.GONE); } - _appmenu.findItem(R.id.run).setEnabled(_idx<2); + setFileRunnable(((_tp&EditFragment.TYPE_HEADER)==0)); } } if (_i != -1) { @@ -212,30 +216,30 @@ public boolean onItemLongClick(AdapterView av, View v, final int i, long l) { @Override public boolean onMenuItemClick(MenuItem p1) { - new AlertDialog.Builder(MainActivity.this) + new AlertDialog.Builder(this) .setTitle(R.string.delete) .setMessage(getString(R.string.confirm_delete, transStr)) .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener(){ - public void onClick(DialogInterface di, int id) { - if (new File(pwd, transStr).delete()) - refresh(); - } - }) + .setPositiveButton(android.R.string.ok, this) .create().show(); return true; } + void setFileRunnable(boolean exec) { + _appmenu.findItem(R.id.run).setVisible(exec); + } + @Override public boolean onOptionsItemSelected(MenuItem menuItem) { switch (menuItem.getItemId()) { case R.id.run: try { lastFrag.save(); - lsp.didSave(lastFrag.getFile()); - Utils.run(this, new StringBuffer(Utils.PREF).append("/usr/bin/bash").toString(), new String[]{"-c", + if ((lastFrag.type&EditFragment.TYPE_MASK)!=EditFragment.TYPE_OTHER) + lsp.didSave(lastFrag.getFile()); + Utils.run(this, new StringBuilder(Utils.PREF).append("/usr/bin/bash").toString(), new String[]{"-c", new StringBuffer(lastFrag.getC()) - .append(" \"").append(escape(lastFrag.getFile().getAbsolutePath())).append("\" ") + .append(" \"").append(Utils.escape(lastFrag.getFile().getAbsolutePath())).append("\" ") .append(Settings.cflags).append(" -o $TMPDIR/m && $TMPDIR/m && echo -n \"\nPress any key to exit...\" && read").toString()}, pwd.getPath(), false); } catch (IOException e) { @@ -251,7 +255,8 @@ public boolean onOptionsItemSelected(MenuItem menuItem) { case R.id.save: try { lastFrag.save(); - lsp.didSave(lastFrag.getFile()); + if ((lastFrag.type&EditFragment.TYPE_MASK)!=EditFragment.TYPE_OTHER) + lsp.didSave(lastFrag.getFile()); toast(getText(R.string.saved)); } catch(IOException e) { e.printStackTrace(); @@ -292,9 +297,19 @@ public boolean onOptionsItemSelected(MenuItem menuItem) { public void inputKey(View view) { String charSequence = ((TextView) view).getText().toString(); - if ("⇥".equals(charSequence)) - charSequence = "\t"; - codeEditor.selectText(false); + if ("⇥".equals(charSequence)) { + if (codeEditor.isUseSpace()) { + int tabLen = codeEditor.getTabSpaces(); + int pos = codeEditor.getCaretPosition(); + Document doc = codeEditor.getText(); + int l = doc.findLineNumber(pos); + int of = doc.getLineOffset(l); + char[] cs = new char[tabLen-(pos-of)%tabLen]; + Arrays.fill(cs, ' '); + charSequence = new String(cs); + } else + charSequence = "\t"; + } codeEditor.getText().setTyping(true); codeEditor.paste(charSequence); } @@ -343,24 +358,33 @@ public void onSaveInstanceState(Bundle bundle) { } @Override - public void onClick(DialogInterface p1, int p2) { - try { - File f = new File(pwd, transTxV.getText().toString()); - if (p2 == DialogInterface.BUTTON_POSITIVE) - f.createNewFile(); - else - f.mkdir(); - refresh(); - } catch (IOException e) { - e.printStackTrace(); - toast(e.getMessage()); - } + public void onClick(DialogInterface di, int id) { + ProgressDialog pd = new ProgressDialog(MainActivity.this); + pd.setMessage(getString(R.string.deleting, transStr)); + pd.setIndeterminate(true); + pd.show(); + transDlg = pd; + new Thread(this).start(); + } + + public void run(){ + final boolean ok = Utils.removeFiles(new File(pwd, transStr)); + runOnUiThread(new Runnable(){ + public void run(){ + if (ok) { + toast(getText(R.string.deleted)); + refresh(); + } + transDlg.dismiss(); + } + }); } @Override protected void onResume() { super.onResume(); // Apply Prefs for Edits + refresh(); for (Fragment f:mFmgr.getFragments()) { TextEditor ed = (TextEditor)f.getView(); ed.setWordWrap(Settings.wordwrap); @@ -369,14 +393,30 @@ protected void onResume() { } } + private DialogInterface.OnClickListener onc = new DialogInterface.OnClickListener(){ + public void onClick(DialogInterface p1, int p2) { + try { + File f = new File(pwd, transTxV.getText().toString()); + if (p2 == DialogInterface.BUTTON_POSITIVE) + f.createNewFile(); + else + f.mkdir(); + refresh(); + } catch (IOException e) { + e.printStackTrace(); + toast(e.getMessage()); + } + } + }; + public void createFile(View view) { View inflate = View.inflate(this, R.layout.edit, null); transTxV = inflate.findViewById(R.id.edit_name); new AlertDialog.Builder(this) .setTitle(R.string.new_) .setView(inflate) - .setPositiveButton(R.string.file, this) - .setNeutralButton(R.string.folder, this) + .setPositiveButton(R.string.file, onc) + .setNeutralButton(R.string.folder, onc) .setNegativeButton(android.R.string.cancel, null) .create().show(); } @@ -400,16 +440,13 @@ protected void onStop() { super.onStop(); } - private static String escape(String str) { - return str.replace("\\", "\\\\").replace("`", "\\`").replace("$", "\\$").replace("\"", "\\\""); - } - - public void toast(CharSequence charSequence) { + private void toast(CharSequence charSequence) { HelperUtils.show(Toast.makeText(this, charSequence, 0)); } public void showList(View view) { - subc.setVisibility(subc.getVisibility()==View.VISIBLE?View.GONE:View.VISIBLE); + View v = subc; + v.setVisibility(v.getVisibility()==View.VISIBLE?View.GONE:View.VISIBLE); } public void setEditor(TextEditor edit) { diff --git a/app/src/main/java/cn/rbc/termuc/MainHandler.java b/app/src/main/java/cn/rbc/termuc/MainHandler.java index 7be9baa..422843e 100644 --- a/app/src/main/java/cn/rbc/termuc/MainHandler.java +++ b/app/src/main/java/cn/rbc/termuc/MainHandler.java @@ -48,6 +48,13 @@ public void handleMessage(Message msg) { case Lsp.INITIALIZE: ma.lsp.initialized(); break; + case Lsp.ERROR: + for (Fragment f:ma.mFmgr.getFragments()) { + TextEditor te = (TextEditor)f.getView(); + te.getText().setDiag(null); + te.invalidate(); + } + return; case Lsp.CLOSE: return; } @@ -124,7 +131,6 @@ public void handleMessage(Message msg) { if (jr.peek()==BEGIN_ARRAY) { jr.beginArray(); tmp2 = new ArrayList(); - //tmpi = ma.getEditor().getCaretPosition(); } else jr.beginObject(); stack.push(n); @@ -135,7 +141,7 @@ public void handleMessage(Message msg) { jr.close(); TextEditor te = (TextEditor)ma.mFmgr.findFragmentByTag(tag).getView(); ArrayList a = (ArrayList)tmp1; - a.sort(this); + Collections.sort(a, this); te.getText().setDiag(a); te.invalidate(); break LOOP; diff --git a/app/src/main/java/cn/rbc/termuc/SearchAction.java b/app/src/main/java/cn/rbc/termuc/SearchAction.java index a1db1d3..41aa98a 100644 --- a/app/src/main/java/cn/rbc/termuc/SearchAction.java +++ b/app/src/main/java/cn/rbc/termuc/SearchAction.java @@ -7,7 +7,7 @@ import android.text.*; import cn.rbc.codeeditor.util.*; -public class SearchAction implements ActionMode.Callback, TextWatcher { +public class SearchAction implements ActionMode.Callback, TextWatcher, TextView.OnEditorActionListener { private MainActivity ma; private EditText e; private Document dp; @@ -37,6 +37,7 @@ public boolean onCreateActionMode(ActionMode p1, Menu p2) { } else idx = 0; e.addTextChangedListener(this); + e.setOnEditorActionListener(this); e.requestFocus(); p1.setCustomView(v); dp = ma.getEditor().getText(); @@ -45,13 +46,18 @@ public boolean onCreateActionMode(ActionMode p1, Menu p2) { } public boolean onActionItemClicked(ActionMode p1, MenuItem p2) { + search(p2.getItemId()); + return false; + } + + private void search(int id) { int i; - Editable ed = e.getText(); + CharSequence ed = e.getText(); int len = ed.length(); if (len==0) - return false; + return; TextEditor te = ma.getEditor(); - if (p2.getItemId()==R.id.menu_last) { + if (id==R.id.menu_last) { i = rindexDoc(ed, idx-1); } else { i = indexDoc(ed, idx+1); @@ -64,7 +70,7 @@ public boolean onActionItemClicked(ActionMode p1, MenuItem p2) { te.setSelection(i, len); idx = i; } - return false; + return; } public void beforeTextChanged(CharSequence cs, int p1, int p2, int p3) { @@ -85,6 +91,12 @@ public void afterTextChanged(Editable ed) { } } + @Override + public boolean onEditorAction(TextView p1, int p2, KeyEvent p3) { + search(R.id.menu_next); + return true; + } + private int indexDoc(CharSequence cs, int idx) { int len = cs.length(); int i, ldp = dp.length()-len; @@ -104,8 +116,7 @@ private int indexDoc(CharSequence cs, int idx) { private int rindexDoc(CharSequence cs, int idx) { int len = cs.length(); - int i; - for (i=idx; i>=0; i--) { + for (int i=idx; i>=0; i--) { boolean b = true; for (int k=0;k + + diff --git a/app/src/main/res/drawable/ic_chevron_right_24.xml b/app/src/main/res/drawable/ic_chevron_right_24.xml new file mode 100644 index 0000000..aeda570 --- /dev/null +++ b/app/src/main/res/drawable/ic_chevron_right_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/search_action.xml b/app/src/main/res/layout/search_action.xml index 1644ed4..126ce83 100644 --- a/app/src/main/res/layout/search_action.xml +++ b/app/src/main/res/layout/search_action.xml @@ -15,6 +15,7 @@ android:maxLines="1" android:hint="@string/find_" android:id="@+id/search_edit" + android:imeOptions="actionSearch" android:layout_weight="1.0"/> diff --git a/app/src/main/res/menu/search.xml b/app/src/main/res/menu/search.xml index 802e0e9..5ee24e0 100644 --- a/app/src/main/res/menu/search.xml +++ b/app/src/main/res/menu/search.xml @@ -3,9 +3,11 @@ diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 51f6b30..e2940a6 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -29,12 +29,17 @@ 文件 文件夹 已保存 + 正在删除 %s + 已删除 打开失败! 浏览文件 名称(例如 main.cpp) 新建文件(夹) 查找… - 向后 - 向前 + 下一个 + 上一个 查找结束 + 文件浏览器 + 显示隐藏文件 + 默认字号 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0851fcd..702f574 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2,12 +2,12 @@ TermuC Settings Global - Dark Mode + Dark mode Editor - Word Wrap - Show Whitespaces + Word wrap + Show whitespaces Compiler - Compiler Flags + Compiler flags Run Undo Redo @@ -16,19 +16,21 @@ Close No Open Files Code Analysis - Auto Completion + Auto completion Engine Language Server Local Snippets None - Server Host - Server Port + Server host + Server port The file %s has been modified. Do you want to reload it? Do you want to delete %s? New File Folder Saved + Deleting %s + Deleted Open failed! Browse Files Name (eg: main.cpp) @@ -37,6 +39,9 @@ Next Prev Find Completed + File Browser + Show hidden files + Default font size @string/lang_serv @string/local @@ -47,11 +52,40 @@ l n + + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + 16 + 17 + 18 + 19 + 20 + 21 + 22 + 23 + 24 + 25 + 26 + 27 + 28 + 29 + 30 + 31 + 32 + darkmode + textsize wordwrap whitespace cflags completion lsphost lspport + showhidden diff --git a/app/src/main/res/xml/settings.xml b/app/src/main/res/xml/settings.xml index 04b4ce5..75bfbbd 100644 --- a/app/src/main/res/xml/settings.xml +++ b/app/src/main/res/xml/settings.xml @@ -1,6 +1,7 @@ + android:title="@string/settings" + android:persistent="false"> + + + + _rowTable; - public Document(TextFieldMetrics metrics) - { + public Document(TextFieldMetrics metrics) { super(); _metrics = metrics; resetRowTable(); } - public void setText(CharSequence text) - { + public void setText(CharSequence text) { int lineCount=1; int len=text.length(); char[] ca=new char[TextBuffer.memoryNeeded(len)]; - for (int i=0;i < len;i++) - { + for (int i=0;i < len;i++) { ca[i] = text.charAt(i); if (text.charAt(i) == '\n') lineCount++; @@ -51,15 +47,13 @@ public void setText(CharSequence text) setBuffer(ca, len, lineCount); } - private void resetRowTable() - { + private void resetRowTable() { ArrayList rowTable = new ArrayList(); rowTable.add(0); //every document contains at least 1 row _rowTable = rowTable; } - public void setMetrics(TextFieldMetrics metrics) - { + public void setMetrics(TextFieldMetrics metrics) { _metrics = metrics; } @@ -67,55 +61,48 @@ public void setMetrics(TextFieldMetrics metrics) * Enable/disable word wrap. If enabled, the document is immediately * analyzed for word wrap breakpoints, which might take an arbitrarily long time. */ - public void setWordWrap(boolean enable) - { - if (enable && !_isWordWrap) - { + public void setWordWrap(boolean enable) { + if (enable && !_isWordWrap) { _isWordWrap = true; analyzeWordWrap(); - } - else if (!enable && _isWordWrap) - { + } else if (!enable && _isWordWrap) { _isWordWrap = false; analyzeWordWrap(); } } - public boolean isWordWrap() - { + public boolean isWordWrap() { return _isWordWrap; } @Override - public synchronized void delete(int charOffset, int totalChars, long timestamp, boolean undoable) - { + public synchronized void delete(int charOffset, int totalChars, long timestamp, boolean undoable) { super.delete(charOffset, totalChars, timestamp, undoable); - + int startRow = findRowNumber(charOffset); int analyzeEnd = findNextLineFrom(charOffset); updateWordWrapAfterEdit(startRow, analyzeEnd, -totalChars); } @Override - public synchronized void insert(char[] c, int charOffset, long timestamp, boolean undoable) - { + public synchronized void insert(char[] c, int charOffset, long timestamp, boolean undoable) { super.insert(c, charOffset, timestamp, undoable); - + int startRow = findRowNumber(charOffset); int analyzeEnd = findNextLineFrom(charOffset + c.length); updateWordWrapAfterEdit(startRow, analyzeEnd, c.length); } - public void insertBefore(char[] cArray, int insertionPoint, long timestamp){ - if(!isValid(insertionPoint) || cArray.length == 0){ + public void insertBefore(char[] cArray, int insertionPoint, long timestamp) { + if (!isValid(insertionPoint) || cArray.length == 0) { return; } insert(cArray, insertionPoint, timestamp, true); } - public void deleteAt(int deletionPoint, long timestamp){ - if(!isValid(deletionPoint)){ + public void deleteAt(int deletionPoint, long timestamp) { + if (!isValid(deletionPoint)) { return; } delete(deletionPoint, 1, timestamp, true); @@ -126,8 +113,8 @@ public void deleteAt(int deletionPoint, long timestamp){ * Deletes up to maxChars number of characters starting from deletionPoint * If deletionPoint is invalid, or maxChars is not positive, nothing happens. */ - public void deleteAt(int deletionPoint, int maxChars, long time){ - if(!isValid(deletionPoint) || maxChars <= 0){ + public void deleteAt(int deletionPoint, int maxChars, long time) { + if (!isValid(deletionPoint) || maxChars <= 0) { return; } int totalChars = Math.min(maxChars, getTextLength() - deletionPoint); @@ -141,12 +128,10 @@ public void deleteAt(int deletionPoint, int maxChars, long time){ * Only UndoStack should use this method to carry out a simple undo/redo * of insertions/deletions. No error checking is done. */ - synchronized void shiftGapStart(int displacement) - { + synchronized void shiftGapStart(int displacement) { super.shiftGapStart(displacement); - - if (displacement != 0) - { + + if (displacement != 0) { int startOffset = (displacement > 0) ? _gapStartIndex - displacement : _gapStartIndex; @@ -157,21 +142,17 @@ synchronized void shiftGapStart(int displacement) } //No error checking is done on parameters. - private int findNextLineFrom(int charOffset) - { + private int findNextLineFrom(int charOffset) { int lineEnd = logicalToRealIndex(charOffset); - while (lineEnd < _contents.length) - { + while (lineEnd < _contents.length) { // skip the gap - if (lineEnd == _gapStartIndex) - { + if (lineEnd == _gapStartIndex) { lineEnd = _gapEndIndex; } if (_contents[lineEnd] == Language.NEWLINE || - _contents[lineEnd] == Language.EOF) - { + _contents[lineEnd] == Language.EOF) { break; } @@ -181,10 +162,8 @@ private int findNextLineFrom(int charOffset) return realToLogicalIndex(lineEnd) + 1; } - private void updateWordWrapAfterEdit(int startRow, int analyzeEnd, int delta) - { - if (startRow > 0) - { + private void updateWordWrapAfterEdit(int startRow, int analyzeEnd, int delta) { + if (startRow > 0) { // if the first word becomes shorter or an inserted space breaks it // up, it may fit the previous line, so analyse that line too --startRow; @@ -203,32 +182,25 @@ private void updateWordWrapAfterEdit(int startRow, int analyzeEnd, int delta) * * No error checking is done on parameters. */ - private void removeRowMetadata(int fromRow, int endOffset) - { + private void removeRowMetadata(int fromRow, int endOffset) { while (fromRow < _rowTable.size() && - _rowTable.get(fromRow) <= endOffset) - { + _rowTable.get(fromRow) <= endOffset) { _rowTable.remove(fromRow); } } - private void adjustOffsetOfRowsFrom(int fromRow, int offset) - { - for (int i = fromRow; i < _rowTable.size(); ++i) - { + private void adjustOffsetOfRowsFrom(int fromRow, int offset) { + for (int i = fromRow; i < _rowTable.size(); ++i) { _rowTable.set(i, _rowTable.get(i) + offset); } } - public void analyzeWordWrap() - { - + public void analyzeWordWrap() { + resetRowTable(); - if (_isWordWrap&&!hasMinimumWidthForWordWrap()) - { - if (_metrics.getRowWidth() > 0) - { + if (_isWordWrap && !hasMinimumWidthForWordWrap()) { + if (_metrics.getRowWidth() > 0) { TextWarriorException.fail("Text field has non-zero width but still too small for word wrap"); } // _metrics.getRowWidth() might legitmately be zero when the text field has not been layout yet @@ -238,8 +210,7 @@ public void analyzeWordWrap() analyzeWordWrap(1, 0, getTextLength()); } - private boolean hasMinimumWidthForWordWrap() - { + private boolean hasMinimumWidthForWordWrap() { final int maxWidth = _metrics.getRowWidth(); //assume the widest char is 2ems wide return (maxWidth >= 2 * _metrics.getAdvance('M')); @@ -248,35 +219,29 @@ private boolean hasMinimumWidthForWordWrap() //No error checking is done on parameters. //A word consists of a sequence of 0 or more non-whitespace characters followed by //exactly one whitespace character. Note that EOF is considered whitespace. - private void analyzeWordWrap(int rowIndex, int startOffset, int endOffset) - { - if (!_isWordWrap) - { + private void analyzeWordWrap(int rowIndex, int startOffset, int endOffset) { + if (!_isWordWrap) { int offset = logicalToRealIndex(startOffset); int end = logicalToRealIndex(endOffset); ArrayList rowTable = new ArrayList(); - - while (offset < end) - { + + while (offset < end) { // skip the gap - if (offset == _gapStartIndex) - { + if (offset == _gapStartIndex) { offset = _gapEndIndex; } char c = _contents[offset]; - if (c == Language.NEWLINE) - { + if (c == Language.NEWLINE) { //start a new row rowTable.add(realToLogicalIndex(offset) + 1); } ++offset; - + } _rowTable.addAll(rowIndex, rowTable); return; } - if (!hasMinimumWidthForWordWrap()) - { + if (!hasMinimumWidthForWordWrap()) { TextWarriorException.fail("Not enough space to do word wrap"); return; } @@ -289,64 +254,49 @@ private void analyzeWordWrap(int rowIndex, int startOffset, int endOffset) final int maxWidth = _metrics.getRowWidth(); int remainingWidth = maxWidth; - while (offset < end) - { + while (offset < end) { // skip the gap - if (offset == _gapStartIndex) - { + if (offset == _gapStartIndex) { offset = _gapEndIndex; } char c = _contents[offset]; wordExtent += _metrics.getAdvance(c); + boolean isWhitespace = ".,; \t\n\uFFFF".indexOf(c)>=0; + //(c == ' ' || c == Language.TAB|| c == Language.NEWLINE || c == Language.EOF); - boolean isWhitespace = (c == ' ' || c == Language.TAB - || c == Language.NEWLINE || c == Language.EOF); - - if (isWhitespace) - { + if (isWhitespace) { //full word obtained - if (wordExtent <= remainingWidth) - { + if (wordExtent <= remainingWidth) { remainingWidth -= wordExtent; - } - else if (wordExtent > maxWidth) - { + } else if (wordExtent > maxWidth) { //handle a word too long to fit on one row int current = logicalToRealIndex(potentialBreakPoint); remainingWidth = maxWidth; //start the word on a new row, if it isn't already if (potentialBreakPoint != startOffset && (rowTable.isEmpty() || - potentialBreakPoint != rowTable.get(rowTable.size() - 1))) - { + potentialBreakPoint != rowTable.get(rowTable.size() - 1))) { rowTable.add(potentialBreakPoint); } - while (current <= offset) - { + while (current <= offset) { // skip the gap - if (current == _gapStartIndex) - { + if (current == _gapStartIndex) { current = _gapEndIndex; } int advance = _metrics.getAdvance(_contents[current]); - if (advance > remainingWidth) - { + if (advance > remainingWidth) { rowTable.add(realToLogicalIndex(current)); remainingWidth = maxWidth - advance; - } - else - { + } else { remainingWidth -= advance; } ++current; } - } - else - { + } else { //invariant: potentialBreakPoint != startOffset //put the word on a new row rowTable.add(potentialBreakPoint); @@ -357,8 +307,7 @@ else if (wordExtent > maxWidth) potentialBreakPoint = realToLogicalIndex(offset) + 1; } - if (c == Language.NEWLINE) - { + if (c == Language.NEWLINE) { //start a new row rowTable.add(potentialBreakPoint); remainingWidth = maxWidth; @@ -371,12 +320,10 @@ else if (wordExtent > maxWidth) _rowTable.addAll(rowIndex, rowTable); } - public String getRow(int rowNumber) - { + public String getRow(int rowNumber) { int rowSize = getRowSize(rowNumber); - if (rowSize == 0) - { + if (rowSize == 0) { return new String(); } @@ -384,37 +331,29 @@ public String getRow(int rowNumber) return subSequence(startIndex, rowSize).toString(); } - public int getRowSize(int rowNumber) - { + public int getRowSize(int rowNumber) { - if (isInvalidRow(rowNumber)) - { + if (isInvalidRow(rowNumber)) { return 0; } - if (rowNumber != (_rowTable.size() - 1)) - { + if (rowNumber != (_rowTable.size() - 1)) { return _rowTable.get(rowNumber + 1) - _rowTable.get(rowNumber); - } - else - { + } else { //last row return getTextLength() - _rowTable.get(rowNumber); } } - public int getRowCount() - { + public int getRowCount() { return _rowTable.size(); } - public int getRowOffset(int rowNumber) - { + public int getRowOffset(int rowNumber) { - if (isInvalidRow(rowNumber)) - { + if (isInvalidRow(rowNumber)) { return -1; } @@ -426,32 +365,25 @@ public int getRowOffset(int rowNumber) * * @return The row number that charOffset is on, or -1 if charOffset is invalid */ - public int findRowNumber(int charOffset) - { + public int findRowNumber(int charOffset) { - if (!isValid(charOffset)) - { + if (!isValid(charOffset)) { return -1; } //binary search of _rowTable int right = _rowTable.size() - 1; int left = 0; - while (right >= left) - { + while (right >= left) { int mid = (left + right) / 2; int nextLineOffset = ((mid + 1) < _rowTable.size()) ? _rowTable.get(mid + 1) : getTextLength(); - if (charOffset >= _rowTable.get(mid) && charOffset < nextLineOffset) - { + if (charOffset >= _rowTable.get(mid) && charOffset < nextLineOffset) { return mid; } - if (charOffset >= nextLineOffset) - { + if (charOffset >= nextLineOffset) { left = mid + 1; - } - else - { + } else { right = mid - 1; } } @@ -461,15 +393,13 @@ public int findRowNumber(int charOffset) } - protected boolean isInvalidRow(int rowNumber) - { + protected boolean isInvalidRow(int rowNumber) { return rowNumber < 0 || rowNumber >= _rowTable.size(); } - public static interface TextFieldMetrics - { + public static interface TextFieldMetrics { /** * Returns printed width of c. * diff --git a/codeeditor/src/main/java/cn/rbc/codeeditor/util/TextBuffer.java b/codeeditor/src/main/java/cn/rbc/codeeditor/util/TextBuffer.java index c350e2e..dea2195 100644 --- a/codeeditor/src/main/java/cn/rbc/codeeditor/util/TextBuffer.java +++ b/codeeditor/src/main/java/cn/rbc/codeeditor/util/TextBuffer.java @@ -254,7 +254,6 @@ synchronized public int findLineNumber(int charOffset){ } } - if (offset == targetOffset) { if(lastKnownLine != -1) // cache the lookup entry diff --git a/codeeditor/src/main/java/cn/rbc/codeeditor/util/Tokenizer.java b/codeeditor/src/main/java/cn/rbc/codeeditor/util/Tokenizer.java index eeef4b2..6b9d17b 100644 --- a/codeeditor/src/main/java/cn/rbc/codeeditor/util/Tokenizer.java +++ b/codeeditor/src/main/java/cn/rbc/codeeditor/util/Tokenizer.java @@ -21,14 +21,16 @@ * The programming language syntax used is set as a static class variable. */ public class Tokenizer { - public final static int UNKNOWN = -1; - public final static int NORMAL = 0; - public final static int KEYWORD = 1; - public final static int OPERATOR = 2; - public final static int NAME = 3; - public final static int NUMBER = 4; - public final static int KEYNAME = 5; - public final static int TYPE = 6; + public final static int + UNKNOWN = -1, + NORMAL = 0, + KEYWORD = 1, + OPERATOR = 2, + NOTE = 3, + NAME = 4, + NUMBER = 5, + KEYNAME = 6, + TYPE = 7; final static int NUM_HEX = 1; final static int NUM_FLOAT = 2; final static int NUM_EXP = 4; @@ -88,8 +90,12 @@ synchronized public static void setLanguage(Language lang) { } public void tokenize(Document hDoc) { - if (!Tokenizer.getLanguage().isProgLang()) + if (!Tokenizer.getLanguage().isProgLang()) { + List tokens = new ArrayList<>(); + tokens.add(new Pair(0, Tokenizer.NORMAL)); + tokenizeDone(tokens); return; + } //tokenize will modify the state of hDoc; make a copy setDocument(hDoc); @@ -190,32 +196,32 @@ public void tokenize() { switch (type) { case Lexer.KEYWORD: - tokens.add(new Pair(idx, Tokenizer.KEYWORD)); + tokens.add(new Pair(idx, KEYWORD)); break; case Lexer.TYPE: - tokens.add(new Pair(idx, Tokenizer.TYPE)); + tokens.add(new Pair(idx, TYPE)); break; case Lexer.COMMENT: - tokens.add(new Pair(idx, Tokenizer.DOUBLE_SYMBOL_DELIMITED_MULTILINE)); + tokens.add(new Pair(idx, DOUBLE_SYMBOL_DELIMITED_MULTILINE)); break; // macro case Lexer.PRETREATMENT_LINE: case Lexer.DEFINE_LINE: - tokens.add(new Pair(idx, Tokenizer.SINGLE_SYMBOL_LINE_A)); + tokens.add(new Pair(idx, SINGLE_SYMBOL_LINE_A)); break; // string, char case Lexer.STRING_LITERAL: case Lexer.CHARACTER_LITERAL: - tokens.add(new Pair(idx, Tokenizer.SINGLE_SYMBOL_DELIMITED_A)); + tokens.add(new Pair(idx, SINGLE_SYMBOL_DELIMITED_A)); break; // number case Lexer.INTEGER_LITERAL: case Lexer.FLOATING_POINT_LITERAL: - tokens.add(new Pair(idx, Tokenizer.NUMBER)); + tokens.add(new Pair(idx, NUMBER)); break; case Lexer.IDENTIFIER: identifier=lexer.yytext(); - tokens.add(new Pair(idx, Tokenizer.NORMAL)); + tokens.add(new Pair(idx, NORMAL)); break; // symbols case Lexer.LPAREN:// ( @@ -224,6 +230,7 @@ public void tokenize() { case Lexer.RBRACK:// ] case Lexer.LBRACE:// { case Lexer.RBRACE:// } + case Lexer.DOT: // . case Lexer.COMMA:// , case Lexer.WHITE_CHAR:// ' ' case Lexer.SEMICOLON:// ; @@ -233,10 +240,10 @@ public void tokenize() { language.updateUserWord(); identifier=null; } - tokens.add(new Pair(idx, Tokenizer.OPERATOR)); + tokens.add(new Pair(idx, type==Lexer.OPERATOR ? OPERATOR : NOTE)); break; default: - tokens.add(new Pair(idx, Tokenizer.NORMAL)); + tokens.add(new Pair(idx, NORMAL)); } lastCtype = type; idx += lexer.yylength(); diff --git a/codeeditor/src/main/java/cn/rbc/codeeditor/view/ClipboardPanel.java b/codeeditor/src/main/java/cn/rbc/codeeditor/view/ClipboardPanel.java index 6d8d825..5225186 100644 --- a/codeeditor/src/main/java/cn/rbc/codeeditor/view/ClipboardPanel.java +++ b/codeeditor/src/main/java/cn/rbc/codeeditor/view/ClipboardPanel.java @@ -11,7 +11,7 @@ import cn.rbc.termuc.R; -public class ClipboardPanel { +public class ClipboardPanel implements ActionMode.Callback { protected FreeScrollingTextField _textField; private Context _context; @@ -42,121 +42,120 @@ public void hide() { public void startClipboardAction() { if (_clipboardActionMode == null) - _textField.startActionMode(new ActionMode.Callback() { - - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - _clipboardActionMode = mode; - //mode.setTitle(android.R.string.selectTextMode); - TypedArray array = _context.getTheme().obtainStyledAttributes(new int[]{ - android.R.attr.actionModeSelectAllDrawable, - android.R.attr.actionModeCutDrawable, - android.R.attr.actionModeCopyDrawable, - android.R.attr.actionModePasteDrawable - }); - menu.add(0, 0, 0, _context.getString(android.R.string.selectAll)) - .setShowAsActionFlags(2) - .setAlphabeticShortcut('a') - .setIcon(array.getDrawable(0)); - - menu.add(0, 1, 0, _context.getString(android.R.string.cut)) - .setShowAsActionFlags(2) - .setAlphabeticShortcut('x') - .setIcon(array.getDrawable(1)); - - menu.add(0, 2, 0, _context.getString(android.R.string.copy)) - .setShowAsActionFlags(2) - .setAlphabeticShortcut('c') - .setIcon(array.getDrawable(2)); - - menu.add(0, 3, 0, _context.getString(android.R.string.paste)) - .setShowAsActionFlags(2) - .setAlphabeticShortcut('v') - .setIcon(array.getDrawable(3)); - - menu.add(0, 4, 0, _context.getString(R.string.delete)) - .setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_IF_ROOM) - .setAlphabeticShortcut('d'); - - menu.add(0, 5, 0, _context.getString(R.string.format)) - .setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_IF_ROOM) - .setAlphabeticShortcut('f'); - array.recycle(); - return true; - } + _textField.startActionMode(this); + } - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - // TODO: Implement this method - return false; - } + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + _clipboardActionMode = mode; + //mode.setTitle(android.R.string.selectTextMode); + TypedArray array = _context.getTheme().obtainStyledAttributes( + new int[]{ + android.R.attr.actionModeSelectAllDrawable, + android.R.attr.actionModeCutDrawable, + android.R.attr.actionModeCopyDrawable, + android.R.attr.actionModePasteDrawable + }); + menu.add(0, 0, 0, _context.getString(android.R.string.selectAll)) + .setShowAsActionFlags(2) + .setAlphabeticShortcut('a') + .setIcon(array.getDrawable(0)); + + menu.add(0, 1, 0, _context.getString(android.R.string.cut)) + .setShowAsActionFlags(2) + .setAlphabeticShortcut('x') + .setIcon(array.getDrawable(1)); + + menu.add(0, 2, 0, _context.getString(android.R.string.copy)) + .setShowAsActionFlags(2) + .setAlphabeticShortcut('c') + .setIcon(array.getDrawable(2)); + + menu.add(0, 3, 0, _context.getString(android.R.string.paste)) + .setShowAsActionFlags(2) + .setAlphabeticShortcut('v') + .setIcon(array.getDrawable(3)); + + menu.add(0, 4, 0, _context.getString(R.string.delete)) + .setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_IF_ROOM) + .setAlphabeticShortcut('d'); + + menu.add(0, 5, 0, _context.getString(R.string.format)) + .setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_IF_ROOM) + .setAlphabeticShortcut('f'); + array.recycle(); + return true; + } - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - switch (item.getItemId()) { - case 0: - _textField.selectAll(); - break; - case 1: - _textField.cut(); - mode.finish(); - break; - case 2: - _textField.copy(); - mode.finish(); - break; - case 3: - _textField.paste(); - mode.finish(); - break; - case 4: - _textField.delete(); - mode.finish(); - break; - case 5: - _textField.format(); - mode.finish(); - } - return false; - } + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + // TODO: Implement this method + return false; + } - @Override - public void onDestroyActionMode(ActionMode p1) { - _textField.selectText(false); - _clipboardActionMode = null; - } - }); + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + switch (item.getItemId()) { + case 0: + _textField.selectAll(); + break; + case 1: + _textField.cut(); + mode.finish(); + break; + case 2: + _textField.copy(); + mode.finish(); + break; + case 3: + _textField.paste(); + mode.finish(); + break; + case 4: + _textField.delete(); + mode.finish(); + break; + case 5: + _textField.format(); + mode.finish(); + } + return false; + } - } + @Override + public void onDestroyActionMode(ActionMode p1) { + _textField.selectText(false); + _clipboardActionMode = null; + } public void startClipboardActionNew() { if (_clipboardActionMode == null) _textField.startActionMode(_clipboardActionModeCallback2, ActionMode.TYPE_FLOATING); } - private void initData(){ + private void initData() { _clipboardActionModeCallback2 = new ActionMode.Callback2() { @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { _clipboardActionMode = mode; menu.add(0, 0, 0, _context.getString(android.R.string.selectAll)) - .setShowAsActionFlags(2) - .setAlphabeticShortcut('a'); + .setShowAsActionFlags(2) + .setAlphabeticShortcut('a'); menu.add(0, 1, 0, _context.getString(android.R.string.cut)) - .setShowAsActionFlags(2) - .setAlphabeticShortcut('x'); + .setShowAsActionFlags(2) + .setAlphabeticShortcut('x'); menu.add(0, 2, 0, _context.getString(android.R.string.copy)) - .setShowAsActionFlags(2) - .setAlphabeticShortcut('c'); + .setShowAsActionFlags(2) + .setAlphabeticShortcut('c'); menu.add(0, 3, 0, _context.getString(android.R.string.paste)) - .setShowAsActionFlags(2) - .setAlphabeticShortcut('v'); + .setShowAsActionFlags(2) + .setAlphabeticShortcut('v'); menu.add(0, 4, 0, _context.getString(R.string.delete)) - .setShowAsActionFlags(2) - .setAlphabeticShortcut('d'); + .setShowAsActionFlags(2) + .setAlphabeticShortcut('d'); menu.add(0, 5, 0, _context.getString(R.string.format)) - .setShowAsActionFlags(2) - .setAlphabeticShortcut('f'); + .setShowAsActionFlags(2) + .setAlphabeticShortcut('f'); return true; } @@ -203,7 +202,7 @@ public void onDestroyActionMode(ActionMode p1) { } @Override - public void onGetContentRect(ActionMode mode, View view, Rect outRect){ + public void onGetContentRect(ActionMode mode, View view, Rect outRect) { caret = _textField.getBoundingBox(_textField.getCaretPosition()); caret.top -= _textField.getScrollY(); caret.left += _textField.getScrollX(); diff --git a/codeeditor/src/main/java/cn/rbc/codeeditor/view/ColorScheme.java b/codeeditor/src/main/java/cn/rbc/codeeditor/view/ColorScheme.java index c694443..3768c09 100644 --- a/codeeditor/src/main/java/cn/rbc/codeeditor/view/ColorScheme.java +++ b/codeeditor/src/main/java/cn/rbc/codeeditor/view/ColorScheme.java @@ -20,7 +20,7 @@ public enum Colorable { FOREGROUND, BACKGROUND, SELECTION_FOREGROUND, SELECTION_BACKGROUND, CARET_FOREGROUND, CARET_BACKGROUND, CARET_DISABLED, LINE_HIGHLIGHT, NON_PRINTING_GLYPH, COMMENT, KEYWORD, NAME, NUMBER, STRING, - SECONDARY, TYPE + SECONDARY, TYPE, NOTE, OPERATOR } protected HashMap _colors = generateDefaultColors(); @@ -66,9 +66,14 @@ public int getTokenColor(int tokenType) { case Tokenizer.NUMBER: element = Colorable.NUMBER; break; + case Tokenizer.NOTE: + element = Colorable.NOTE; + break; + case Tokenizer.OPERATOR: + element = Colorable.OPERATOR; + break; case Tokenizer.SINGLE_SYMBOL_LINE_A: //fall-through case Tokenizer.SINGLE_SYMBOL_WORD: - case Tokenizer.OPERATOR: element = Colorable.SECONDARY; break; case Tokenizer.SINGLE_SYMBOL_LINE_B: //类型 diff --git a/codeeditor/src/main/java/cn/rbc/codeeditor/view/ColorSchemeDark.java b/codeeditor/src/main/java/cn/rbc/codeeditor/view/ColorSchemeDark.java index 252d704..e103e13 100644 --- a/codeeditor/src/main/java/cn/rbc/codeeditor/view/ColorSchemeDark.java +++ b/codeeditor/src/main/java/cn/rbc/codeeditor/view/ColorSchemeDark.java @@ -10,6 +10,8 @@ private ColorSchemeDark() { setColor(Colorable.BACKGROUND, 0xFF2B2B2B); setColor(Colorable.TYPE, 0xFF99CCEE); setColor(Colorable.KEYWORD, 0xFF6AB0E2); + setColor(Colorable.NOTE, 0xFF8AB0E2); + setColor(Colorable.OPERATOR, 0xFF8AB0E2); setColor(Colorable.SECONDARY, 0xFFAAAAAA); setColor(Colorable.COMMENT, 0xFF50BB50); setColor(Colorable.STRING, 0xFFFF8E8E); diff --git a/codeeditor/src/main/java/cn/rbc/codeeditor/view/ColorSchemeLight.java b/codeeditor/src/main/java/cn/rbc/codeeditor/view/ColorSchemeLight.java index 4a21ea9..367d935 100644 --- a/codeeditor/src/main/java/cn/rbc/codeeditor/view/ColorSchemeLight.java +++ b/codeeditor/src/main/java/cn/rbc/codeeditor/view/ColorSchemeLight.java @@ -33,7 +33,11 @@ public ColorSchemeLight(){ setColor(Colorable.NUMBER, 0xFFAA2200); //类型 setColor(Colorable.TYPE, BLUE_LIGHT); //0xFF2A40FF); - //符号 + //操作符 + setColor(Colorable.OPERATOR, 0xFF007C1F); + //标点 + setColor(Colorable.NOTE, 0xFF0096FF); + //宏 setColor(Colorable.SECONDARY, GREY); //光标 setColor(Colorable.CARET_DISABLED, 0xFF000000); diff --git a/codeeditor/src/main/java/cn/rbc/codeeditor/view/FreeScrollingTextField.java b/codeeditor/src/main/java/cn/rbc/codeeditor/view/FreeScrollingTextField.java index 80b7f60..836ccea 100644 --- a/codeeditor/src/main/java/cn/rbc/codeeditor/view/FreeScrollingTextField.java +++ b/codeeditor/src/main/java/cn/rbc/codeeditor/view/FreeScrollingTextField.java @@ -112,7 +112,9 @@ * this extra char. Some bounds manipulation is done so that this implementation * detail is hidden from client classes. */ -public abstract class FreeScrollingTextField extends View implements Document.TextFieldMetrics { +public abstract class FreeScrollingTextField extends View + implements Document.TextFieldMetrics, OnRowChangedListener, OnSelectionChangedListener, + DialogInterface.OnDismissListener, Runnable { //--------------------------------------------------------------------- //-------------------------- Caret Scroll --------------------------- @@ -255,14 +257,8 @@ public abstract class FreeScrollingTextField extends View implements Document.Te private boolean isLayout = false; private boolean isTextChanged = false; private boolean isCaretScrolled = false; - private final Runnable mScrollCaretDownTask = new Runnable() { - @Override - public void run() { - mCtrlr.moveCaretDown(); - if (!caretOnLastRowOfFile()) - postDelayed(mScrollCaretDownTask, SCROLL_PERIOD); - } - }; + private boolean useSpace = false; + private final Runnable mScrollCaretDownTask = this; private final Runnable mScrollCaretUpTask = new Runnable() { @Override public void run() { @@ -471,22 +467,9 @@ private void initView(Context context) { mSizeMin = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 8.f, dm); mSizeMax = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 32.f, dm); - mRowListener = new OnRowChangedListener() { - @Override - public void onRowChanged(int newRowIndex) { - // Do nothing - } - }; - - selLis = new OnSelectionChangedListener() { - @Override - public void onSelectionChanged(boolean active, int selStart, int selEnd) { - if (active) - mClipboardPanel.show(); - else - mClipboardPanel.hide(); - } - }; + mRowListener = this; + + selLis = this; resetView(); mClipboardPanel = new ClipboardPanel(this); @@ -496,6 +479,23 @@ public void onSelectionChanged(boolean active, int selStart, int selEnd) { invalidate(); } + public void run() { + mCtrlr.moveCaretDown(); + if (!caretOnLastRowOfFile()) + postDelayed(mScrollCaretDownTask, SCROLL_PERIOD); + } + + public void onRowChanged(int newRowIndex) { + // Do nothing + } + + public void onSelectionChanged(boolean active, int selStart, int selEnd) { + if (active) + mClipboardPanel.show(); + else + mClipboardPanel.hide(); + } + public void onNewLine(String s, int caretPosition, int pos) { isTextChanged = true; mCaretSpan.first += 1; @@ -816,7 +816,6 @@ private void realDraw(Canvas canvas) { // row by row int rowEnd = 0; for (m=-1; currRowNum <= endRowNum && currIndex < mL; currRowNum++) { - if (showLN && currLineNum != lastLineNum) { String num = String.valueOf(currLineNum); int padx = (int) (mLeftOffset - mTextPaint.measureText(num) - mSpaceWidth); @@ -832,18 +831,14 @@ private void realDraw(Canvas canvas) { // check if formatting changes are needed if (reachedNextSpan(currIndex, nextSpan)) { currSpan = nextSpan; - lastType = currType; currType = currSpan.second; - spanColor = mColorScheme.getTokenColor(currSpan.second); mTextPaint.setColor(spanColor); if (lastType != currType) { Typeface currTypeface = currType==Tokenizer.KEYWORD ? boldTypeface : defTypeface; - if (mTextPaint.getTypeface() != currTypeface) mTextPaint.setTypeface(currTypeface); - spanColor = mColorScheme.getTokenColor(currType); mTextPaint.setColor(spanColor); } @@ -1315,7 +1310,6 @@ private int makeCharColumnVisible(int charOffset) { int charLeft = visibleRange.first; int charRight = visibleRange.second; - if (isCaretScrolled) { // 拖动水滴滚动在距离SCROLL_EDGE_SLOP / 3的时候就开始滚动 if (charRight + SCROLL_EDGE_SLOP / 3 >= (getScrollX() + getContentWidth())) @@ -2028,6 +2022,17 @@ public void setTabSpaces(int spaceCount) { invalidate(); } + public int getTabSpaces() { + return mTabLength; + } + + public void setUseSpace(boolean enable) { + useSpace = enable; + } + + public boolean isUseSpace() { + return useSpace; + } /** * Enable/disable auto-indent */ @@ -2197,27 +2202,29 @@ private void handleLongPressDialogDisplay(char c) { * with the user-selected char. If false, the user-selected char will * be inserted at the caret position. */ + private boolean mTransBool; + private CharSequence mTransTx; + private void showCharacterPicker(String candidates, boolean replace) { - final boolean shouldReplace = replace; - final SpannableStringBuilder dummyString = new SpannableStringBuilder(); + mTransBool = replace; + SpannableStringBuilder dummyString = new SpannableStringBuilder(); Selection.setSelection(dummyString, 0); CharacterPickerDialog dialog = new CharacterPickerDialog(getContext(), this, dummyString, candidates, true); - - dialog.setOnDismissListener(new OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - if (dummyString.length() > 0) { - if (shouldReplace) - mCtrlr.onPrintableChar(Language.BACKSPACE); - mCtrlr.onPrintableChar(dummyString.charAt(0)); - } - } - }); + mTransTx = dummyString; + dialog.setOnDismissListener(this); dialog.show(); } + public void onDismiss(DialogInterface dialog) { + if (mTransTx.length() > 0) { + if (mTransBool) + mCtrlr.onPrintableChar(Language.BACKSPACE); + mCtrlr.onPrintableChar(mTransTx.charAt(0)); + } + } + @Override public boolean onKeyUp(int keyCode, KeyEvent event) { if (mNavMethod.onKeyUp(keyCode, event)) diff --git a/codeeditor/src/main/java/cn/rbc/codeeditor/view/TextFieldController.java b/codeeditor/src/main/java/cn/rbc/codeeditor/view/TextFieldController.java index 3436ca7..98ab247 100644 --- a/codeeditor/src/main/java/cn/rbc/codeeditor/view/TextFieldController.java +++ b/codeeditor/src/main/java/cn/rbc/codeeditor/view/TextFieldController.java @@ -14,16 +14,19 @@ import static cn.rbc.codeeditor.util.DLog.log; import android.util.*; import cn.rbc.codeeditor.util.*; +import java.util.*; +import android.widget.*; //********************************************************************* //************************ Controller logic *************************** //********************************************************************* -public class TextFieldController implements Tokenizer.LexCallback { +public class TextFieldController implements Tokenizer.LexCallback, Runnable { private final Tokenizer _lexer = new Tokenizer(this); public boolean _isInSelectionMode = false; private boolean _isInSelectionMode2; public boolean lexing; private FreeScrollingTextField field; + private List mRes; public TextFieldController(FreeScrollingTextField textField) { field = textField; @@ -50,16 +53,16 @@ public void cancelSpanning() { @Override //This is usually called from a non-UI thread public void lexDone(final List results) { - field.post(new Runnable() { - @Override - public void run() { - field.hDoc.setSpans(results); - lexing = false; - field.invalidate(); - } - }); + mRes = results; + field.post(this); } + public void run() { + field.hDoc.setSpans(mRes); + lexing = false; + field.invalidate(); + } + //- TextFieldController ----------------------------------------------- //---------------------------- Key presses ---------------------------- @@ -72,9 +75,9 @@ public void onPrintableChar(char c) { selectionDelete(); selectionDeleted = true; } - - int pos = field.mCaretPosition; - field.hDoc.setTyping(true); + FreeScrollingTextField fld = field; + int pos = fld.mCaretPosition; + fld.hDoc.setTyping(true); switch (c) { case Language.BACKSPACE: if (selectionDeleted) @@ -82,28 +85,41 @@ public void onPrintableChar(char c) { if (pos > 0) { //pos--; //char p; - int l = pos > 1 && ((c=field.hDoc.charAt(pos-2)) == 0xd83d || c == 0xd83c)? 2 : 1; + int l = pos > 1 && ((c = fld.hDoc.charAt(pos - 2)) == 0xd83d || c == 0xd83c) ? 2 : 1; //String s = - field.hDoc.deleteAt(pos-l, l, System.nanoTime()); + fld.hDoc.deleteAt(pos - l, l, System.nanoTime()); /*if (pos>0 && field.hDoc.charAt(--pos) == 0xd83d || field.hDoc.charAt(pos) == 0xd83c) { - field.hDoc.deleteAt(pos, System.nanoTime()); - moveCaretLeft(true); - }*/ + field.hDoc.deleteAt(pos, System.nanoTime()); + moveCaretLeft(true); + }*/ - field.onDel(String.valueOf(c), field.mCaretPosition, 1); + fld.onDel(String.valueOf(c), fld.mCaretPosition, 1); moveCaretLeft(true); - if (l==2) + if (l == 2) moveCaretLeft(true); } break; - case Language.NEWLINE: - if (field.isAutoIndent) { + if (fld.isAutoIndent) { char[] indent = createAutoIndent(); field.hDoc.insertBefore(indent, pos, System.nanoTime()); moveCaret(field.mCaretPosition + indent.length); break; } + field.hDoc.insertBefore(new char[]{c}, pos, System.nanoTime()); + moveCaretRight(true); + field.onAdd(String.valueOf(c), pos, 1); + break; + case Language.TAB: + if (fld.isUseSpace()) { + int tl = fld.mTabLength; + char[] cs = new char[tl - pos % tl]; + Arrays.fill(cs, ' '); + field.hDoc.insertBefore(cs, pos, System.nanoTime()); + moveCaret(pos + cs.length); + field.onAdd(new String(cs), pos, cs.length); + break; + } default: field.hDoc.insertBefore(new char[]{c}, pos, System.nanoTime()); moveCaretRight(true); @@ -122,40 +138,48 @@ public void onPrintableChar(char c) { * 创建自动缩进 */ private char[] createAutoIndent() { - int lineNum = field.hDoc.findLineNumber(field.mCaretPosition); - int startOfLine = field.hDoc.getLineOffset(lineNum); + Document doc = field.hDoc; + int pos = field.mCaretPosition; + int lineNum = doc.findLineNumber(pos); + int startOfLine = doc.getLineOffset(lineNum); int whitespaceCount = 0; //查找上一行的空白符个数 - int i, mL = field.hDoc.getTextLength(); + int i, mL = doc.getTextLength(), mTL = field.mTabLength; char c; - for (i=startOfLine; i= field.mCaretPosition) + for (i = startOfLine; i < pos;) { + c = doc.charAt(i++); + if (c != ' ' && c != Language.TAB) break; if (c == Language.TAB) - whitespaceCount += field.getAutoIndentWidth(); + whitespaceCount += mTL-whitespaceCount%mTL; else if (c == ' ') ++whitespaceCount; } //寻找最后字符 int endChar = 0; - for (i=startOfLine;i (docLength - 1)) - //exclude the terminal EOF + //exclude the terminal EOF return field.hDoc.subSequence(field.mCaretPosition, docLength - field.mCaretPosition - 1).toString(); return field.hDoc.subSequence(field.mCaretPosition, maxLen).toString(); diff --git a/codeeditor/src/main/java/cn/rbc/codeeditor/view/autocomplete/AutoPanelAdapter.java b/codeeditor/src/main/java/cn/rbc/codeeditor/view/autocomplete/AutoPanelAdapter.java index a2f0f1b..58269e0 100644 --- a/codeeditor/src/main/java/cn/rbc/codeeditor/view/autocomplete/AutoPanelAdapter.java +++ b/codeeditor/src/main/java/cn/rbc/codeeditor/view/autocomplete/AutoPanelAdapter.java @@ -93,8 +93,8 @@ public View getView(int i, View view, ViewGroup viewGroup) { boolean isDarkMode = mTextFiled.getColorScheme().isDark(); log(text); int t; - if ((t=text.indexOf('(')) >= 0) { - //函数 + if ((t=text.indexOf(tp==15?' ':'(')) >= 0) { + // 宏、函数 ForegroundColorSpan argsForegroundColorSpan = null; spannableString = new SpannableString(text); if(isDarkMode) { @@ -106,20 +106,8 @@ public View getView(int i, View view, ViewGroup viewGroup) { } spannableString.setSpan(foregroundColorSpan, 0, t, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); spannableString.setSpan(argsForegroundColorSpan, t, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } else if (tp == 14) { - //关键字 - // if(setting.isDarkMode()) { - // foregroundColorSpan = new ForegroundColorSpan(Color.YELLOW); - // } - // else{ - foregroundColorSpan = new ForegroundColorSpan(mAutoComplete._textColor); - // } - // text = text.substring(3); - spannableString = new SpannableString(text); - spannableString.setSpan(new StyleSpan(Typeface.BOLD), 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - spannableString.setSpan(foregroundColorSpan, 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } else { - //其他 + // 其他 spannableString = new SpannableString(text); // if(setting.isDarkMode()) { // foregroundColorSpan = new ForegroundColorSpan(Color.WHITE); @@ -127,6 +115,8 @@ public View getView(int i, View view, ViewGroup viewGroup) { // else{ foregroundColorSpan = new ForegroundColorSpan(mAutoComplete._textColor); // } + if (tp == 14) // 关键字 + spannableString.setSpan(new StyleSpan(Typeface.BOLD), 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); spannableString.setSpan(foregroundColorSpan, 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } textView.setText(spannableString); diff --git a/fastlane/metadata/android/en-US/changelogs/3.txt b/fastlane/metadata/android/en-US/changelogs/3.txt new file mode 100644 index 0000000..04466e7 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/3.txt @@ -0,0 +1,6 @@ +• Update target API to 30 (Android 10) +• Update color schemas +• Support opening any text files +• Add 'Default font size' and 'Show hidden files' options +• Fix bugs when connecting to language server +• Fix bugs that modifications are lost while rotating or etc