Refactor: Completion Manager

This commit is contained in:
zt515 2017-07-22 00:11:53 +08:00
parent 438e1e4d24
commit b611b0eaf4
30 changed files with 332 additions and 117 deletions

View File

@ -64,6 +64,6 @@ dependencies {
compile 'com.github.wrdlbrnft:modular-adapter:0.2.0.6'
compile 'com.github.wrdlbrnft:sorted-list-adapter:0.2.0.19'
compile 'com.simplecityapps:recyclerview-fastscroll:1.0.16'
compile 'com.ramotion.cardslider:card-slider:0.1.0'
// compile 'com.ramotion.cardslider:card-slider:0.1.0'
compile 'com.github.igalata:Bubble-Picker:v0.2.4'
}

View File

@ -32,9 +32,9 @@
<activity
android:name=".ui.term.NeoTermRemoteInterface"
android:theme="@style/AppTheme.Dark"
android:excludeFromRecents="true"
android:exported="false">
android:exported="false"
android:theme="@style/AppTheme.Dark">
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
@ -114,6 +114,10 @@
android:name=".services.NeoTermService"
android:enabled="true" />
<service
android:name=".services.NeoFloatingTermService"
android:enabled="true" />
<meta-data
android:name="com.sec.android.support.multiwindow"
android:value="true" />

View File

@ -2,6 +2,7 @@ package io.neoterm
import android.app.Application
import io.neoterm.customize.color.ColorSchemeManager
import io.neoterm.customize.completion.AutoCompletionManager
import io.neoterm.customize.font.FontManager
import io.neoterm.customize.script.UserScriptManager
import io.neoterm.preference.NeoPreference
@ -21,6 +22,7 @@ class App : Application() {
ColorSchemeManager.init(this)
FontManager.init(this)
UserScriptManager.init(this)
AutoCompletionManager.init(this)
}
companion object {

View File

@ -0,0 +1,14 @@
package io.neoterm.customize.completion
import android.content.Context
import io.neoterm.customize.completion.provider.PathProvider
import io.neoterm.frontend.completion.CompletionManager
/**
* @author kiva
*/
object AutoCompletionManager {
fun init(context: Context) {
CompletionManager.registerProvider(PathProvider())
}
}

View File

@ -0,0 +1,44 @@
package io.neoterm.customize.completion.provider
import io.neoterm.frontend.completion.model.CompletionCandidate
import io.neoterm.frontend.completion.provider.ICandidateProvider
import java.io.File
/**
* @author kiva
*/
class PathProvider : ICandidateProvider {
override val providerName: String
get() = "NeoTermProvider.PathProvider"
override fun provideCandidates(text: String): List<CompletionCandidate>? {
var file = File(text)
if (file.isDirectory) {
if (file.canRead()) {
val candidates = mutableListOf<CompletionCandidate>()
file.listFiles().mapTo(candidates, {
CompletionCandidate(it.name)
})
return candidates
}
return null
}
val partName = file.name
file = file.parentFile
if (file.canRead()) {
val candidates = mutableListOf<CompletionCandidate>()
file.listFiles({ pathname -> pathname.name.startsWith(partName) })
.mapTo(candidates, {
CompletionCandidate(it.name)
})
return candidates
}
return null
}
override fun canComplete(text: String): Boolean {
return text.startsWith('/')
}
}

View File

@ -1,4 +1,4 @@
package io.neoterm.frontend.completion
package io.neoterm.customize.completion.widget
import android.content.Context
import android.view.Gravity
@ -12,13 +12,14 @@ import android.widget.TextView
import io.neoterm.R
import io.neoterm.backend.TerminalColors
import io.neoterm.customize.color.ColorSchemeManager
import io.neoterm.frontend.completion.model.CompletionCandidate
import io.neoterm.view.TerminalView
/**
* @author kiva
*/
class AutoCompletePopupWindow(val context: Context) {
var candidates: List<CompleteCandidate>? = null
class CandidatePopupWindow(val context: Context) {
var candidates: List<CompletionCandidate>? = null
var popupWindow: PopupWindow? = null
var wantsToFinish = false
var candidateAdapter: CandidateAdapter? = null
@ -60,21 +61,21 @@ class AutoCompletePopupWindow(val context: Context) {
candidates = null
}
class CandidateAdapter(val autoCompletePopupWindow: AutoCompletePopupWindow) : BaseAdapter() {
class CandidateAdapter(val candidatePopupWindow: CandidatePopupWindow) : BaseAdapter() {
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
var convertView = convertView
val viewHolder: CandidateViewHolder =
if (convertView != null) {
convertView.tag as CandidateViewHolder
} else {
convertView = LayoutInflater.from(autoCompletePopupWindow.context)
convertView = LayoutInflater.from(candidatePopupWindow.context)
.inflate(R.layout.item_complete_candidate, null, false)
val viewHolder = CandidateViewHolder(convertView)
convertView.tag = viewHolder
viewHolder
}
val candidate = getItem(position) as CompleteCandidate
val candidate = getItem(position) as CompletionCandidate
viewHolder.apply {
display.text = candidate.displayName
if (candidate.description != null) {
@ -90,7 +91,7 @@ class AutoCompletePopupWindow(val context: Context) {
}
override fun getItem(position: Int): Any? {
return autoCompletePopupWindow.candidates?.get(position)
return candidatePopupWindow.candidates?.get(position)
}
override fun getItemId(position: Int): Long {
@ -98,7 +99,7 @@ class AutoCompletePopupWindow(val context: Context) {
}
override fun getCount(): Int {
return autoCompletePopupWindow.candidates?.size ?: 0
return candidatePopupWindow.candidates?.size ?: 0
}
}

View File

@ -2,10 +2,12 @@ package io.neoterm.frontend.client
import android.util.Log
import android.view.KeyEvent
import io.neoterm.frontend.completion.AutoCompleteManager
import io.neoterm.frontend.completion.CompleteCandidate
import io.neoterm.frontend.completion.AutoCompletePopupWindow
import io.neoterm.frontend.completion.OnAutoCompleteListener
import io.neoterm.BuildConfig
import io.neoterm.customize.completion.widget.CandidatePopupWindow
import io.neoterm.frontend.completion.CompletionManager
import io.neoterm.frontend.completion.listener.OnAutoCompleteListener
import io.neoterm.frontend.completion.model.CompletionCandidate
import io.neoterm.frontend.completion.model.CompletionResult
import io.neoterm.view.TerminalView
import java.util.*
@ -15,7 +17,7 @@ import java.util.*
class TermCompleteListener(var terminalView: TerminalView?) : OnAutoCompleteListener {
private val inputStack = Stack<Char>()
private val popupWindow = AutoCompletePopupWindow(terminalView!!.context)
private val popupWindow = CandidatePopupWindow(terminalView!!.context)
override fun onKeyCode(keyCode: Int, keyMod: Int) {
when (keyCode) {
@ -52,25 +54,39 @@ class TermCompleteListener(var terminalView: TerminalView?) : OnAutoCompleteList
return
}
val candidates = AutoCompleteManager.filter(text)
Log.e("NeoTerm-AC", "Completing for $text")
candidates.forEach {
Log.e("NeoTerm-AC", " Candidate: ${it.completeString}")
val result = CompletionManager.tryCompleteFor(text)
if (!result.hasResult()) {
// A provider accepted the task
// But no candidates are provided
// Give it zero angrily!
result.markScore(0)
return
}
if (candidates.isNotEmpty()) {
showAutoCompleteCandidates(candidates)
if (BuildConfig.DEBUG) {
val candidates = result.candidates
Log.e("NeoTerm-AC", "Completing for $text")
candidates.forEach {
Log.e("NeoTerm-AC", " Candidate: ${it.completeString}")
}
}
showAutoCompleteCandidates(result)
}
private fun showAutoCompleteCandidates(candidates: List<CompleteCandidate>) {
popupWindow.candidates = candidates
private fun showAutoCompleteCandidates(result: CompletionResult) {
popupWindow.candidates = result.candidates
popupWindow.show(terminalView!!)
}
private fun getCurrentEditingText(): String {
val builder = StringBuilder()
val size = inputStack.size
(0..(size - 1))
var start = inputStack.lastIndexOf(' ')
if (start < 0) {
start = 0
}
(start..(size - 1))
.map { inputStack[it] }
.takeWhile { !(it == 0.toChar() || it == ' ') }
.forEach { builder.append(it) }

View File

@ -2,7 +2,7 @@ package io.neoterm.frontend.client
import io.neoterm.backend.TerminalSession
import io.neoterm.view.ExtraKeysView
import io.neoterm.frontend.completion.OnAutoCompleteListener
import io.neoterm.frontend.completion.listener.OnAutoCompleteListener
import io.neoterm.view.TerminalView
/**

View File

@ -1,37 +0,0 @@
package io.neoterm.frontend.completion
/**
* @author kiva
*/
object AutoCompleteManager {
private val completeCandidates = mutableListOf<CompleteCandidate>()
init {
val programs = arrayOf("ls", "clean", "exit", "apt", "neoterm-normalize-binary", "less", "ln", "lsof")
val desc = arrayOf("List files and directories",
"Clear screen",
"Exit current executablePath",
"Installing, Updating, Upgrading packages",
"Fix program error caused by linux shebang",
"View files",
"Create symlinks or hardlinks",
"List opened files")
for (i in programs.indices) {
val candidate = CompleteCandidate(programs[i])
candidate.description = desc[i]
completeCandidates.add(candidate)
}
}
fun filter(text: String) : List<CompleteCandidate> {
val result = mutableListOf<CompleteCandidate>()
if (text.isNotEmpty()) {
completeCandidates.forEach {
if (it.completeString.startsWith(text, ignoreCase = true)) {
result.add(it)
}
}
}
return result
}
}

View File

@ -0,0 +1,40 @@
package io.neoterm.frontend.completion
import io.neoterm.frontend.completion.model.CompletionResult
import io.neoterm.frontend.completion.provider.ICandidateProvider
/**
* @author kiva
*/
object CompletionManager {
private val candidateProviders = mutableMapOf<String, ICandidateProvider>()
fun registerProvider(provider: ICandidateProvider) {
this.candidateProviders.put(provider.providerName, provider)
}
fun unregisterProvider(providerName: String) {
this.candidateProviders.remove(providerName)
}
fun unregisterProvider(provider: ICandidateProvider) {
unregisterProvider(provider.providerName)
}
fun getProvider(providerName: String): ICandidateProvider? {
return candidateProviders[providerName]
}
fun tryCompleteFor(text: String): CompletionResult {
val detector = detectProviders(text)
val provider = detector.detectBest()
val candidates = provider?.provideCandidates(text) ?: listOf()
return CompletionResult(candidates, detector)
}
private fun detectProviders(text: String): ProviderDetector {
return ProviderDetector(candidateProviders.values
.takeWhile { it.canComplete(text) })
}
}

View File

@ -1,13 +0,0 @@
package io.neoterm.frontend.completion;
/**
* @author Kiva
* @version 1.0
*/
public interface OnAutoCompleteListener {
void onAutoComplete(String newText);
void onKeyCode(int keyCode, int keyMod);
void onCleanUp();
}

View File

@ -0,0 +1,25 @@
package io.neoterm.frontend.completion
import io.neoterm.frontend.completion.listener.MarkScoreListener
import io.neoterm.frontend.completion.provider.ICandidateProvider
/**
* @author kiva
*/
class ProviderDetector(val providers: List<ICandidateProvider>) : MarkScoreListener {
private var detectedProvider: ICandidateProvider? = null
override fun onMarkScore(score: Int) {
// TODO: Save provider score
}
fun detectBest(): ICandidateProvider? {
// TODO: detect best
detectedProvider = if (providers.isEmpty())
null
else
providers[0]
return detectedProvider
}
}

View File

@ -0,0 +1,9 @@
package io.neoterm.frontend.completion.listener
/**
* @author kiva
*/
interface MarkScoreListener {
fun onMarkScore(score: Int)
}

View File

@ -0,0 +1,14 @@
package io.neoterm.frontend.completion.listener
/**
* @author Kiva
* *
* @version 1.0
*/
interface OnAutoCompleteListener {
fun onAutoComplete(newText: String?)
fun onKeyCode(keyCode: Int, keyMod: Int)
fun onCleanUp()
}

View File

@ -1,9 +1,9 @@
package io.neoterm.frontend.completion
package io.neoterm.frontend.completion.model
/**
* @author kiva
*/
class CompleteCandidate(var completeString: String) {
class CompletionCandidate(var completeString: String) {
var displayName: String = completeString
var description: String? = null
}

View File

@ -0,0 +1,16 @@
package io.neoterm.frontend.completion.model
import io.neoterm.frontend.completion.listener.MarkScoreListener
/**
* @author kiva
*/
class CompletionResult(val candidates: List<CompletionCandidate>, var scoreMarker: MarkScoreListener) {
fun markScore(score: Int) {
scoreMarker.onMarkScore(score)
}
fun hasResult(): Boolean {
return candidates.isNotEmpty()
}
}

View File

@ -0,0 +1,15 @@
package io.neoterm.frontend.completion.provider
import io.neoterm.frontend.completion.model.CompletionCandidate
/**
* @author kiva
*/
interface ICandidateProvider {
val providerName: String
fun provideCandidates(text: String): List<CompletionCandidate>?
fun canComplete(text: String): Boolean
}

View File

@ -1,18 +1,13 @@
package io.neoterm.frontend.floating
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.content.Context
import android.content.DialogInterface
import android.view.LayoutInflater
import android.view.View
import io.neoterm.R
import io.neoterm.backend.TerminalSession
import io.neoterm.frontend.ShellParameter
import io.neoterm.frontend.tinyclient.BasicSessionCallback
import io.neoterm.frontend.tinyclient.BasicViewClient
import io.neoterm.frontend.ShellParameter
import io.neoterm.utils.TerminalUtils
import io.neoterm.view.TerminalView
/**
* @author kiva
@ -23,9 +18,7 @@ class TerminalDialog(val context: Context) {
fun onSessionFinished(dialog: TerminalDialog, finishedSession: TerminalSession?)
}
@SuppressLint("InflateParams")
private var view: View = LayoutInflater.from(context).inflate(R.layout.ui_term_dialog, null, false)
private var terminalView: TerminalView
private var termWindowView = WindowTermView(context)
private var terminalSessionCallback: BasicSessionCallback
private var dialog: AlertDialog? = null
private var terminalSession: TerminalSession? = null
@ -33,10 +26,9 @@ class TerminalDialog(val context: Context) {
private var cancelListener: DialogInterface.OnCancelListener? = null
init {
terminalView = view.findViewById(R.id.terminal_view_dialog) as TerminalView
TerminalUtils.setupTerminalView(terminalView, BasicViewClient(terminalView))
termWindowView.setTerminalViewClient(BasicViewClient(termWindowView.terminalView))
terminalSessionCallback = object : BasicSessionCallback(terminalView) {
terminalSessionCallback = object : BasicSessionCallback(termWindowView.terminalView) {
override fun onSessionFinished(finishedSession: TerminalSession?) {
sessionFinishedCallback?.onSessionFinished(this@TerminalDialog, finishedSession)
super.onSessionFinished(finishedSession)
@ -50,7 +42,7 @@ class TerminalDialog(val context: Context) {
}
dialog = AlertDialog.Builder(context)
.setView(view)
.setView(termWindowView.rootView)
.setOnCancelListener {
terminalSession?.finishIfRunning()
cancelListener?.onCancel(it)
@ -63,7 +55,7 @@ class TerminalDialog(val context: Context) {
.callback(terminalSessionCallback)
.systemShell(false)
terminalSession = TerminalUtils.createShellSession(context, parameter)
terminalView.attachSession(terminalSession)
termWindowView.attachSession(terminalSession)
return this
}
@ -94,8 +86,7 @@ class TerminalDialog(val context: Context) {
fun imeEnabled(enabled: Boolean): TerminalDialog {
if (enabled) {
terminalView.isFocusable = true
terminalView.isFocusableInTouchMode = true
termWindowView.setInputMethodEnabled(true)
}
return this
}

View File

@ -1,7 +0,0 @@
package io.neoterm.frontend.floating
/**
* @author kiva
*/
class TerminalWindowView {
}

View File

@ -0,0 +1,40 @@
package io.neoterm.frontend.floating
import android.annotation.SuppressLint
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.inputmethod.InputMethodManager
import io.neoterm.R
import io.neoterm.backend.TerminalSession
import io.neoterm.utils.TerminalUtils
import io.neoterm.view.TerminalView
import io.neoterm.view.TerminalViewClient
/**
* @author kiva
*/
class WindowTermView(val context: Context) {
@SuppressLint("InflateParams")
var rootView: View = LayoutInflater.from(context).inflate(R.layout.ui_term_dialog, null, false)
private set
var terminalView: TerminalView = rootView.findViewById(R.id.terminal_view_dialog) as TerminalView
private set
init {
TerminalUtils.setupTerminalView(terminalView)
}
fun setTerminalViewClient(terminalViewClient: TerminalViewClient?) {
terminalView.setTerminalViewClient(terminalViewClient)
}
fun attachSession(terminalSession: TerminalSession?) {
terminalView.attachSession(terminalSession)
}
fun setInputMethodEnabled(enabled: Boolean) {
terminalView.isFocusable = enabled
terminalView.isFocusableInTouchMode = enabled
}
}

View File

@ -19,6 +19,10 @@ object NeoPreference {
const val KEY_HAPPY_EGG = "neoterm_fun_happy"
const val KEY_FONT_SIZE = "neoterm_general_font_size"
const val KEY_CURRENT_SESSION = "neoterm_service_current_session"
// const val KEY_FLOATING_WINDOW_X = "neoterm_floating_window_x"
// const val KEY_FLOATING_WINDOW_Y = "neoterm_floating_window_y"
// const val KEY_FLOATING_WIDTH = "neoterm_floating_window_width"
// const val KEY_FLOATING_HEIGHT = "neoterm_floating_window_height"
const val VALUE_HAPPY_EGG_TRIGGER = 8
const val VALUE_NEOTERM_ONLY = "NeoTermOnly"
@ -96,6 +100,23 @@ object NeoPreference {
return null
}
// fun storeWindowSize(context: Context, width: Int, height: Int) {
// store(KEY_FLOATING_WIDTH, width)
// store(KEY_FLOATING_HEIGHT, height)
// }
//
// fun storeWindowLocation(context: Context, x: Int, y: Int) {
// store(KEY_FLOATING_WINDOW_X, x)
// store(KEY_FLOATING_WINDOW_Y, y)
// }
//
// fun applySavedWindowParameter(context: Context, layout: WindowManager.LayoutParams) {
// layout.x = loadInt(KEY_FLOATING_WINDOW_X, 200)
// layout.y = loadInt(KEY_FLOATING_WINDOW_Y, 200)
// layout.width = loadInt(KEY_FLOATING_WIDTH, 500)
// layout.height = loadInt(KEY_FLOATING_HEIGHT, 800)
// }
/**
* TODO
* To print the job name about to be executed in bash:

View File

@ -150,8 +150,11 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference
startActivity(Intent(this, PackageManagerActivity::class.java))
true
}
R.id.menu_item_discovery -> {
startActivity(Intent(this, SetupActivity::class.java))
R.id.menu_item_float_up -> {
val tab = tabSwitcher.selectedTab
if (tab != null && tab is TermTab) {
floatTabUp(tab)
}
true
}
R.id.menu_item_new_session -> {
@ -325,6 +328,10 @@ class NeoTermActivity : AppCompatActivity(), ServiceConnection, SharedPreference
super.onActivityResult(requestCode, resultCode, data)
}
private fun floatTabUp(tab: TermTab) {
}
private fun enterSystemShell() {
toggleSwitcher(showSwitcher = true, easterEgg = false)
addNewSession("NeoTerm #0", systemShell, createRevealAnimation())

View File

@ -16,7 +16,7 @@ import io.neoterm.frontend.client.TermCompleteListener
import io.neoterm.ui.term.NeoTermActivity
import io.neoterm.utils.TerminalUtils
import io.neoterm.view.ExtraKeysView
import io.neoterm.frontend.completion.OnAutoCompleteListener
import io.neoterm.frontend.completion.listener.OnAutoCompleteListener
import io.neoterm.view.TerminalView
/**

View File

@ -35,7 +35,7 @@ import io.neoterm.backend.KeyHandler;
import io.neoterm.backend.TerminalBuffer;
import io.neoterm.backend.TerminalEmulator;
import io.neoterm.backend.TerminalSession;
import io.neoterm.frontend.completion.OnAutoCompleteListener;
import io.neoterm.frontend.completion.listener.OnAutoCompleteListener;
/**
* View displaying and interacting with a {@link TerminalSession}.
@ -231,12 +231,19 @@ public final class TerminalView extends View {
/**
* @param client Listener for all kinds of key events, both hardware and IME (which makes it different from that
* available with {@link View#setOnKeyListener(OnKeyListener)}.
* available with {@link View#setOnKeyListener(OnKeyListener)}.
*/
public void setTerminalViewClient(TerminalViewClient client) {
this.mClient = client;
}
@Override
public void setOnKeyListener(OnKeyListener l) {
if (l instanceof TerminalViewClient) {
setTerminalViewClient(((TerminalViewClient) l));
}
}
/**
* Attach a {@link TerminalSession} to this view.
*

View File

@ -50,8 +50,7 @@
android:layout_marginEnd="@dimen/preview_layout_margin"
android:text="@string/setup_next"
android:textColor="#9b9b9b"
android:textStyle="bold"
android:visibility="invisible" />
android:textStyle="bold" />
<com.igalata.bubblepicker.rendering.BubblePicker
android:id="@+id/bubble_picker"

View File

@ -33,8 +33,8 @@ License.
app:showAsAction="never" />
<item
android:id="@+id/menu_item_discovery"
android:title="@string/discovery"
android:id="@+id/menu_item_float_up"
android:title="@string/float_up"
app:showAsAction="never" />
<item

View File

@ -5,13 +5,14 @@
<string name="copy_text">复制</string>
<string name="general_settings">通用设置</string>
<string name="new_session">新建终端</string>
<string name="package_settings">软件包设置</string>
<string name="package_settings">软件包</string>
<string name="paste_text">粘贴</string>
<string name="pref_general_backspace_map_to_esc">返回键发送ESC</string>
<string name="pref_general_backspace_map_to_esc_desc">当返回键按下时发送ESC而不是关闭窗口</string>
<string name="pref_general_bell">响铃</string>
<string name="pref_general_bell_desc">收到 \'\\a\' 时响铃</string>
<string name="pref_general_shell_desc">登录时使用指定的 Shell</string>
<string name="pref_general_shell">登录程序</string>
<string name="pref_general_shell_desc">登录时使用指定的程序</string>
<string name="pref_general_vibrate">振动</string>
<string name="pref_general_vibrate_desc">收到 \'\\a\' 时振动</string>
<string name="pref_general_program_selection">程序选择模式</string>
@ -39,7 +40,7 @@
<string name="ui_settings">界面设置</string>
<string name="shell_not_found">Shell %s 未找到, 请先安装.</string>
<string name="installer_message">正在安装</string>
<string name="installer_install_zsh_required">将要安装 oh-my-zsh 并将您的登录 executablePath 切换到 zsh</string>
<string name="installer_install_zsh_required">将要安装 oh-my-zsh 并将您的登录程序切换到 zsh</string>
<string name="fullscreen_mode_changed">全屏模式已改变,请重启 NeoTerm</string>
<string name="permission_denied">NeoTerm 无法取得必需的权限,正在退出</string>
<string name="error">还有这种操作?</string>
@ -50,6 +51,7 @@
<string name="package_list_empty">软件包列表为空,请检查你的软件源</string>
<string name="menu_refresh_list">刷新</string>
<string name="menu_update">更新并刷新</string>
<string name="float_up">浮动窗口</string>
<string-array name="pref_general_program_selection_entries">
<item>只使用 NeoTerm</item>

View File

@ -29,6 +29,7 @@
<string name="customization_settings">Customization</string>
<string name="toggle_ime">Toggle IME</string>
<string name="float_up">Float Up</string>
<string name="shell_not_found">Shell %s not found, please install it first.</string>
<string name="fullscreen_mode_changed">FullScreen mode changed, please restart NeoTerm.</string>
<string name="permission_denied">NeoTerm cannot get essential permissions, exiting.</string>
@ -51,7 +52,7 @@
<string name="crash_stack_trace">Stack Trace</string>
<string name="installer_message">Installing</string>
<string name="installer_install_zsh_required">We are about to install oh-my-zsh and switch your login executablePath to zsh</string>
<string name="installer_install_zsh_required">We are about to install oh-my-zsh and switch your login program to zsh</string>
<string name="pref_general_bell">Bell</string>
<string name="pref_general_bell_desc">Bell when receiving \'\\a\'</string>
@ -59,8 +60,8 @@
<string name="pref_general_vibrate_desc">Vibrate when receiving \'\\a\'</string>
<string name="pref_general_backspace_map_to_esc">BackSpace Mapped to Esc</string>
<string name="pref_general_backspace_map_to_esc_desc">Send esc when backspace is pressed</string>
<string name="pref_general_shell" translatable="false">Shell</string>
<string name="pref_general_shell_desc">Which executablePath should we use when login</string>
<string name="pref_general_shell">Login Program</string>
<string name="pref_general_shell_desc">Which program should we use as shell when login</string>
<string name="pref_general_program_selection">Program Selection</string>
<string name="pref_general_program_selection_desc">When both Neo Term and your Android OS have a program, which one should we choose?</string>
<string name="pref_general_initial_command">Initial Command</string>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
<!-- See https://developer.android.com/training/backup/autosyncapi.html -->
<include domain="file" path="home/auto_backup" />
<include domain="file" path="home/.neoterm" />
</full-backup-content>

View File

@ -37,4 +37,8 @@ public abstract class AbstractTabViewHolder {
*/
public ImageButton closeButton;
/**
* The custom button of a tab.
*/
public ImageButton customButton;
}